Buenas prácticas oficiales de Symfony

9.3. La anotación @Security

La anotación @Security es la forma más sencilla de controlar el acceso a cada controlador de la aplicación. Su sintaxis es muy fácil de leer y resulta muy cómodo configurar el acceso de cada controlador justo encima del código de la acción.

En la aplicación de prueba, para crear un nuevo artículo en el blog es necesario disponer del role ROLE_ADMIN. Con la anotación @Security, el código resultante es:

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
// ...

/**
 * Displays a form to create a new Post entity.
 *
 * @Route("/new", name="admin_post_new")
 * @Security("has_role('ROLE_ADMIN')")
 */
public function newAction()
{
    // ...
}

9.3.1. Definiendo restricciones complejas mediante expresiones

Si la lógica de tu aplicación es más compleja, puedes utilizar expresiones dentro de la anotación @Security. En el siguiente ejemplo, se restringe el acceso para que sólo puedan editar los artículos aquellos usuarios cuyo email coincida con el autor del artículo (obtenido mediante el método getAuthorEmail del objeto Post):

use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Route("/{id}/edit", name="admin_post_edit")
 * @Security("user.getEmail() == post.getAuthorEmail()")
 */
public function editAction(Post $post)
{
    // ...
}

Esta configuración requiere del uso de los ParamConverters, ya que hay que realizar una consulta a la base de datos para obtener el objeto Post y asignarlo al argumento $post del controlador. Esto es lo que hace que puedas utilizar una variable llamada post dentro de la expresión de la anotación @Security.

La gran desventaja de las expresiones es que no se pueden reutilizar fácilmente en otras partes de la aplicación. Imagina que quieres añadir en una plantilla un enlace a la acción de editar el artículo pero solamente si el usuario que está accediendo a la aplicación es autor de ese artículo. Ahora mismo tendrías que repetir esa misma expresión utilizando el código Twig:

{% if app.user and app.user.email == post.authorEmail %}
    <a href=""> ... </a>
{% endif %}

La solución más sencilla para reutilizar esta lógica sería crear un nuevo método en la entidad Post que compruebe si el usuario indicado es el autor del artículo:

// src/AppBundle/Entity/Post.php
// ...

class Post
{
    // ...

    /**
     * ¿Es el usuario indicado el autor del Post?
     *
     * @return bool
     */
    public function isAuthor(User $user = null)
    {
        return $user && $user->getEmail() == $this->getAuthorEmail();
    }
}

Ahora ya puedes reutilizar este mismo método tanto en el controlador como en la plantilla:

use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Route("/{id}/edit", name="admin_post_edit")
 * @Security("post.isAuthor(user)")
 */
public function editAction(Post $post)
{
    // ...
}
{% if post.isAuthor(app.user) %}
    <a href=""> ... </a>
{% endif %}