Doctrine2: Désactiver temporairement les événements d’un listener

Pré-requis: Symfony2 et Doctrine2

Dans ce bref article, je vais vous montrer la possibilité de désactiver temporairement un ou des événement(s) d’un listener. Nous allons imaginer un scénario ou lorsque l’on modifie une entité, notre listener envoit un email à l’administrateur du site.

Voici un exemple de listener pour illustrer notre cas:


<?php

namespace MyProject\FooBundle\Listener;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;

class FooListener implements EventSubscriber
{
    public function getSubscribedEvents()
    {
        return array(
            Events::prePersist,
            Events::preUpdate,
            Events::postPersist,
            Events::postUpdate
        );
    }

    ...
}

Le listener ci-dessus est défini comme service avec le nom « foo.listener ». Il doit être taggé avec l’attribut « doctrine.event_subscriber » pour que cela fonctionne car nous utilisons les événements de Doctrine2.

Nous allons maintenant mettre en place le code permettant de désactiver les événements du listener lors d’un import de données de masse. Si vous consulter l’API de l’eventManager, vous constaterez qu’il existe la fonction removeEventListener. Elle reçoit comme paramètres un tableau d’événement(s) ainsi que la référence de notre listener.

<?php

...
$em = $this->getContainer()->get('doctrine')->getManager();
$eventManager = $em->getEventManager();

$eventManager->removeEventListener(
    array('prePersist', 'preUpdate', 'postPersist', 'postUpdate'),
    $this->getContainer()->get('foo.listener')
);

Avec le code ci-dessus, tous les événements mentionnés (‘prePersist’, ‘preUpdate’, ‘postPersist’, ‘postUpdate’) seront désactivés.

Voilà. J’espère que cette petite astuce vous servira dans vos prochains développements.

Share

Réaliser un système de Timeout pour Symfony2

[MAJ] Une petite précision sur ce que fait réellement cette fonctionnalité. Cela me permet de déconnecter un utilisateur de son interface si l’activité est interrompu pendant plus de 30 minutes. C’est un reset de session automatique.

Symfony ne fournissant pas cette fonction dans son framework, je vais vous montrer sa mise en place avec un listener. J’ai pour habitude d’avoir dans mes développements, un bundle Core qui me permet de centraliser les choses du mon projet.

Nous allons commencer par définir un paramètre dans notre arbre de configuration. Cela nous permettra de le renseigner ensuite dans notre fichier config.yml se trouvant dans le dossier app/config. Voici le code se trouvant dans le fichier CoreBundle/DependencyInjection/configuration.php

namespace Funstaff\CoreBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('funstaff_core');

        $rootNode
            ->children()
                ->scalarNode('timeout')->defaultValue(3600)
                ->isRequired()->end()
            ->end();

        return $treeBuilder;
    }
}

Nous pouvons maintenant injecter notre paramètre dans le container. Pour cela, nous allons ajouter ce code dans le fichier FunstaffCoreExtension.php

namespace Funstaff\CoreBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

class FunstaffCoreExtension extends Extension
{
    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
    	$configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $loader = new Loader\XmlFileLoader($container,
                         new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.xml');

        $container->setParameter('core.timeout', $config['timeout']);
    }
}

Dernière chose à faire avant d’implémenter notre listener, renseigner ce paramètre dans notre fichier config.yml. La valeur est exprimée en seconde.

funstaff_core:
    timeout:    1800 # 30 minutes

Pour respecter l’arborescence des dossiers, j’ai créé mon fichier RequestListener.php dans le path suivant: FunstaffCoreBundle/Request/Listener.

namespace Funstaff\CoreBundle\Request\Listener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class RequestListener implements EventSubscriberInterface
{
    protected $session;

    protected $securityContext;

    protected $timeout;

    /**
     * Construct
     * 
     * @param Session $session
     */
    public function __construct(Session $session,
                                SecurityContext $securityContext,
                                $timeout)
    {
        $this->session = $session;
        $this->securityContext = $securityContext;
        $this->timeout = $timeout;
    }

    /**
     * Get Subscribed Events
     * 
     * @return array event list
     */
    public static function getSubscribedEvents()
    {
        return array(
            'kernel.request' => 'onKernelRequest',
        );
    }

    /**
     * On Kernel Request
     */
    public function onKernelRequest(GetResponseEvent $event)
    {

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

        $meta = $this->session->getMetadataBag();
        $lastused = $meta->getLastUsed();

        if (null !== $lastused && (time() - $lastused) > $this->timeout) {
            $this->securityContext->setToken(null);
            $this->session->invalidate();
        }
    }
}

Il nous reste une dernière chose à faire pour que cela fonctionne. Nous allons attacher notre listener à la request en définissant les éléments dans le fichier Resources/config/services.xml comme ceci:

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <parameters>
        <parameter key="request.listener.class">Funstaff\CoreBundle\Request\Listener\RequestListener</parameter>
    </parameters>

    <services>
        <service id="timeout.request.listener" scope="request">
            <tag name="kernel.event_subscriber"/>
            <argument type="service" id="session" />
            <argument type="service" id="security.context" />
            <argument>%core.timeout%</argument>
        </service>
    </services>
</container>

Je précise pour finir, que je travaille toujours en mode sécurité: anonymous = true. Il vous faudra peut-être modifier un peu le listener ci-dessus dans le cas contraire.

Voilà. Nous en avons terminé avec notre système de timeout. J’espère que cela vous servira dans vos prochains développement.

Share

Doctrine 2.2, SoftDeleteable behavior et Symfony 2

Depuis la sortie de la version 2 de Doctrine, il manquait cruellement l’extension SoftDelete qui vous permet de gérer la suppression de vos enregistrements sans réellement les détruire. Depuis peu, Gediminas Morkevicius alias l3pp4rd a implémenter cela dans ses extensions dédiées à Doctrine 2. Ci-dessous, je vais vous montrer comment changer la version de doctrine dans Symfony2 et sa configuration.

Pour cela, nous allons modifier le fichier deps à la racine de votre projet. Nous allons changer les versions de doctrine pour passer à la version 2.2 et ajouter les extensions:

[doctrine-common]
    git=http://github.com/doctrine/common.git
    version=2.2.1

[doctrine-dbal]
    git=http://github.com/doctrine/dbal.git
    version=2.2.1

[doctrine]
    git=http://github.com/doctrine/doctrine2.git
    version=2.2.1

[gedmo-doctrine-extensions]
    git=http://github.com/l3pp4rd/DoctrineExtensions.git

Passons maintenant à la définition du namespace pour ces extensions. Modifions le fichier autoload.php se trouvant dans le dossier app

registerNamespaces(array(
    ...
    'Gedmo' => __DIR__.'/../vendor/gedmo-doctrine-extensions/lib',
));
...

Nous pouvons maintenant déclarer le service dont nous avons besoin. Ouvrons le fichier services.xml dans le dossier « Resources/config »:

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <parameters>
        ...
        <parameter key="gedmo.softdeleteable.listener.class">Gedmo\SoftDeleteable\SoftDeleteableListener</parameter>
    </parameters>
    <services>
        ...
        <service id="gedmo.listener.softdeleteable" class="%gedmo.softdeleteable.listener.class%">
            <tag name="doctrine.event_subscriber" />
            <call method="setAnnotationReader">
                <argument type="service" id="annotation_reader" />
            </call>
        </service>
    </services>
</container>

Chargeons maintenant le filtre qui va modifier vos requêtes lors de l’utilisation de softDelete. Pour cela, nous allons ajouter quelques lignes au boot de notre Bundle. Dans mon cas, j’ai toujours un bundle Core dans mes projets, voici le code à insérer:

<?php

namespace Funstaff\CoreBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class FunstaffCoreBundle extends Bundle
{
    public function boot()
    {
        $doctrine = $this->container->get('doctrine');
        $doctrine->getEntityManager()->getConfiguration()->addFilter(
            'soft-deleteable',
            'Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter'
            );

        $em = $doctrine->getEntityManager();
        $em->getFilters()->enable('soft-deleteable');
    }
}

La nouvelle extension est dès maintenant activée.

Pour l’utiliser, nous allons ajouter une annotation sur notre entité comme indiqué ci-dessous:

<?php 

namespace Funstaff\UserBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as GEDMO;

/**
 * @ORM\Entity
 * @ORM\Table(name="user")
 * @GEDMO\SoftDeleteable(fieldName="deletedAt")
 */
class User
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    ...
}

J’espère que cette petite explication vous servira pour vos prochains développements.

Pour en savoir plus sur l’utilisation de celle-ci, veuillez consulter la documentation officielle qui se trouve ici.

Bon tests 😉

Share

FunstaffTikaBundle: Wrapper pour Tika

Aujourd’hui, je vous propose un petit bundle Symfony2 de ma création. Celui-ci permet d’extraire du contenu et des metadatas sur vos fichiers. Vous avez la liste des fichiers supportés à cette adresse.

Installation

Clone:

git clone https://github.com/Funstaff/FunstaffTikaBundle vendor/bundles/Funstaff/TikaBundle

Ajout en submodule:

git submodule add https://github.com/Funstaff/FunstaffTikaBundle vendor/bundles/Funstaff/TikaBundle

Télécharger le binaire Tika (runnable jar) à cette adresse et déplacer le dans le path de votre choix que vous renseignerez dans votre configuration.

Nous allons ajouter le namespace « Funstaff » dans le fichier autoload.php

$loader->registerNamespaces(array(
    ...
    'Funstaff' => __DIR__.'/../vendor/bundles',
));

Activons maintenant le bundle:

public function registerBundles()
{
    $bundles = array(
        ...
        new Funstaff\TikaBundle\FunstaffTikaBundle(),
    );
}

Sa configuration est très simple. Il vous suffit de déclarer ces éléments dans votre fichier config.yml:

funstaff_tika:
    tika_path:      /path/to/tika-app-1.0.jar
    output_format:  ~
    logging:        ~

Options possibles pour ces paramètres:
tika_path: Chemin sur le binaire Tika
output_format: xml, html ou text (défaut: xml)
jogging: true ou false (si non défini, utilise le paramètres jogging du Symfony2)

Utilisation

Dès maintenant, vous avez accès au service « funstaff.tika ». Voici comment l’utiliser.

$tika = $this->get('funstaff.tika')
        ->setOutputFormat('text')
        ->addDocument('foo', '/path/to/foo')
        ->extractContent();

Dans l’exemple ci-dessus, nous avons fixé le format de sortie au format texte, ajouté le document foo et lancer l’extraction. Nous allons maintenant pouvoir récupérer les informations:

foreach ($tika->getDocuments() as $document) {
    $content = $document->getContent();
}

Vous pouvez ajouter plusieurs documents en rajoutant plusieurs lignes « addDocument ».

Fonctions existantes pour l’extraction:
extractContent: Uniquement le texte
extractMetadata: Uniquement les metadata
extractAll: Texte et métadata

Exemple avec la récupération du texte et des metadatas:

foreach ($tika->getDocuments() as $document) {
    $content = $document->getContent();
    $metadata = $document->getMetadata();
    $author = $metadata->get('Author');
}

Voilà, nous avons effectué le tour du propriétaire. Si ce bundle vous intéresse, vous le trouverez sur github à l’adresse suivante: FunstaffTikaBundle

Si vous désirez me laisser votre feedback: Github issue

Share

Symfony2, Assetic, less et yui compressor: Installation sur Mac

Après un petit moment de silence, voici une petite publication concernant l’installation de LESS pour pouvoir l’utiliser avec Symfony2 et Assetic. Pour cela, nous allons utiliser MacPorts. Vous devez posséder les droits administrateur pour le faire.

L’utilisation de LESS demande une installation de Node.js ainsi que Node Package Manager.

sudo port install nodejs
sudo port install npm

Nous allons contrôler que nos 2 éléments ci-dessous soient bien installés.

$ node --version
v0.4.11
$ npm --version
1.0.26

Avec les deux commandes ci-dessous, vous avez maintenant la base. Il nous reste à installer LESS:

sudo npm install -g less

Vous avez la possibilité de voir les packages installés avec la commande suivante:

npm list -g

Il nous reste à installer YUI-Compressor. Vous pouvez télécharger la dernière version chez yahoo. Copier le fichier « yuicompressor-2.4.6.jar » du dossier build dans le dossier app/Resources/java.

Passons maintenant à la configuration d’assetic. Nous allons ajouter quelques lignes dans le fichier se trouvant dans app/config/config.yml:

# Assetic Configuration
assetic:
    debug:          %kernel.debug%
    use_controller: false
    filters:
        cssrewrite: ~
        less:
            node:       /opt/local/bin/node
            node_paths: [/opt/local/lib/node, /opt/local/lib/node_modules]
        yui_css:
            jar: %kernel.root_dir%/Resources/java/yuicompressor-2.4.6.jar
        yui_js:
            jar: %kernel.root_dir%/Resources/java/yuicompressor-2.4.6.jar

Il nous reste à ajouter quelques lignes dans notre layout de base dans la partie head:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        {% stylesheets
            '@FunstaffCoreBundle/Resources/assets/less/foo.less'
             filter='less,?yui_css'
             output='css/foot.css'
             %}
            <link href="{{ asset_url }}" rel="stylesheet" media="screen" />
        {% endstylesheets %}
        ...
    </head>
    <body>
        ...
    </body>
</html>

Vous nouvelle configuration est prête à être utilisé.

Encore une dernière chose, si vous voulez générer votre css pour votre site en production, il suffit d’exécuter cette commande:

./app/console assetic:dump --no-debug

Voici les sources qui m’ont aidé à écrire cet article:

Share

Symfony2: Génération d’un nouveau projet version 2

Bonjour,

Avec un peu de retard, suite à des problèmes de santé, je vous présente brièvement la version 2 du bootstrapper qui vous permet de générer facilement un projet Symfony2. Vous pouvez décharger cette nouvelle version à l’adresse suivante: Symfony2Project. Ce nouveau générateur est basé sur les components de Symfony2.

La première chose à faire est de copier le fichier default.xml.dist et le renommer default.xml. Ce fichier xml renseigne les éléments nécessaire pour le bon fonctionnement du générateur. Vous le trouvez dans le répertoire Resources/Profile.

Pour connaître toutes ces options, vous pouvez taper dans votre terminal:

./symfony2project generate:project

Vous devez au minimum renseigner les éléments suivants:
– Nom du bundle (AppName)
– Le namespace que vous avez choisi (VendorName)
– Et le répertoire ou vous allez générer votre projet (Path)

Ensuite, selon les éléments que vous voulez mettre en place, vous avez des options que voici:
– controller: Permet de renommer le controleur générer lors de l’initialisation du projet
– protocol: Protocole utilisé par git (git, http)
– session-start: Démarrage automatique de la session
– session-name: Nom de la session
– orm: Type d’orm (doctrine ou propel)
– odm: Type d’odm (mongodb)
– assetic: Activation du bundle Assetic
– swiftmailer: Activation du bundle swiftmailer
– doctrine-migration: Activation du bundle de migration
– doctrine-fixtures: Activation du bundle de fixtures
– template-engine: Genérateur de modèle (php ou twig). Le défaut est fixé sur twig
– profile: Nom du fichier profile (dans le cas ou vous avez réaliser un nouveau fichier xml). Le défaut est fixé sur default
– assets-symlink: création du lien symbolique sur les ressources des bundles
– force-delete: Force la suppression d’un ancien projet résidant dans le path

Profile:
Le fichier profile peut-être personnalisé selon vos désirs. Il existe une section User qui vous permet d’ajouter vos éléments. Vous pouvez également déposer ce fichier sur un serveur http et renseigner la directive –profile. Exemple:

./symfony2project generate:project AppName VendorName Path
--profile=http://myserver/profile/default

Attention: Ne pas spécifier l’extension (.xml)

Voici une ligne de commande avec toutes les options:

./symfony2project generate:project Core Funstaff /path/to/your/project
--controller=Main --session-start --session-name=funstaff --orm=doctrine
--assetic --swiftmailer --doctrine-migration --doctrine-fixtures
--assets-symlink --force-delete

J’espère que ce nouvel outil vous facilitera la vie.

Bonne utilisation 😉

Share

Symfony2: Génération d’un nouveau projet

ATTENTION: Nouvelle version du script. Un nouvel article en préparation.

Ce soir, juste une petite publication, pour vous annoncer la publication d’un script maison. Symfony2 ne proposant pas une tâche pour générer un nouveau projet, j’ai décidé de réaliser un script de mise en place des éléments. Vous le trouvez sur github en cliquant sur le lien ci-dessous

Symfony2Project

Voici la syntaxe pour son utilisation:

php symfony2project.php –app=AppName –vendor=VendorName [–path=/your/destination/path] [–controller=controllerName] [–protocol=git|http] [–session-start=false|true] [–session-name=sessionName] [–symfony-repository=fabpot|symfony] [–with-db=false|true] [–template-engine=twig|php]

–app: Le nom de votre application (en faite le Bundle principal)
–vendor: Nom du « vendor » (obligatoire)
–path: Destination (Ex: /www/virtualhosts/foo)
–controller: Si vous l’indiquez le script génèrera un controller et un template
–protocol: git ou http (selon le cas d’utilisation)
–session-start: false ou true (démarrage automatique de la session) (défaut: false)
–session-name: Nom de la session (défaut: Nom de l’application)
–symfony-repository: fabpot ou symfony (défaut: symfony)
–with-db: false ou true (défaut: true)
–template-engine: twig ou php (default: twig)

J’ai encore quelques améliorations dans ma ToDo list. Si vous essayez ce script et qu’il vous convient, merci de m’encourager par un petit commentaire 🙂 Vous pouvez également y participer en soumettant un « Pull Request« .

Maintenant, je vais me remettre à la découverte de Symfony2 qui est fondamentalement différent de la version 1.

[MAJ]
27.11.2010: Ajout de l’option auto_start sur la session (réf)
28.11.2010: Ajout de l’option symfony-repository permettant de choisir entre 2 dépôt (fabpot ou symfony)
01.12.2010: Ajout des options session-name et with-db
23.01.2011: Ajout de l’option template-engine
31.01.2011: Ajout de l’option vendor

Share