<?xml version="1.0" encoding="UTF-8"?>
<!-- EN-Revision: 24249 -->
<!-- Reviewed: no -->
<sect1 id="learning.form.decorators.layering">
    <title>Chainer les décorateurs</title>

    <para>
        Si vous avez bien suivi <link linkend="learning.form.decorators.simplest">la section
            précédente</link>, vous avez pu remarquer que la méthode
        <methodname>render()</methodname> prend un argument, <varname>$content</varname>.
        Il est de type chaîne de caractères. <methodname>render()</methodname> va utiliser cette
        chaîne et décider de la remplacer, de rajouter ou de faire précéder du contenu à celle-ci.
        Ceci permet de chaîner les décorateurs -- ce qui ouvre des possibilités de créer ses propres
        décorateurs qui vont rendre chacun une petite partie des données d'un élément -- c'est la
        chaîne complète de décorateurs qui déterminera le rendu final réel de l'élément.
    </para>

    <para>
        Voyons voir en pratique comment ça fonctionne.
    </para>

    <para>
        Pour la plupart des éléments, les décorateurs suivants sont chargés par défaut&#160;:
    </para>

    <itemizedlist>
        <listitem>
            <para>
                <classname>ViewHelper</classname>&#160;: utilise une aide de vue pour rendre
                l'élément balise de formulaire à proprement parlé.
            </para>
        </listitem>

        <listitem>
            <para>
                <classname>Errors</classname>&#160;: utilise l'aide de vue
                <classname>FormErrors</classname> pour afficher les erreurs de validation
                éventuelles.
            </para>
        </listitem>

        <listitem>
            <para>
                <classname>Description</classname>&#160;: utilise l'aide de vue
                <classname>FormNote</classname> afin de rendre la description éventuelle de
                l'élément.
            </para>
        </listitem>

        <listitem>
            <para>
                <classname>HtmlTag</classname>&#160;: encapsule les trois objets ci-dessus
                dans un tag <code>&lt;dd&gt;</code>.
            </para>
        </listitem>

        <listitem>
            <para>
                <classname>Label</classname>&#160;: rend l'intitulé de l'élément en utilisant
                l'aide de vue <classname>FormLabel</classname> (et en encapsulant le tout
                dans un tag <code>&lt;dt&gt;</code>).
            </para>
        </listitem>
    </itemizedlist>

    <para>
        Notez bien que chaque décorateur n'a qu'une petite tâche particulière et opère sur une
        partie spécifique des données de l'élément auquel il est rattaché, le décorateur
        <classname>Errors</classname> récupère les messages de validation de l'élément et
        effectue leur rendu, le décorateur <classname>Label</classname> rend simplement le
        libellé. Ceci fait que chaque décorateur est très petit, réutilisable, et surtout testable.
    </para>

    <para>
        Cet argument <varname>$content</varname> vient de là aussi&#160;: chaque décorateur
        travaille avec sa méthode <methodname>render()</methodname> sur un contenu (généralement
        généré par le décorateur immédiatement précédent dans la pile globale) et embellit ce
        contenu en lui rajoutant ou en lui faisant précéder des informations. Il peut aussi
        remplacer totalement son contenu.
    </para>

    <para>
        Ainsi, pensez au mécanisme des décorateurs comme la conception d'un oignon de l'intérieur
        vers l'extérieur.
    </para>

    <para>
        Voyons voir un exemple, le même que celui<link
            linkend="learning.form.decorators.simplest">de la section précédente</link>&#160;:
    </para>

    <programlisting language="php"><![CDATA[
class My_Decorator_SimpleInput extends Zend_Form_Decorator_Abstract
{
    protected $_format = '<label for="%s">%s</label>'
                       . '<input id="%s" name="%s" type="text" value="%s"/>';

    public function render($content)
    {
        $element = $this->getElement();
        $name    = htmlentities($element->getFullyQualifiedName());
        $label   = htmlentities($element->getLabel());
        $id      = htmlentities($element->getId());
        $value   = htmlentities($element->getValue());

        $markup  = sprintf($this->_format, $id, $label, $id, $name, $value);
        return $markup;
    }
}
]]></programlisting>

    <para>
        Supprimons la fonctionnalité libellé (label) et créons un décorateur spécifique pour lui.
    </para>

    <programlisting language="php"><![CDATA[
class My_Decorator_SimpleInput extends Zend_Form_Decorator_Abstract
{
    protected $_format = '<input id="%s" name="%s" type="text" value="%s"/>';

    public function render($content)
    {
        $element = $this->getElement();
        $name    = htmlentities($element->getFullyQualifiedName());
        $id      = htmlentities($element->getId());
        $value   = htmlentities($element->getValue());

        $markup  = sprintf($this->_format, $id, $name, $value);
        return $markup;
    }
}

class My_Decorator_SimpleLabel extends Zend_Form_Decorator_Abstract
{
    protected $_format = '<label for="%s">%s</label>';

    public function render($content)
    {
        $element = $this->getElement();
        $id      = htmlentities($element->getId());
        $label   = htmlentities($element->getLabel());

        $markup = sprint($this->_format, $id, $label);
        return $markup;
    }
}
]]></programlisting>

    <para>
        Ok, ca semble bon mais il y a un problème&#160;: le dernier décorateur va l'emporter. Vous
        allez vous retrouver avec comme seul rendu, celui du dernier décorateur.
    </para>

    <para>
        Pour faire fonctionner le tout comme il se doit, concaténez simplement le contenu précédent
        <varname>$content</varname> avec le contenu généré&#160;:
    </para>

    <programlisting language="php"><![CDATA[
return $content . $markup;
]]></programlisting>

    <para>
        Le problème avec cette approche est que vous ne pouvez pas choisir où se place le contenu
        du décorateur en question. Heureusement, un mécanisme standard existe&#160;;
        <classname>Zend_Form_Decorator_Abstract</classname> possède le concept de place et définit
        des constantes pour le régler. Aussi, il permet de préciser un séparateur à placer entre
        les 2. Voyons celà&#160;:
    </para>

    <programlisting language="php"><![CDATA[
class My_Decorator_SimpleInput extends Zend_Form_Decorator_Abstract
{
    protected $_format = '<input id="%s" name="%s" type="text" value="%s"/>';

    public function render($content)
    {
        $element = $this->getElement();
        $name    = htmlentities($element->getFullyQualifiedName());
        $id      = htmlentities($element->getId());
        $value   = htmlentities($element->getValue());

        $markup  = sprintf($this->_format, $id, $name, $value);

        $placement = $this->getPlacement();
        $separator = $this->getSeparator();
        switch ($placement) {
            case self::PREPEND:
                return $markup . $separator . $content;
            case self::APPEND:
            default:
                return $content . $separator . $markup;
        }
    }
}

class My_Decorator_SimpleLabel extends Zend_Form_Decorator_Abstract
{
    protected $_format = '<label for="%s">%s</label>';

    public function render($content)
    {
        $element = $this->getElement();
        $id      = htmlentities($element->getId());
        $label   = htmlentities($element->getLabel());

        $markup = sprintf($this->_format, $id, $label);

        $placement = $this->getPlacement();
        $separator = $this->getSeparator();
        switch ($placement) {
            case self::APPEND:
                return $markup . $separator . $content;
            case self::PREPEND:
            default:
                return $content . $separator . $markup;
        }
    }
}
]]></programlisting>

    <para>
        Notez que dans l'exemple ci-dessus, nous intervertissons les comportements par défaut
        avec append et prepend.
    </para>

    <para>
        Créons dès lors un élément de formulaire qui va utiliser tout celà&#160;:
    </para>

    <programlisting language="php"><![CDATA[
$element = new Zend_Form_Element('foo', array(
    'label'      => 'Foo',
    'belongsTo'  => 'bar',
    'value'      => 'test',
    'prefixPath' => array('decorator' => array(
        'My_Decorator' => 'path/to/decorators/',
    )),
    'decorators' => array(
        'SimpleInput',
        'SimpleLabel',
    ),
));
]]></programlisting>

    <para>
        Comment ça fonctionne&#160;? et bien nous appelons <methodname>render()</methodname>,
        l'élément va alors commencer une itération sur tous ses décorateurs, en appelant
        <methodname>render()</methodname> sur chacun. Il va passer une chaîne vide comme contenu
        pour le premier décorateur, et le rendu de chaque décorateur va servir de contenu pour
        le suivant, ainsi de suite&#160;:
    </para>

    <itemizedlist>
        <listitem>
            <para>
                Contenu initial&#160;: chaîne vide: ''.
            </para>
        </listitem>

        <listitem>
            <para>
                Chaîne vide ('') est passée au décorateur <classname>SimpleInput</classname>, qui
                génère un tag de formulaire de type input qu'il ajoute à la chaîne vide&#160;:
                <emphasis>&lt;input id="bar-foo" name="bar[foo]" type="text"
                    value="test"/&gt;</emphasis>.
            </para>
        </listitem>

        <listitem>
            <para>
                Ce contenu généré est alors passé comme contenu original pour le décorateur
                <classname>SimpleLabel</classname> qui génère un libellé et le place avant le
                contenu original avec comme séparateur <constant>PHP_EOL</constant>, ce qui
                donne&#160;: <emphasis>&lt;label for="bar-foo"&gt;\n&lt;input id="bar-foo"
                    name="bar[foo]" type="text" value="test"/&gt;</emphasis>.
            </para>
        </listitem>
    </itemizedlist>

    <para>
        Mais attendez une minute&#160;! Et si nous voulions que le libellé soit rendu après le tag
        de formulaire pour une raison quelconque&#160;? Vous souvenez-vous de l'option
        "placement"&#160;? Vous pouvez la préciser comme option de décorateur, et le plus simple
        est alors de la passer à la création de l'élément&#160;:
    </para>

    <programlisting language="php"><![CDATA[
$element = new Zend_Form_Element('foo', array(
    'label'      => 'Foo',
    'belongsTo'  => 'bar',
    'value'      => 'test',
    'prefixPath' => array('decorator' => array(
        'My_Decorator' => 'path/to/decorators/',
    )),
    'decorators' => array(
        'SimpleInput'
        array('SimpleLabel', array('placement' => 'append')),
    ),
));
]]></programlisting>

    <para>
        Notez que passer des options vous oblige à préciser le nom du décorateur dans un tableau en
        tant que premier élément, le deuxième élément est un tableau d'options.
    </para>

    <para>
        Le code ci-dessus propose un rendu&#160;: <emphasis>&lt;input id="bar-foo" name="bar[foo]"
            type="text" value="test"/&gt;\n&lt;label for="bar-foo"&gt;</emphasis>.
    </para>

    <para>
        Grâce à cette technique, vous pouvez avoir plusieurs décorateurs dont chacun s'occupe
        de rendre une petite partie d'un élément&#160;; et c'est en utilisant plusieurs décorateurs
        et en les chaînant correctement que vous obtiendrez un rendu complet&#160;: l'oignon final.
    </para>

    <para>
        Avantages et inconvénients d'une telle technique, commençons par les inconvénients&#160;:
    </para>

    <itemizedlist>
        <listitem>
            <para>
                C'est plus complexe qu'un rendu simple. Vous devez faire attention à chaque
                décorateur mais en plus à l'ordre dans lequel ils agissent.
            </para>
        </listitem>

        <listitem>
            <para>
                Ça consomme plus de ressources. Plus de décorateurs, plus d'objets, multipliés par
                le nombre d'éléments dans un formulaire et la consommation en ressources augmente.
                La mise en cache peut aider.
            </para>
        </listitem>
    </itemizedlist>

    <para>
        Les avantages sont&#160;:
    </para>

    <itemizedlist>
        <listitem>
            <para>
                Réutilisabilité. Vous pouvez créer des décorateurs complètement réutilisables car
                vous ne vous souciez pas du rendu final, mais de chaque petit bout de rendu.
            </para>
        </listitem>

        <listitem>
            <para>
                Fléxibilité. Il est en théorie possible d'arriver au rendu final voulu très
                exactement, et ceci avec une petite poignée de décorateurs.
            </para>
        </listitem>
    </itemizedlist>

    <para>
        Les exemples ci-dessus montrent l'utilisation de décorateurs au sein même d'un objet
        <classname>Zend_Form</classname> et nous avons vu comment les décorateurs jouent les uns
        avec les autres pour arriver au rendu final. Afin de pouvoir les utiliser de manière
        indépendante, a version 1.7 a ajouté des méthodes flexibles rendant les formulaires
        ressemblant au style Rail. Nous allons nous pencher sur ce fait dans la section suivante.
    </para>
</sect1>
