FosUserBundle - Part 2. Personalizando

26 de mayo de 2015

En un artículo anterior daba una introducción al FOSUserBundle. Después de ver lo útil que resulta este al ofrecer una clase usuario lista para desarrollar en nuestras aplicaciones Symfony 2, ahora toca personalizar estas funcionalidades adaptándolas a nuestro proyectos reales.

2.1 Sobreescribiendo plantillas

El primer paso que casi todos damos al utilizar funcionalidades de terceros en nuestras aplicaciones, es que no se note que es de tercero sino que se mescle en nuestro sistema como si siempre hubiece estado ahí. De ahí que, la interfaz de usuario es el punto crítico en este sentido. Siendo necesario que todos los componentes sigan un mismo diseño gráfico y maquetación.

El bundle nos provee con las plantillas que necesitamos para cada una de sus funcionalidades, sin embargo estas no se adaptan directamente en nuestros diseños por lo general. Pero que no cunda el pánico. Utilizando los propios mecanismos de Symfony 2 se pueden sobreescribir estas plantillas.

La documentación muestra dos estrategías a seguir para lograr este objetivo. Pero siguiendo las nuevas Buenas prácticas oficiales de Symfony no recomiendo crear un bundle solo para manejar los usuarios de la aplicación, ya que no sería necesario si solo queremos personalizar el funcionamiento del FOSUserBundle. Estas operaciones se pueden realizar desde la aplicación principal sin necesidad de extender este bundle.

La estrategia que seguiremos es simple, utilizar el órden que usa Symfony 2 para cargar las plantillas de la aplicación. Para cargar una plantilla Symfony siempre comienza a buscar porapp/Resources/<Nombre>Bundle/views si no la encuentra ahí busca ensrc/<Nombre>Bundle/Resources/views. Usando esto, podemos crear un directorio enapp/Resources/FOSUserBundle/views y dentro sobreescribir con la misma estructura de directorio todas las plantillas del FOSUserBundle. Pudiéramos ir hasta donde se encuentran estas plantillas y copiarlas hasta este directorio para apoyarnos del código que ya tiene.

En lo particular, lo primero que siempre sobre escribo es la plantillavendor/friendsofsymfony/user-bundle/Resources/views/layout.html.twig. Esto debido a que todas las demás extienden de esta, y permitiendo de forma unánime que todas las plantillas usen nuestros estilos. Pudiera quedar de la siguiente forma:

{# app/Resources/FOSUserBundle/views/layout.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
    <div class="container">
        <div class="col-md-8">
        {% for type, messages in app.session.flashbag.all() %}
            {% for message in messages %}
                <div class="alert alert-{{ type }}">
                    <button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
                    {{ message | trans({}, 'flashes') }}
                </div>
            {% endfor %}
        {% endfor %}
        {% block fos_user_content %}
        {% endblock fos_user_content %}
        </div>
        <div class="col-md-4"></div>
    </div>
 {% endblock %}

Seguir haciendo esto para todas las demás plantillas que se quieran sobreescribir.

2.2 Sobreescribiendo funciones del controlador mediante eventos

No se aconseja sobreescribir controladores, deberías primero intentar escuchar los eventos que son lanzados en estos para modificar el comportamiento de cada uno. Si aun así no te es suficiente con este tutorial puedes apoyarte en la documentación del proyecto para sobre escribir los controladores.

El bundle provee de un grupos de eventos que son lanzados en distintos lugares de la aplicación. En la clase FOS\UserBundle\FOSUserEvents se recogen todos estos en inglés pero se explican solos.

Todos los controladores siguen la mismo convención. Disparan un evento SUCCESS cuando el formulario es validado antes de salvar el usuarios, y un evento COMPLETED cuando está hecho. Todos los eventos SUCCESS permiten asignar una respuesta al controlador si no desea la redirección por defecto y todos los COMPLETED dan acceso a la respuesta antes de terminar. Los controladores que manejan formulario lanzan además un eventoINITIALIZE cuando se tiene el usuario antes de que sea asignado a el formulario.

Para escuchar los eventos puedes usar cualquier mecanismo de Symfony (Listener o EventSubscriber). Aquí te dejo un ejemplo simple que muestran en la própia documentación del bundle.

// src/Acme/UserBundle/EventListener/PasswordResettingListener.php
 
namespace Acme\UserBundle\EventListener;
 
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
 
/**
 * Listener responsible to change the redirection at the end of the password resetting
 */
class PasswordResettingListener implements EventSubscriberInterface
{
    private $router;
 
    public function __construct(UrlGeneratorInterface $router)
    {
        $this->router = $router;
    }
 
    /**
     * {@inheritDoc}
     */
    public static function getSubscribedEvents()
    {
        return array(
            FOSUserEvents::RESETTING_RESET_SUCCESS => 'onPasswordResettingSuccess',
        );
    }
 
    public function onPasswordResettingSuccess(FormEvent $event)
    {
        $url = $this->router->generate('homepage');
 
        $event->setResponse(new RedirectResponse($url));
    }
}

Y lo registramos de la siguiente manera:

// src/Acme/UserBundle/Resources/config/services.yml
services:
    acme_user.password_resetting:
        class: Acme\UserBundle\EventListener\PasswordResettingListener
        arguments: [@router]
        tags:
            - { name: kernel.event_subscriber }

2.3 Sobreescribiendo formularios

Por lo general nuestros usuarios poseen más atributos de los que el bundle provee. Se hace necesario entonces que los formularios se modifiquen para poder usarlos apropiadamente.

Para esto solo debemos crear un formulario que herede del formulario a sobreescribir, registrarlo como un servicio y decir al FOSUserBundle que ahora el formulario que debe usar es el nuestro. Simplemente les dejo aquí un código sobre sobreescribir el formulario de registro con el campo $name que habla por si solo:

Nuestra entidad debería verse así:

// src/Acme/UserBundle/Entity/User.php
<?php
 
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
 
class User extends BaseUser
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;
 
    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Assert\NotBlank(message="Please enter your name.", groups={"Registration", "Profile"})
     * @Assert\Length(
     *     min=3,
     *     max="255",
     *     minMessage="The name is too short.",
     *     maxMessage="The name is too long.",
     *     groups={"Registration", "Profile"}
     * )
     */
    protected $name;
 
    // ...
}

El nuevo formulario deberia entonces quedar:

// src/Acme/UserBundle/Form/Type/RegistrationFormType.php
<?php
 
namespace Acme\UserBundle\Form\Type;
 
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
 
class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // add your custom field
        $builder->add('name');
    }
 
    public function getParent()
    {
        return 'fos_user_registration';
    }
 
    public function getName()
    {
        return 'acme_user_registration';
    }
}

Publicas el formulario como un servicio:

# src/Acme/UserBundle/Resources/config/services.yml
services:
    acme_user.registration.form.type:
        class: Acme\UserBundle\Form\Type\RegistrationFormType
        tags:
            - { name: form.type, alias: acme_user_registration }

Por último le decimos a el bundle que para el registro el nuevo formulario es el que tiene que usar:

# app/config/config.yml
fos_user:
    # ...
    registration:
        form:
            type: acme_user_registration

Y hasta aquí cómo sobreescribir funcionalidades del FOSUserBundle. Espero que les ayude.

Mejorar el rendimiento de Symfony Cmf Dynamic Routing utilizando la opción de configuración uriFilterRegexp

He estado trabajando por un tiempo con Symfony Cmf (SfCmf) y he desarrollado algunos sitios con él. Además he creado algunos bundles extras para imple...


Mejorando la experiencia de desarrollar aplicaciones Symfony con Flex

Después de algún tiempo de espera tras anunciar Flex, ya se encuentra disponible y público lo que será el nuevo juguetito de los desarrolladores Symfo...


IMPRESIONES DEL SEGUNDO ENCUENTRO DE DESARROLLADORES HABANA.

Hace solo algunos días que conozco este grupo de entusiastas y emprendedores que se comunican mediante Google Group bajo el nombre de Desarrolladores...