Anotaciones propias para Symfony 2

annotations
annotations

En un proyecto reciente estaba buscando filtros de controladores para implementar una capa de verificación de credenciales por permisos de usuario y fue como me enteré que en symfony existían las anotaciones personalizadas, que son básicamente bloques de texto en forma de comentarios que se escriben en las propiedades o los métodos de las clases y con lo cual mi problema de verificación de credenciales.

En este artículo describiré como crear la clase de tipo anotación y cómo utilizarla.

Lo primero es definir la clase “Permissions.php”, yo lo hice dentro de una carpeta llamada “Annotations” en mi coreBundle que es como mi proyecto concentrador de código.

namespace DemoProject\CoreBundle\Annotations;
 
use Doctrine\Common\Annotations\Annotation;
 
/**
* @Annotation
* @Target({"METHOD"})
*/
final class Permissions extends Annotation
{
	public $is_admin;
}

Como verán la clase es bastante básica, solo fue necesario definir una propiedad, la cual se utilizará como anotación en los comentarios de los métodos que necesito validar, también hay otro parámetro que quizás no les llame tanto la atención, pero que es necesario escribirlo y es el comentario de “@Annotation” y “@Target” .. el primero define que la clase será de tipo anotación, el segundo define el alcance de su uso, para este ejemplo es para utilizar en métodos, existen diferentes tipos según el uso que le quieran dar, por ej. puede ser PROPERTY, CLASS o METHOD.

Ahora que la clase se ha creado, para utilizarla simplemente es necesario incluir la declaración de uso dentro de la clase controladora, para mi caso sería el siguiente:

namespace DemoProject\PagosBundle\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use DemoProject\CoreBundle\Annotations\Permissions;
 
class PagosController extends Controller
{
	/**
	 * @Permissions(is_admin=true)
	 */
	public function getPagosAction()
	{
		return new JsonResponse(array('message' => 'ok'), 200);
	}
}

Si han notado, lo único que hice en este controlador fue agregar el uso de la clase Permissions y utilizar como comentario la clase y la propiedad is_admin .. hasta este punto no tiene ningún efecto el uso de la anotación, la idea es asegurar la acción para que solamente usuarios con privilegios puedan acceder al contenido de respuesta, entonces lo siguiente es crear un EventListener que se lanzará siempre antes de ejecutarse cualquier acción.

Entonces nuevamente desde mi bundle de CoreBundle crearé una carpeta llamada “EventListener” y ahí mismo crear un archivo llamado “BeforeControllerListener.php”.

namespace DemoProject\CoreBundle\EventListener;
 
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 
class BeforeControllerListener
{
    public function __construct($reader, $db)
    {
        $this->db = $db;
        $this->reader = $reader;
    }
 
    public function onKernelController(FilterControllerEvent $event)
    {
            $user = 123;
            foreach ($this->reader->getMethodAnnotations($method) as $configuration) 
            {
                if (isset($configuration->is_admin))
                {
                    $is_admin = bool($configuration->is_admin);
                    $verify = $this->db->getRepository('CoreBundle:PagosEntity')->getInfo($user);
                    if ($verify->is_admin != $is_admin)
                    {
                        throw new AccessDeniedHttpException('Acceso denegado');
                    }
                }
            }
    }
}

Ahora explico, como había mencionado arriba, esta evento se lanzará antes de ejecutarse cualquier acción y desde el método “onKernelController” se buscarán todas las anotaciones y se verificará que exista en particular la propiedad de anotación “is_admin”, si la propiedad es encontrada, entonces se debe de validar contra algo en la BD, por eso es que se hace un db->getRepository->getInfo, donde $user en este caso es un id de usuario, pero podría salir de una sesión, una cookie o de algún token, es sólo un ejemplo, no quiero detallar mucho esta parte.

La idea es que los métodos (acciones) de los controladores que tengan la anotación “Permission(is_admin=true)” se deben verificar contra permisos establecidos en la BD y en caso de que el usuario logueado no tenga permisos para acceder a estas secciones privadas, al validarlas contra la anotación darán como resultado “false” y por ende se lanzará una excepción.

Lo último que hace falta es incluir el EventListener dentro de los servicios del proyecto de symfony y eso lo hacen directamente desde el archivo “services” dentro de su carpeta “config”.

services:
    platform.before_controller_listener:
        class: DemoProject\CoreBundle\EventListener\BeforeControllerListener
        arguments: ["@annotation_reader", "@doctrine.orm.entity_manager"]
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }

Y ahora resumiento el mini-howto .. lo que hice fue crear mi clase de tipo anotación, incluirla en el método de controlador que quiero proteger, luego crear una clase para escuchar los llamados antes de la ejecución de los controladores y para finalizar solo se necesita incluir el listener en la definición de servicios.

Quizás sea algo técnico y complicado, espero que se entienda el concepto, de todas maneras en los comentarios pueden dejar sus preguntas para tratar de aclarar cualquier idea que no haya explicado correctamente.

Espero que les sirva para entender las anotaciones y de paso la inclusión de dependencias en los servicios de su aplicación de symfony.

Happy Coding! 🙂

Co-fundador de Qbit Mexhico, usuario de linux, Developer en tecnologías web.. Nicaragüense, centro en basketball, primer centro en rugby y pintor los fines de semana. Ortögrafo y ambientalista psicológico (de escritorio).. ese soy yo!

Si te ha servido compártelo y difunde nuestro blog..

Twitter LinkedIn Flickr YouTube 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *