Annotation et paramètres par défaut pour les routes sf2

Si l’on souhaite passer la page courante dans une route sf2, en utilisant les annotations, pour obtenir à une structure comme ceci  :

/news/ (page = 1)
/news/2 (page = 2)

On peut utiliser l’option defaults de @Route en affectant 2 routes à son contrôleur :

    /**
     *
     * @Route("/news",  name="news_list", defaults={"page" = 1})
     * @Route("/news/{page}", name="news_list_page", requirements={"page" = "\d+"})
     * @Template()
     */
    public function indexAction($page)
    {
       return array()
    }

Symfony2 et la gestion des rôles personnalisés

Par défaut, Symfony2 donne comme exemple les rôles ROLE_USER, ROLE_ADMIN et ROLE_SUPER_ADMIN dans le fichier app/config/security.yml.

Il est bien sûr possible d’en créer de nouveaux en pensant à bien les préfixer obligatoirement par ROLE_ sinon la gestion des droits d’accès échouera (cf le constructeur de RoleHierarchyVoter).

Utiliser un Validator dans un Form\Type Symfony2

Vous avez sûrement déjà dû utiliser des fonctions de validation appelées en callback sur vos formulaires (CallbackValidator) pour tester un retour de formulaire et lever une erreur sur un certain champs si la valeur de ce dernier n’est pas valide.

public function buildForm(FormBuilder $builder, array $options)
{
    $builder->add('myfield', 'text');
    $builder->addValidator(new CallbackValidator(function(FormInterface $form) {
    $myfield = $form->get('myfield');
    if ($myfield->getData() != null && !is_string($myfield->getData())) {
        $myfield->addError(new FormError('Invalid format'));
    }
}

Et bien sachez que vous pouvez utiliser dans vos Form/Type des Validator (à l’instar de ceux qu’on peut invoquer en utilisant les Assert dans nos entitées) qui vont pouvoir vous apporter un nombre important d’outils qui vont vous aider à valider vos formulaires.

Tout d’abord, il vous faudra utiliser les namespace de classes suivants :

<?php
namespace Acme\Bundle\AcmeBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraints\RegexValidator;

class FooType extends AbstractType
{
    [...]
}

Les deux derniers sont ceux que nous devons rajouter pour pouvoir utiliser cette contrainte de validation. Ici nous avons choisit la validation de Regex mais vous pouvez utiliser n’importe quel type de contrainte tant qu’elle possède une classe Validator associée. (cf. liste des toutes les contraintes)

Il vous suffira désormais de juste instancier de nouveaux objets dans votre CallbackValidator afin de pouvoir analyser et valider une valeur.

[...]
public function buildForm(FormBuilder $builder, array $options)
{
    $builder->add('myfield', 'text');
    $builder->addValidator(new CallbackValidator(function(FormInterface $form) {
    $myfield = $form->get('myfield');
    if ( ! is_null($myfield->getData()) ) {
        $validator      = new RegexValidator();
        $constraint     = new Regex(array(
            'pattern' => "/^[a-z0-9-]+$/"
        ));
        $isValid = $validator->validate( $myfield->getData(), $constraint );
        if ( ! $isValid ) {
            $myfield->addError( new FormError( "This field is not valid (only alphanumeric characters separated by hyphens)" ) );
        }
    }
}
[...]

Voilà une astuce bien sympathique qui peut vous être utile à valider un champs qui, par exemple, ne figure pas dans les attributs de votre entitée. Exactement comme vous pourriez le faire en utilisant les validations de type Assert.

Utiliser Uploadify avec les sessions Symfony2

Pour uploader des images avec Uploadify tout en conservant la même session Symfony2 dans un environnement sécurisé, il est necessaire de passer le paramètre de session pour récupérer la même session lors de l’appel Flash.

Passer son identifiant de session à Uploadify

Dans notre template Twig, nous allons passer notre identifiant de session (app.session.id) au script javascript d’Uploadify pour pouvoir le récupérer par la suite dans sf2.

{% extends "::base.html.twig" %}
{% block css %}
    {{ parent() }}
    <link href="{{ asset('bundles/novawaycms/js/uploadify/uploadify.css') }}" rel="stylesheet" type="text/css" />
{% endblock %}

{% block body %}
<form><input id="file_upload" type="file" name="file_upload" /></form>
{% endblock %}

{% block js %}
    {{ parent() }}
<script type="text/javascript" src="{{ asset('bundles/novawaycms/js/uploadify/swfobject.js') }}"></script>
<script type="text/javascript" src="{{ asset('bundles/novawaycms/js/uploadify/jquery.uploadify.v2.1.4.min.js') }}"></script>

<script type="text/javascript">

$(document).ready(function() {
    $('#file_upload').uploadify({
        'uploader'  : '{{ asset('bundles/novawaycms/js/uploadify/uploadify.swf') }}',
        'script'    : '{{ path("processUpload") }}',
        'scriptData': {'sid':'{{app.session.id}}'},
        'cancelImg' : '{{ asset('bundles/novawaycms/js/uploadify/cancel.png') }}',
        'auto'      : true,
        'buttonText'  : 'Parcourir ...',
        'multi'          : true,
        'fileExt'     : '*.jpg;*.gif;*.png',
        'fileDesc'    : 'Fichiers Images Web (.JPG, .GIF, .PNG)',
        'onComplete'  : function(event, ID, fileObj, response, data) {

        }
    });
});
{% endblock %}

On ajoute le paramètre sid qui contient notre identifiant de session Symfony2 à la requête qui sera éxécutée par la librairie.

Récupérer l’identifiant de session dans Symfony2

Puis, on crée notre listener de session UploadifySessionListener que nous ajouterons dans un dossier EventListener de notre bundle.

<?php

/*
 * Override Session listener for uploadify
 */

namespace Novaway\Bundle\CmsBundle\EventListener;

use Symfony\Component\DependencyInjection\ContainerInterface;

use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

/**
 * Sets the session on the request.
 *
 * This will also start the session if it was already started during a previous
 * request.
 *
 */
class UploadifySessionListener
{
    private $container;
    private $autoStart;

    public function __construct(ContainerInterface $container, $autoStart = false)
    {
        $this->container = $container;
        $this->autoStart = $autoStart;
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
            return;
        }

        if (!$this->container->has('session')) {
            return;
        }

        $request = $event->getRequest();
        if ($request->hasSession()) {
            return;
        }

        $request->setSession($session = $this->container->get('session'));

        if ($request->getMethod() == 'POST' &&
            $this->container->get('request')->request->has('Filename') &&
            $this->container->get('request')->request->has('sid')) {

            session_id($this->container->get('request')->request->get('sid'));

            //fix problème de récuperation du token
            $this->container->get('request')->cookies->set(session_name(), $session->getId());
        }

        if ($this->autoStart || $request->hasPreviousSession()) {
            $session->start();
        }
    }
}

Il ne reste plus qu’à dire à notre application d’utiliser notre listener de session à la place de celui par défaut.

Pour cela, nous allons surcharger celui-ci grâce à l’injection de dépendance dans notre fichier de configuration (app/config/config.yml) :

    parameters:
        session_listener.class: Novaway\Bundle\CmsBundle\EventListener\UploadifySessionListener

Uploadify est désormais utilisable en mode connecté.

Note: pour illustrer ce billet, nous passons directement l’id de session brute. Dans un environnement de production, il serait préférable de le crypter/décrypter pour éviter des injections de session.