Symfony2: création d'un service

06.09.2011  • Samuel Breton

Bonjour,

Nous allons voir dans cet article la mise en place d’un callback sur une entité dans symfony2.

Contexte

Nous allons ici prendre l’exemple d’un site de commande. Chaque commande a un statut et nous souhaitons enregistrer dans la base de données chaque changement de statut afin de conserver un historique.

Pour cela, Symfony2 et Doctrine fournissent un ensemble d’actions pouvant être appelées à chaque étapes du cycle de vie d’une entité (“lifecycle”).

Vous trouverez à cette adresse l’ensemble des événements disponibles: Lifecycle Events

Dans notre cas, nous avons choisi d’effectuer la sauvegarde du statut après chaque mise à jour de notre entité Order (qui gère les commandes du site). Pour cela nous allons utiliser l’événement postUpdate qui sera appelé après chaque update de l’entité.

Voici, les deux objets concernés par ce service:

L’objet commande

/**
 * Projet\OrderBundle\Entity\Order
 *
 * @ORM\Entity(repositoryClass="Projet\OrderBundle\Repository\OrderRepository")
 * @ORM\Table(name="Projet_order_order")
 * @ORM\HasLifecycleCallbacks
 */
class Order
{
     /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var Yatoo\UserBundle\Entity\User $user
     *
     * @ORM\ManyToOne(targetEntity="Yatoo\UserBundle\Entity\User", inversedBy="orders")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     */
    protected $user;

    /**
     * @var string $comment
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    protected $title;

    /**
     * @var decimal $price
     *
     * @ORM\Column(name="price", type="decimal", length="7", scale="2")
     */
    protected $price;

    /**
     * @var integer $quantity
     *
     * @ORM\Column(name="quantity", type="integer")
     */
    protected $quantity;

    /**
     * @var Yatoo\CardBundle\Entity\Pricing $pricing
     */
    public $pricing;
 
     /**
     * @var smallint $status
     *
     * @ORM\Column(name="status", type="smallint")
     */
    protected $status;

    /**
     * @var text $comment
     *
     * @ORM\Column(name="comment", type="text", nullable="true")
     */
    protected $comment;

    /**
     * @ORM\OneToMany(targetEntity="HistoricalStatus", mappedBy="order", cascade={"all"})
     * @ORM\JoinColumn(name="order_id", referencedColumnName="id")
     */
    protected $historicalStatus;

    /**
     * @Gedmo\Timestampable(on="create")
     * @ORM\Column(name="created_at", type="datetime")
     */
    protected $createdAt;

    protected $statusOld;
    
    ...

}

Et HistoricalStatus qui servira à stocker l’historique des changements de statut des commandes.

/**
 * Projet\OrderBundle\Entity\HistoricalStatus
 *
 * @ORM\Entity(repositoryClass="Projet\OrderBundle\Repository\HistoricalStatusRepository")
 * @ORM\Table(name="Projet_order_historical_status")
 * @ORM\HasLifecycleCallbacks
 */
class HistoricalStatus
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var object $order
     *
     * @ORM\ManyToOne(targetEntity="Order", inversedBy="files")
     * @ORM\JoinColumn(name="order_id", referencedColumnName="id")
     */
    protected $order;


    /**
     * @var smallint $status
     *
     * @ORM\Column(name="status", type="smallint")
     */
    protected $status;

    /**
     * @var text $comment
     *
     * @ORM\Column(name="comment", type="text", nullable="true")
     */
    protected $comment;

    /**
     * @Gedmo\Timestampable(on="create")
     * @ORM\Column(name="created_at", type="datetime")
     */
    protected $createdAt;

    ...
}

Mise en place

Dans un premier temps, il faut créer le dossier Listener dans votre bundle.Dans notre exemple ce dossier se trouvera à l’adresse : repSite/src/Projet/OrderBundle/Listener puis créer le fichier OrderListener.php.

Voici pour exemple le code de notre service:

namespace Projet\OrderBundle\Listener;

use Projet\OrderBundle\Entity\HistoricalStatus;
use Projet\OrderBundle\Entity\Order;

use Doctrine\ORM\Event\LifecycleEventArgs;

class OrderListener
{
    public function postUpdate(LifecycleEventArgs $args) {

        $entity = $args->getEntity();
        $entityManager = $args->getEntityManager();

        if ($entity instanceof Order) {

            if($entity->getOldStatus() != $entity->getStatus()) {
                $historicalStatus = new HistoricalStatus();
                $historicalStatus->setOrder($entity);
                $historicalStatus->setComment($entity->getComment());
                $historicalStatus->setStatus($entity->getStatus());
                $entityManager->persist($historicalStatus);
                $entityManager->flush();
            }
        }
      }
}

Pour expliquer rapidement ce que fait ce bout de code :

Si le statut de la commande a changé, on crée un nouveau statut historique et on l’enregistre.

La fonction $entity->getOldStatus() permet de récupérer l’ancien statut de la commande avant sa mise à jour. La valeur est initialisée à la création de l’objet dans le constructeur.

Configuration

Une fois votre listener écrit, il faut le configurer pour qu’il soit appelé. Pour cela, éditer simplement le fichier : Projet\OrderBundle\Ressources\config\services.xml et ajoutez y ces lignes:


            

Vous pouvez aussi le déclarer en yml :

services:
    order.postUpdate :
        class: Projet\OrderBundle\Listener\OrderListener
        tags:
            - { name: doctrine.event_listener, event: postUpdate }
Directeur conseil chez Spiriit
J'accompagne nos clients sur la mise en place de la stratégie, de l'architecture et dans la structuration du projet. J'interviens en amont des projets pour la planification et en aval sur la partie KPI / Performance.
Voir l’étude de cas
Lire l’article
Lire l’actualité
En savoir plus
En savoir plus
Voir le témoignage
Fermer