I. Prérequis

La version du Zend Framework 2 utilisée pour cet article est la 2.0.0beta3. L'utilisation du Zend Framework 2 nécessite au minimum la version 5.3.3 de PHP.

Zend Framework 2.0 RequirementsZend Framework 2.0 Requirements

II. Qu'est-ce qui change

Le Zend Framework 2 a complètement modifié sa gestion des vues. La première version du framework séparait le layout des vues de contenus. Il n'existe maintenant plus de composant lié au layout, celui-ci est traité comme une vue à part entière. Le framework hiérarchise maintenant ses fichiers de template afin de déterminer celui correspondant au layout, vue qui englobe les autres. Ce système d'imbrication offre une plus grande flexibilité pour les développeurs.

III. Imbrication des vues

Par défaut, la vue créée par l'action de notre contrôleur est injectée dans la vue initialisée dans le bootstrap. Cette première vue représente notre layout, voici son initialisation :

Zend\Mvc\Bootstrap.php
Sélectionnez
protected function setupView($application)
{
    [...]
    // Inject MVC Event with view model $mvcEvent  = $application->getMvcEvent(); 
    $viewModel = $mvcEvent->getViewModel();
    $viewModel->setTemplate($defaultViewStrategy->getLayoutTemplate());
    [...]
}

Une fois l'objet de vue retourné depuis notre méthode d'action, celui-ci est injecté dans le layout depuis l'écouteur d'injection de vue :

Zend\Mvc\View\InjectViewModelListener.php
Sélectionnez
public function injectViewModel(MvcEvent $e)
{
    $result = $e->getResult();
    if (!$result instanceof ViewModel) {
        return;
    }
    $model = $e->getViewModel();
    if ($result->terminate()) {
        $e->setViewModel($result);
        return;
    }
    $model->addChild($result);
}

Cette méthode nous fait remarquer que l'injection se produit uniquement si l'attribut $terminate est à « true ». Il est alors possible de supprimer le rendu du layout depuis les instructions suivantes :

IndexController.php
Sélectionnez
public function indexAction()
{
    $viewModel = new ViewModel();
    $viewModel->setTerminal(true);
    return $viewModel;
}

L'injection de la vue courante au sein du layout est le comportement le plus courant. Cependant, il est possible d'avoir un niveau d'imbrication de vues plus élevé. Voici un exemple de l'imbrication d'une vue fille au sein de la vue courante, elle-même injectée dans le layout :

layout/layout.phtml
Sélectionnez
<html lang="en">
  <head>
    <meta charset="utf-8">
    <?php echo $this->headTitle('ZF2 Lazy loading module') ?>
    <?php echo $this->headMeta()->appendName('viewport', 'width=device-width, initial-scale=1.0') ?>
    <?php echo $this->headLink() ?>
    <?php echo $this->headScript() ?>
  </head>
  <body>
    <div class="navbar navbar-fixed-top">
      <div class="navbar-inner">
        <div class="container">
          <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">

            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </a>
          <a class="brand" href="<?php echo $this->url('home') ?>">Home</a>
        </div>
      </div>
    </div>

    <div class="container">
      <?php echo $this->content; ?>
      <hr>
      <footer>
          My footer
      </footer>
    </div>
  </body>
</html>
index/index.phtml
Sélectionnez
<div class="row">
    <div class="span12">
        <?php echo $this->content; ?>
    </div> 
</div>
index/child.phtml
Sélectionnez
<h2>3 niveaux d'imbrications</h2>
IndexController.php
Sélectionnez
public function indexAction()
{
    $viewModelChild = new ViewModel();
    $viewModelChild->setTemplate('index/child');

    $viewModel = new ViewModel();
    $viewModel->addChild($viewModelChild);
    return $viewModel;
}

Il est possible d'imbriquer encore de nombreuses vues les unes dans les autres, mais le code de votre application risque alors de perdre en clarté.

Dans nos exemples, nous retournons des objets de type ViewModel pour représenter la vue courante, mais il est également possible de retourner d'autres valeurs.

IV. Création de l'objet de vue

Deux méthodes écoutent la distribution de l'action (évènement de nom « dispatch ») avec de faibles priorités, ce qui leur permet d'intervenir après le retour de l'action. Les deux écouteurs sont chargés de convertir le retour de l'action en un objet ViewModel, type d'objet attendu lors du rendu :

Zend\Mvc\View\CreateViewModelListener.php
Sélectionnez
/**
* Attach listeners
*
* @param  Events $events
* @return void
*/
public function attach(Events $events)
{
        $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'createViewModelFromArray'), -80);
        $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'createViewModelFromNull'), -80);
}

/**
* Inspect the result, and cast it to a ViewModel if an assoc array is detected
*
* @param  MvcEvent $e
* @return void
*/
public function createViewModelFromArray(MvcEvent $e)
{
        $result = $e->getResult();
        if (!ArrayUtils::hasStringKeys($result, true)) {
            return;
        }

        $model = new ViewModel($result);
        $e->setResult($model);
}

/**
* Inspect the result, and cast it to a ViewModel if null is detected
*
* @param MvcEvent $e
* @return void
*/
public function createViewModelFromNull(MvcEvent $e)
{
        $result = $e->getResult();
        if (null !== $result) {
            return;
        }

        $model = new ViewModel;
        $e->setResult($model);
}

Le type de retour de nos actions gérés sont donc :

  • Zend\View\Model ;
  • null ;
  • Array.

Tout autre type renvoyé par l'action de notre contrôleur ne générera pas d'erreur, mais aura pour conséquence de ne pas afficher la vue courante. Comme nous le voyons depuis l'injection de vue, seul les objets de type Zend\View\Model sont injectés dans le layout :

Zend\Mvc\View\InjectViewModelListener.php
Sélectionnez
public function injectViewModel(MvcEvent $e)
{
    $result = $e->getResult();
    if (!$result instanceof ViewModel) {
        return;
    }
    [...]
}

Les méthodes-écouteurs « createViewModelFromArray » et « createViewModelFromNull » qui transforment le retour de l'action courante en objet de type ViewModel interviennent avant l'injection.

Les trois méthodes suivantes produisent donc un objet de vue identique :

IndexController.php
Sélectionnez
public function monactionAction()
{
}
IndexController.php
Sélectionnez
public function monactionAction()
{
    return new ViewModel();
}
IndexController.php
Sélectionnez
public function monactionAction()
{
    return array();
}

Avec l'insertion de variables dans nos vues, les deux exemples suivants produisent un résultat identique :

IndexController.php
Sélectionnez
public function monactionAction()
{
    return new ViewModel(array('key' => 'value'));
}
IndexController.php
Sélectionnez
public function monactionAction()
{
    return array('key' => 'value');
}

Par défaut, l'objet créé pour le rendu est toujours de type ViewModel. Ce type de vue gère le rendu de code HTML, et il existe d'autres objets de vue pour la gestion d'autres formats, comme par exemple, le JSON.

V. Le modèle de vue JsonModel

Nous venons d'expliquer le comportement de l'objet ViewModel, mais deux autres modèles de vue sont proposés par le framework : JsonModel et FeedModel. Intéressons-nous au premier de ces modèles. La classe JsonModel permet d'encapsuler les données au format JSON. Afin de pouvoir gérer les vues au format JSON, nous avons besoin de modifier la stratégie de rendu qui, par défaut, utilise un objet de type PhpRendererStrategy. Nous allons alors utiliser la stratégie propre au format JSON :

IndexController.php
Sélectionnez
public function jsonAction()
{
     $locator = $this->getLocator();
     $jsonRendererStrategy = $locator->get('Zend\View\Strategy\JsonStrategy');
     $view = $locator->get('Zend\View\View');
     $view->events()->attachAggregate($jsonRendererStrategy,10);
     return new \Zend\View\Model\JsonModel(array(
          'key' => 'value',
          'state'=> 'ok'
     ));
}

Nous confions à la classe JsonStrategy la responsabilité de choisir l'objet de rendu à utiliser. Notre vue sera alors rendu par l'objet de type JsonRenderer, objet de rendu par défaut de la stratégie de rendu de JSON.

VI. Conclusion et ressources

La vue et le layout sont désormais plus flexibles et il devient plus simple de gérer le format de retour. Cet article est tiré en partie du Image non disponible livre "Au cœur de Zend Framework 2"Livre "Au coeur de Zend Framework 2", disponible dans le début du quatrième trimestre de l'année 2012. Dans ce livre, vous retrouverez en détails tous les composants intervenants lors du rendu de la vue.

Retrouvez mes modules ou autres fonctionnalités, créés pour le Zend Framework 2, sur mon compte github disponible à l'adresse https://github.com/blanchonvincent.

Retrouvez aussi la liste des tutoriels du Zend Framework 2 sur mon compte developpez.com : blanchon-vincent.developpez.comblanchon-vincent.developpez.com