Loguer des messages dans un fichier spécifique avec Symfony2 et Monolog

Les fichiers de logs de Symfony sont très utiles pour comprendre ce qu’il se passe dans votre application et la débuguer. Je m’en sers constamment, la configuration de base répondant à mes besoins, jusqu’ici.

Travaillant sur un service de synchronisation très sensible il est intéressant d’y loguer ce qu’il s’y passe (quand la synchronisation commence, qui la déclenche, quels sont les éléments synchronisés, etc.). dans un fichier dédié, pour plus de clarté.

Pour loguer dans un fichier autre que ceux par défaut (dev.log, prod.log et test.log) il existe une documentation qui pour moi n’est pas des plus détaillée. Pourtant, une fois le mécanisme compris l’écriture de logs Symfony dans d’autres fichiers est des plus simple.

Partons du service de synchronisation suivant :

<?php 
class SynchronizerService
{
    /** @var LoggerInterface $logger */
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @param SynchronizationItem[] $synchronizationItems
     */
    public function sync($synchronizationItems)
    {
        $this->logger->notice('Synchronization started.');
    }
}

L’idée d’un service étant qu’il soit réutilisable partout, les dépendances sont injectées dans le constructeur et celles-ci sont le plus génériques possible. Ce n’est pas parce que le logger par défaut de Symfony2 est Monolog que notre service doit en dépendre.

Mon premier point de blocage à ce stade fut le suivant : comment faire à partir de LoggerInterface pour spécifier le fichier dans lequel écrire ? Réponse : on ne fait pas. Tout se passe dans la configuration du service et de Monolog.

La définition du SynchronizerService est classique, à l’exception de la présence d’un tag :

# services.yml
foo.sync.synchronizer:
  class: Foobar\SyncBundle\Service\SynchronizerService
  arguments:
    - "@logger"
  tags:
    - { name: monolog.logger, channel: sync }

Les tags portent bien leur nom puisqu’ils vous permettent de taguer vos services au même titre que des articles de blogs. On peut parler de catégorisation également. Pour plus d’information sur les tags, une page de documentation dédiée est disponible.

Maintenant que l’injection du logger est faite et que le service est tagué la dernière étape est de configurer Monolog afin d’utiliser un nouvel handler :

# config.yml    
monolog:
  handlers:
    sync:
      type: stream
      path: "%kernel.logs_dir%/sync_%kernel.environment%.log"
      level: debug
      channels: ['sync']   

La propriété qui nous intéresse ici est « channels ». Elle indique au handler quels channels écouter, ou plus simplement quels tags écouter.

La configuration est finie, la prochaine fois que vous exécuterez le service un nouveau fichier de log commencant par « sync_ » sera créé, en fonction de l’environnement dans lequel il est exécuté.

Symfony2 et Twitter Bootstrap : insérer un input checkbox ou radio dans un label

Ayant récemment utilisé Twitter Bootstrap pour l’interface d’administration d’un projet en Symfony2, je me suis heurté à un problème basique à propos de la gestion des formulaires.

Pour aligner verticalement un input de type radio ou checkbox avec son label il faut inclure le premier dans le second :

<label class="checkbox">
    <input type="checkbox"> Check me out
</label>

Ce code peut paraître simple, mais produire ce même HTML avec Symfony2 l’est moins. En effet, pour afficher les formulaires, Symfony2 utilise des blocs de templates Twig. Chaque bloc correspond à un type de champs, à l’affichage des labels ou plus globalement à l’affichage du formulaire. On peut retrouver leur définition dans le fichier form_div_layout.html.twig.

Première méthode : redéfinir le bloc dans la template

La façon la plus simple d’écraser la définition du bloc est de le faire à l’intérieur de la template du formulaire concernée :

{% form_theme form _self %}

{% block checkbox_widget %}
    {% spaceless %}
        <label class="checkbox">
            <input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
            {{ form_label(form) }}
        </label>
    {% endspaceless %}
{% endblock %}

La première ligne indique à Twig de chercher à l’intérieur de la template si des fragments de blocs ne sont pas disponibles. Pour le reste du code, nul besoin d’explication. Il suffit de copier-coller le bloc défini dans form_div_layout.html.twig puis de réordonner le HTML.

Seconde méthode : redéfinir le bloc de façon globale

Si vous avez besoin d’appliquer ce fragment de template sur tous les formulaires de votre projet, il est plus simple de le définir à un seul endroit plutôt que de le copier-coller dans chaque template. Symfony permet de faire cela très rapidement et simplement.

Créer un fichier Resources/views/Form/fields.html.twig dans le répertoire de votre bundle avec le code ci-dessous :

{% block checkbox_widget %}
    {% spaceless %}
        <label class="checkbox">
            <input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
            {{ form_label(form) }}
        </label>
    {% endspaceless %}
{% endblock %}

Pour rendre Twig au courant de la présence de ce fichier il existe là encore deux méthodes.
La première suit l’exemple précédent, à savoir indiquer à Twig dans la template la location du fichier concerné :

{% form_theme form 'AcmeDemoBundle:Form:fields.html.twig' %}

La seconde, qui permet de ne plus avoir à modifier chaque template, consiste à indiquer à Twig l’emplacement du fichier concerné, mais cette fois dans le fichier de configuration de Symfony : app/config/config.yml.

twig:
    form:
        resources:
            - 'AcmeDemoBundle:Form:fields.html.twig'

Conclusion

Tout au long de cet article nous nous sommes intéressés aux checkbox. Le fonctionnement est identique pour les radios en adaptant les bons blocs.

Il n’est finalement pas très compliqué de modifier les templates de formulaires Symfony2. Cela requiert un minimum de connaissances quant à l’héritage des blocs Twig mais en quelques dizaines de minutes maximum il est possible de personnaliser un projet complet.