Skip to content

Commit

Permalink
minor #5184 Removing a section about Roles that I think has no real u…
Browse files Browse the repository at this point in the history
…se-case (weaverryan)

This PR was merged into the 2.3 branch.

Discussion
----------

Removing a section about Roles that I think has no real use-case

Hi guys!

| Q             | A
| ------------- | ---
| Doc fix?      | no
| New docs?     | no
| Applies to    | 2.3+
| Fixed tickets | n/a

This came up after a conversation with someone on Twitter that got hung up on this section (they were also new to Doctrine, so the ManyToMany relationship was a bit challenging). But it occurred to me that this section is totally unnecessary for beginners, and maybe totally unnecessary for *everyone*. Is there a real-use case for this?

It was added 4 years ago at sha: 5a2824b, and it's original purpose was to talk
about Roles as domain objects. If this has a real-use case, then we should clarify
what it is, and move it to its own cookbook. This is a details that beginners are
trying to deal with, unnecessarily.

Thanks!

Commits
-------

6c9ce9c Removing a section about Roles that I think has no real use-case
  • Loading branch information
weaverryan committed Apr 17, 2015
2 parents 2948d6e + 6c9ce9c commit 16e0849
Showing 1 changed file with 0 additions and 207 deletions.
207 changes: 0 additions & 207 deletions cookbook/security/entity_provider.rst
Original file line number Diff line number Diff line change
Expand Up @@ -525,213 +525,6 @@ This tells Symfony to *not* query automatically for the User. Instead, when
someone logs in, the ``loadUserByUsername()`` method on ``UserRepository``
will be called.

Managing Roles in the Database
------------------------------

The end of this tutorial focuses on how to store and retrieve a list of roles
from the database. As mentioned previously, when your user is loaded, its
``getRoles()`` method returns the array of security roles that should be
assigned to the user. You can load this data from anywhere - a hardcoded
list used for all users (e.g. ``array('ROLE_USER')``), a Doctrine array
property called ``roles``, or via a Doctrine relationship, as you'll learn
about in this section.

.. caution::

In a typical setup, you should always return at least 1 role from the ``getRoles()``
method. By convention, a role called ``ROLE_USER`` is usually returned.
If you fail to return any roles, it may appear as if your user isn't
authenticated at all.

.. caution::

In order to work with the security configuration examples on this page
all roles must be prefixed with ``ROLE_`` (see
the :ref:`section about roles <book-security-roles>` in the book). For
example, your roles will be ``ROLE_ADMIN`` or ``ROLE_USER`` instead of
``ADMIN`` or ``USER``.

In this example, the ``AppBundle:User`` entity class defines a
many-to-many relationship with a ``AppBundle:Role`` entity class.
A user can be related to several roles and a role can be composed of
one or more users. The previous ``getRoles()`` method now returns
the list of related roles. Notice that ``__construct()`` and ``getRoles()``
methods have changed::

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
// ...

class User implements AdvancedUserInterface, \Serializable
{
// ...

/**
* @ORM\ManyToMany(targetEntity="Role", inversedBy="users")
*
*/
private $roles;

public function __construct()
{
$this->roles = new ArrayCollection();
}

public function getRoles()
{
return $this->roles->toArray();
}

// ...

}

The ``AppBundle:Role`` entity class defines three fields (``id``,
``name`` and ``role``). The unique ``role`` field contains the role name
(e.g. ``ROLE_ADMIN``) used by the Symfony security layer to secure parts
of the application::

// src/AppBundle/Entity/Role.php
namespace AppBundle\Entity;

use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Table(name="app_role")
* @ORM\Entity()
*/
class Role implements RoleInterface
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id()
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @ORM\Column(name="name", type="string", length=30)
*/
private $name;

/**
* @ORM\Column(name="role", type="string", length=20, unique=true)
*/
private $role;

/**
* @ORM\ManyToMany(targetEntity="User", mappedBy="roles")
*/
private $users;

public function __construct()
{
$this->users = new ArrayCollection();
}

/**
* @see RoleInterface
*/
public function getRole()
{
return $this->role;
}

// ... getters and setters for each property
}

For brevity, the getter and setter methods are hidden, but you can
:ref:`generate them <book-doctrine-generating-getters-and-setters>`:

.. code-block:: bash
$ php app/console doctrine:generate:entities AppBundle/Entity/User
Don't forget also to update your database schema:

.. code-block:: bash
$ php app/console doctrine:schema:update --force
This will create the ``app_role`` table and a ``user_role`` that stores
the many-to-many relationship between ``app_user`` and ``app_role``. If
you had one user linked to one role, your database might look something like
this:

.. code-block:: bash
$ mysql> SELECT * FROM app_role;
+----+-------+------------+
| id | name | role |
+----+-------+------------+
| 1 | admin | ROLE_ADMIN |
+----+-------+------------+
$ mysql> SELECT * FROM user_role;
+---------+---------+
| user_id | role_id |
+---------+---------+
| 1 | 1 |
+---------+---------+
And that's it! When the user logs in, Symfony security system will call the
``User::getRoles`` method. This will return an array of ``Role`` objects
that Symfony will use to determine if the user should have access to certain
parts of the system.

.. sidebar:: What's the purpose of the RoleInterface?

Notice that the ``Role`` class implements
:class:`Symfony\\Component\\Security\\Core\\Role\\RoleInterface`. This is
because Symfony's security system requires that the ``User::getRoles`` method
returns an array of either role strings or objects that implement this interface.
If ``Role`` didn't implement this interface, then ``User::getRoles``
would need to iterate over all the ``Role`` objects, call ``getRole``
on each, and create an array of strings to return. Both approaches are
valid and equivalent.

.. _cookbook-doctrine-entity-provider-role-db-schema:

Improving Performance with a Join
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To improve performance and avoid lazy loading of roles when retrieving a user
from the custom entity provider, you can use a Doctrine join to the roles
relationship in the ``UserRepository::loadUserByUsername()`` method. This will
fetch the user and their associated roles with a single query::

// src/AppBundle/Entity/UserRepository.php
namespace AppBundle\Entity;

// ...

class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$q = $this
->createQueryBuilder('u')
->select('u, r')
->leftJoin('u.roles', 'r')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery();

// ...
}

// ...
}

The ``QueryBuilder::leftJoin()`` method joins and fetches related roles from
the ``AppBundle:User`` model class when a user is retrieved by their email
address or username.

.. _`cookbook-security-serialize-equatable`:

Understanding serialize and how a User is Saved in the Session
Expand Down

0 comments on commit 16e0849

Please sign in to comment.