Ready to go User System

By the end of this lesson, you’ll have a password protected admin area to handle site management. Along the way:

  • Installing extra bundles
  • Introduction to services
  • Setting up Users through the CLI
  • User authentication and basic security
Follow along using the code available from this GIT repository.
Start - saving_and_retrieving_data
End - ready_to_go_user_system

Installing bundles

Composer is a PHP dependency manager responsible for installing and updating PHP components in a similar manner to Linux packages. It can be configured through the command line or a JSON schema file, if using the command line, the JSON schema is automatically updated. It retrieves assets (by default) from Packagist, a PHP oriented software repository.

For the user management side of things, we’ll be using the FOSUserBundle. From a shell within your project directory, we first download the bundle itself

  1. composer require friendsofsymfony/user-bundle "[email protected]"

With the code downloaded, it still isn’t part of our Symfony project yet. There is a kernel (core) which creates the operating environment in which Symfony operates and the Standard Edition provides a space for us to configure the kernel. Add the following line along with the other definitions

  1. // app/AppKernel.php
  2. public function registerBundles()
  3. {
  4.   $bundles = array(
  5.     ...
  6.     new FOS\UserBundle\FOSUserBundle(),
  7.   );

When registering a bundle in the Kernel, Symfony will look in the namespace you provided for some expected files. The minimum for a piece of code to be a bundle is the following, as taken from the default bundle that you are writing your application in.

  1. <?php
  2.  
  3. namespace AppBundle;
  4.  
  5. use Symfony\Component\HttpKernel\Bundle\Bundle;
  6.  
  7. class AppBundle extends Bundle
  8. {
  9. }

Symfony Configuration

YAML

Symfony configuration can be defined in various formats (ini, raw PHP, XML and YAML), sharing the different formats across the same project. In this tutorial we will be using YAML (YAML Ain't Markup Language) because Symfony Standard Edition is configured with it; it is possible to mix and match the various formats because they get mixed and compiled to PHP.

YAML is a quick to write, human friendly way of defining data serialization, think of it is a way to define multi level PHP arrays with the ability to carry out variable substitution.

I strongly recommend you familiarise yourself with this data format if it is new to you as, it is used for a lot of configuration within Symfony once you start using 3rd party bundles. Click here for a reference, below is a general summary for some of the rules:

  • Key/values are split through use of a colon
  • Indentation is very important and depicts depth
  • When you see a value with curly brackets { } you are creating an associative array so need more key / value pairs
  • When you see a value with square brackets [ ] you are creating a simple one dimensional (1D) array with values seperated by comma's
  • When you see a value on a new line starting with a hyphen - this is another form of creating a nested set of definitions or 1D arrays.
  • When values are wrapped in %, it infers variable substitution will take place by a parameter set elsewhere

FOSUser Configuration

With the User downloaded and hooked in to the kernel, we still need to configure it. Not all componenets will need configuration but, FOSUser bundle is a generic implementation designed to support a wide range of functions so, does need configuration.

  1. # app/config/config.yml
  2. framework:
  3.   # Uncomment this line to enable the Symfony translation service
  4.   translator:      { fallbacks: ["%locale%"] }
  5.  
  6. # Add as a first level configuration
  7. fos_user:
  8.   # ORM because we are using a relational database
  9.   db_driver: orm
  10.   # Which set of firewall rules will security incorporate with
  11.   firewall_name: main
  12.   # The User data object where user information will be stored
  13.   user_class: AppBundle\Entity\User
  14.   # Email communication settings
  15.   from_email:
  16.     address:        [email protected]
  17.     sender_name:    Website Administrator

Security

Security is a first class citizen in Symfony and user authorisation (verifying a user has access) is provided by the firewall service.

Take note that authentication (challenging with a username/password request) and authorisation are not the same thing. Authentication (and loading up the roles to session) may be provided by FOSUser but, it is Symfony which handles the page by page authorisation.

We define these settings in the security configuration file. Copy and paste the following, replacing everything in the file

  1. # app/config/security.yml
  2.  
  3. security:
  4.  
  5.   # What hashing library we will use to encode passwords
  6.   encoders:
  7.     FOS\UserBundle\Model\UserInterface: bcrypt
  8.  
  9.   # Some user roles and an inheritance structure
  10.   role_hierarchy:
  11.     ROLE_ADMIN:       ROLE_USER
  12.     ROLE_SUPER_ADMIN: ROLE_ADMIN
  13.  
  14.   # Services capable of providing authentication
  15.   providers:
  16.     fos_userbundle:
  17.       id: fos_user.user_provider.username
  18.  
  19.   # Request matcher / listener definition
  20.   firewalls:
  21.  
  22.     # A firewall for bypassing security when using the development environment
  23.     dev:
  24.       pattern: ^/(_(profiler|wdt)|css|images|js)/
  25.       security: false
  26.  
  27.     main:
  28.       # Which set of URLs will this firewall rule cover
  29.       pattern: ^/
  30.       form_login:
  31.         provider: fos_userbundle
  32.         csrf_token_generator: security.csrf.token_manager
  33.       logout:       true
  34.       # Allow anonymous users by default
  35.       anonymous:    true
  36.  
  37.   # URL rules to authenticate against specific user roles
  38.   access_control:
  39.     # IS_AUTHENTICATED_ANONYMOUSLY is every user
  40.     - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
  41.     - { path: ^/admin/, role: ROLE_ADMIN }

Compared to the main configuration, there is nothing especially different about it. The same content could be included in the main config.yml file, it's just that the Symfony standard edition abstracts the different functionality in to seperate files. Look at the top of the main configuration file to see where these extra files are included.

  1. # app/config/config.yml
  2. imports:
  3.   - { resource: security.yml}

Routing

In an application that makes use of multiple bundles, it is possible that there will be controllers and routes present beyond our own AppBundle. A user system is no exception, take a look at the following file which is just one set of configurations which can be imported in to your Symfony project from FOSUser.

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!-- vendor/friendsofsymfony/user-bundle/Resources/config/routing/security.xml -->
  3. <routes xmlns="http://symfony.com/schema/routing"
  4.    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5.    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
  6.  
  7.     <route id="fos_user_security_login" path="/login" methods="GET POST">
  8.         <default key="_controller">FOSUserBundle:Security:login</default>
  9.     </route>
  10.  
  11.     <route id="fos_user_security_check" path="/login_check" methods="POST">
  12.         <default key="_controller">FOSUserBundle:Security:check</default>
  13.     </route>
  14.  
  15.     <route id="fos_user_security_logout" path="/logout" methods="GET POST">
  16.         <default key="_controller">FOSUserBundle:Security:logout</default>
  17.     </route>
  18.  
  19. </routes>

One, many or all can be used, for simplicity We will be using only the security.xml route as the site won’t feature public registration. To plug these routes in to our app, Symfony has a configuration file dedicated to routing which we will add the definition to.

  1. # app/config/routing.yml
  2. app:
  3.   resource: "@AppBundle/Controller/"
  4.   type:     annotation
  5.  
  6. fos_user:
  7.   resource: "@FOSUserBundle/Resources/config/routing/security.xml"

You should see in the above file, a reference to app stating that it is using annotations. This is how Symfony picks up your routes! What you did when adding the FOSUser line was to import routes from that bundle and because, type was left blank, the routes are expected to be fully defined in config format.

Whereas the Symfony security rules are defined in an extra included file, the routing definitions are a resource from the router service. In case you want to place the router definitions elsewhere, the configuration option is located at:

  1. # app/config/config.yml
  2. framework:
  3.   router:
  4.     resource: "%kernel.root_dir%/config/routing.yml"

Creating Users

With FOSUser and some security setup, we are ready to create our User Object. To do this, we will create another entity (data object definition), this time extending the FOSUser bundle which gets pulled in with a use statement.

  1. <?php
  2. // src/AppBundle/Entity/User.php
  3. namespace AppBundle\Entity;
  4.  
  5. // Include User clas to override
  6. use FOS\UserBundle\Model\User as BaseUser;
  7. use Doctrine\ORM\Mapping as ORM;
  8.  
  9. /**
  10. * @ORM\Entity
  11. * @ORM\Table(name="fos_user")
  12. */
  13. class User extends BaseUser
  14. {
  15.   /**
  16.    * @ORM\Id
  17.    * @ORM\Column(type="integer")
  18.    * @ORM\GeneratedValue(strategy="AUTO")
  19.    */
  20.   protected $id;
  21. }

The user object we are extending already includes many of the properties you would expect, ID being one but we need to declare it as the primary key ourselves. If you want to find out more about the user object; it can be found at vendor/friendsofsymfony/user-bundle/Model/User.php

We haven’t yet generated the setters and getters or synced the schema to the database so, we need to do that.

  1. # Generate some setter and getter methods automatically
  2. php bin/console doctrine:generate:entities AppBundle/Entity/User
  3. # Validate the DB schema before updating!
  4. php bin/console doctrine:schema:validate
  5. # Run the schema update
  6. php bin/console doctrine:schema:update --force

The User bundle is now ready to use. We have chosen not to have a registration form on the website so, we’ll instead use the CLI to create a master account and promote them to admin. If you are more interested in some of the other commands available (which you should be), pay a visit to the documenation page or just use the first part of the command as shown below

  1. php bin/console fos:user:create adminuser test@example.com adminpassword --super-admin
  2. # For exploring the various command line options
  3. php bin/console fos

Now, visit http://127.0.0.1/app_dev.php/login and try logging in. After logging in, look at the debug toolbar. Previously, it was showing “anon” next to the user but instead now it shows our adminuser

Exercises

  • Look at and try importing some of the other route options from fos_user bundle
  • Explore the FOSUser Objects for their predefined properties and methods
    1. vendor/friendsofsymfony/user-bundle/Model
  • Explore some of the packages available on the Awesome Symfony list or Packagist

Communication, our greatest tool

Whether you want to connect, have a problem I can help with or simply want to drop a line, I welcome your words!