Ajouter un commentaire

Application CoffeeBar 5/19 - Provoquer l'ouverture de commande

Maintenant que nous avons notre formulaire - qui ouvre une note - nous allons lui attacher un objet. Avec cet objet, nous allons pouvoir traiter le formulaire. Nous verrons alors comment avec cet objet, nous allons pouvoir déclencher notre tout premier événement, le point de départ de toute notre application.

NOTE : le long du tutoriel, je vais vous faire rééditer des classes et des vues sur lesquels on sera déjà passé. Il va y avoir des parties qui auront disparu au second passage : typiquement les use, les instructions extends, implements etc... Cela ne veut pas dire qu'il faut les retirer. C'est juste pour alléger le code. S'il faut retirer du code, soit je vous remettrais le passage intégralement, pour voir ce qui a disparu, soit je vous signalerais qu'il faut effacer une ligne pour la remplacer par une autre.

L'événement TabOpened

Tous les scénarii commencent au moment où une note est ouverte (CoffeeBar\Event\TabOpened). On identifie une note avec son id unique, le numéro de la table qui a ouvert la note et le nom du serveur qui a ouvert la note.

// module/CoffeeBar/src/CoffeeBar/Event/TabOpened.php
namespace CoffeeBar\Event ;
class TabOpened
{
    protected $id ; // string (guid)
    protected $tableNumber ; // string (numéro de la table)
    protected $waiter ; // string (serveur)

    // getters & setters
}

 

L'opération OpenTab correspondante

Pour qu'une note soit ouverte, il nous faut également l'objet qui fait l'action d'ouvrir cette note.

Le tutoriel d'origine parle de commande (le fait de faire quelquechose), mais en français, il y a trop de sens au mot commande (surtout dans le cadre d'un commerce), je parlerais donc d'une opération (le mot action ayant également un sens dans une architecture MVC). L'opération correspondante qui ouvre une note sera donc CoffeeBar\Command\OpenTab.

// module/CoffeeBar/src/CoffeeBar/Command/OpenTab.php
namespace CoffeeBar\Command ;
class OpenTab
{
    protected $id ; // string (guid)
    protected $tableNumber ; // string (numéro table)
    protected $waiter ; // string (serveur)
    // l’objet ‘OpenTab’ déclenche une opération (événement) ‘openTab’

    // getters & setters
}

 

Le gestionnaire d'événements

Dans le tutoriel d'origine, les opérations sont gérées par un gestionnaire central. Quand une opération a lieu, le gestionnaire prend l'opération en charge et déclenche dans ce cas là les événements qui vont bien. Avec Zend Framework 2, cela ne va pas se passer pareil. Il n'y a rien pour 'prendre en charge une opération' d'une part et pour 'écouter un événement' d'autre part. Que ce soit une opération qui a lieu ou un événement qui est déclenché, il faut intercepter ce qu'il se passe : au final, que ce soit une opération ou un événement, c'est déclencher un événement, que l'événement soit nommé comme l'opération ou comme l'événement...

Tout le long du tutoriel, nous avons des événements (gérés par le gestionnaire d'événements) et des événements (les objets de l'espace de nom Coffee\Event\*). Un objet opération (Coffee\Command\*) va déclencher un événement (géré par le gestionnaire d'événements) mais un objet événement (Coffee\Event\*), va AUSSI déclencher un événement. Nous allons avoir une opération CoffeeBar\Command\OpenTab qui va déclencher un événement 'openTab' et une fois que l'événement 'openTab' va être intercepté, on va générer un objet événement CoffeeBar\Event\TabOpened qui va déclencher un événement 'tabOpened'. Notez la casse que j'utilise, je vais faire de mon mieux pour être clair sur ce point...

Dès qu'on parle d'événements, on va devoir mettre en place un gestionnaire d'événement. Par défaut, l'application de Zend Framework 2 (raccourci honteux : je parle bien entendu du Application Skeletton qui se base sur le framework de Zend Framework 2. Zend Framework 2 n'est pas une application à lui seul) comprend un gestionnaire d'événements par défaut, anonyme. Pour notre usage, nous allons donc créer un gestionnaire d'événement personnalisé, basé sur le gestionnaire de Zend.

// module/CoffeeBar/src/CoffeeBar/Service/TabEventManager.php
<?php
namespace CoffeeBar\Service ;
use Zend\EventManager\EventManager;

class TabEventManager extends EventManager
{
}


Voila. Tout simplement. Franchement, à part lui donner un nom, on n'a rien fait d'autre.

Par contre, on va l'ajouter dans notre gestionnaire de services.

// module/CoffeeBar/Module.php
<?php
namespace CoffeeBar;
class Module implements FormElementProviderInterface
{
...
    public function getServiceConfig()
    {
        return array(
            'invokables' => array(
                'TabEventManager' => 'CoffeeBar\Service\TabEventManager',
            ),
        ) ;
    }
}

 

Le gestionnaire d'événements a une méthode EventManager::trigger('nom_de_l_evenement', 'cible', 'paramètres'). Lorsque EventManager::trigger() est appelé, un événement 'nom_de_l_evenement' est déclenché dans le gestionnaire d'événements.

Le gestionnaire d'événements a une méthode EventManager::attach('nom_de_l_evenement', 'callback'). Lorsqu'un événement 'nom_de_l_evenement' est déclenché, la méthode 'callback' s'exécute.

Dans tous les cas, il est nécessaire que le gestionnaire d'événements soit présent, ou pour déclencher l'événement, ou pour y répondre.

Déclencher un événement

Voyons maintenant comment notre objet CoffeeBar\Command\OpenTab peut déclencher un événement 'openTab' dans le gestionnaire d'événements.

On vient de voir qu'il était nécessaire, pour l'objet CoffeeBar\Command\OpenTab d'intégrer le gestionnaire d'événements pour pouvoir déclencher un événement 'openTab'.

// module/CoffeeBar/src/CoffeeBar/Command/OpenTab.php
namespace CoffeeBar\Command ;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
// OpenTab déclenche un événement. Il faut donc que l’objet puisse avoir
// accès à un Event Manager pour y déclencher l’événement
class OpenTab implements EventManagerAwareInterface
{
    // propriété liées à l’interface EventManagerAwareInterface
    protected $events ;

    // méthode définie par l’interface EventManagerAwareInterface
    // on va injecter le gestionnaire d'événement dans la méthode setEventManager
    public function setEventManager(EventManagerInterface $events)
    {
        $this->events = $events;
        return $this;
    }
     
    // méthode définie par l’interface EventManagerAwareInterface
    public function getEventManager()
    {
        return $this->events;
    }
}

 

Une fois que l'objet CoffeeBar\Command\OpenTab a un objet héritant de Zend\EventManager\EventManager, on va pouvoir déclencher l'événement.

Utiliser une méthode dédiée

On pourrait utiliser une méthode triggerMe() qui déclencherait l'événement, mais on serait alors obligé de l'invoquer.

Déclencher systématiquement l'événement dans le constructeur

Pour ma part, je souhaitais, autant que possible, que si l'objet était instancié, l'événement serait déclenché -> dans le constructeur alors ? Avec le gestionnaire de services, toutefois, ce n'est pas possible puisqu'on construit tous nos objets dans le gestionnaire de services et on y fait appel après coup... Les événements seraient tous déclenchés au lancement de l'application...

Déclencher systématiquement l'événement une fois que l'objet est défini

Finalement, comme l'objet CoffeeBar\Command\OpenTab sera véritablement défini après que le formulaire d'ouverture de note sera validé, il faut déclencher l'événement une fois que les propriétés de l'objet CoffeeBar\Command\OpenTab étaient définies par le formulaire : juste avant d'hydrater l'objet donc. Parmi les hydrators disponibles dans Zend Framework 2, seul l'hydrator Zend\Stdlib\Hydrator\ArraySerializable implemente des méthodes obligatoires. Les autres hydrators utilisent les propriétés et/ou les getters / setters de l'objet.

// module/CoffeeBar/src/CoffeeBar/Command/OpenTab.php
namespace CoffeeBar\Command ;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
// OpenTab déclenche un événement. Il faut donc que l’objet puisse avoir
// accès à un Event Manager pour y déclencher l’événement
class OpenTab implements EventManagerAwareInterface
{
    // les autres méthodes

    // la méthode populate() est obligatoire si on veut utiliser l’hydrator ArraySerializable()
    // Or l’hydrator ArraySerializable est le seul hydrator exposé par Zend Framework qui permet
    // d’hydrater un objet avec une fonction personnalisée
    // Nous avons besoin de la fonction personnalisée pour déclencher l’événement au moment
    // où on hydrate l’objet...
    public function populate($data = array())
    {
        // hydrating the object
        $this->id = (isset($data['id'])) ? $data['id'] : null;
        $this->tableNumber = (isset($data['tableNumber'])) ? $data['tableNumber'] : null;
        $this->waiter = (isset($data['waiter'])) ? $data['waiter'] : null;
        // triggering the event
        $this->events->trigger('openTab', '', array('openTab' => $this)) ;
    }

    // la méthode getArrayCopy() est obligatoire pour l’hydrator ArraySerializable()
    public function getArrayCopy() {
        return array(
            'id' => $this->id,
            'tableNumber' => $this->tableNumber,
            'waiter' => $this->waiter,
                ) ;
    }
}

Voyons comment on organise tout ça dans notre gestionnaire de services.

// module/CoffeeBar/Module.php
<?php
namespace CoffeeBar;
use CoffeeBar\Command\OpenTab;
use Zend\Stdlib\Hydrator\ArraySerializable;
class Module
{
    public function getConfig() {...}

    public function getAutoloaderConfig() {...}
   
    // on charge le Service Manager
    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                // formulaire OpenTabForm avec l'instruction setObject()
                'OpenTabForm' => function($sm) {
                    // parce que le formulaire OpenTabForm utilise un élément de formulaire personnalisé
                    // il faut utiliser $this->serviceLocator->get('FormElementManager') ;
                    // et utiliser le formulaire à partir du Form Element Manager
                    $formManager = $sm->get('FormElementManager') ;
                    $form = $formManager->get('CoffeeBar\Form\OpenTabForm') ;
                    // OpenTabCommand : clé dans le Service Manager
                    $form->setObject($sm->get('OpenTabCommand')) ;
                    // on peut ajouter l'hydrator directement
                    // dans le fichier CoffeeBar/Form/OpenTabForm.php
                    $form->setHydrator(new ArraySerializable()) ;
                    return $form ;
                },
                'OpenTabCommand' => function($sm) {
                    $eventsManager = $sm->get('TabEventManager') ;
                    $openTab = new OpenTab() ;
                    // injection du gestionnaire d’événement dans l’objet OpenTab
                    $openTab->setEventManager($eventsManager) ;
                    return $openTab ;
                },
            ),
        ) ;
    }
}

 

Le contrôleur

Et enfin le controller, où il faut compléter et traiter le formulaire s'il est valide.

// module/CoffeeBar/src/CoffeeBar/Controller/TabController.php
<?php
namespace CoffeeBar\Controller ;
use Zend\Mvc\Controller\AbstractActionController;
class TabController extends AbstractActionController
{
    public function openAction()
    {
        // récupérer le formulaire dans le ServiceManager
        $form = $this->serviceLocator->get('OpenTabForm') ;
        $request = $this->getRequest() ;

        // si le formulaire a été posté
        if($request->isPost()) {
            // assigné les données du tableau $_POST aux éléments du formulaire
            $form->setData($request->getPost()) ;
            
            // si le formulaire est valide, hydraté l'objet qui est lié au formulaire (OpenTab)
            if($form->isValid()) {
                $openTab = $form->getObject() ;
                // on redirige directement vers la page de prise de commande
                // notez qu'on passe en paramètre le numéro de la table
                return $this->redirect()->toRoute('tab/order', array('id' => $openTab->getTableNumber()));
            }
        }

        $result['form'] = $form ;
        return array('result' => $result) ;
    }
}

 

Quand le formulaire sera validé, on va mapper les données du formulaire CoffeeBar\Form\OpenTabForm sur l'objet CoffeeBar\Command\OpenTab. Au moment où on va hydrater notre objet, l'événement 'openTab' va se déclencher.

Restez dans le coin !! dans le prochain article, nous allons voir qui et comment on va prendre cet événement 'openTab' en charge.
 

Vous trouverez l'intégralité de l'application sur mon github