7.2. Fragmentos de código

En ocasiones es necesario incluir cierto código HTML o PHP en varias páginas. Para no tener que repetirlo, casi siempre es suficiente con utilizar la instrucción include().

Si por ejemplo varias de las plantillas de la aplicación utilizan el mismo fragmento de código, se puede guardar en un archivo llamado miFragmento.php en el directorio global de plantillas (miproyecto/apps/miaplicacion/templates/) e incluirlo en las plantillas mediante la instrucción siguiente:

<?php include(sfConfig::get('sf_app_template_dir').'/miFragmento.php') ?>

Sim embargo, esta forma de trabajar con fragmentos de código no es muy limpia, sobre todo porque puede que los nombres de las variables utilizadas no coincidan en el fragmento de código y en las distintas plantillas. Además, el sistema de cache de Symfony (que se explica en el Capítulo 12) no puede detectar el uso de include(), por lo que no se puede incluir en la cache el código del fragmento de forma independiente al de las plantillas. Symfony define 3 alternativas al uso de la instrucción include() y que permiten manejar de forma inteligente los fragmentos de código:

  • Si el fragmento contiene poca lógica, se puede utilizar un archivo de plantilla al que se le pasan algunas variables. En este caso, se utilizan los elementos parciales partial).
  • Si la lógica es compleja (por ejemplo se debe acceder a los datos del modelo o se debe variar los contenidos en función de la sesión) es preferible separar la presentación de la lógica. En este caso, se utilizan componentes component).
  • Si el fragmento va a reemplazar una zona específica del layout, para la que puede que exista un contenido por defecto, se utiliza un slot.

Nota Existe otro tipo de fragmento de código, llamado "slot de componentes", que se utiliza cuando el fragmento depende del contexto (por ejemplo si el fragmento debe ser diferente para las acciones de un mismo módulo). Más tarde en este capítulo se explican los "slots de componentes".

Todos estos fragmentos se incluyen mediante los helpers del grupo llamado Partial. Estos helpers están disponibles en cualquier plantilla de Symfony sin necesidad de declararlos al principio.

7.2.1. Elementos parciales

Un elemento parcial es un trozo de código de plantilla que se puede reutilizar. Por ejemplo, en una aplicación de publicación, el código de plantilla que se encarga de mostrar un artículo se utiliza en la página de detalle del artículo, en la página que lista los mejores artículo y en la página que muestra los últimos artículos. Se trata de un código perfecto para definirlo como elemento parcial, tal y como muestra la figura 7-2.

Reutilización de elementos parciales en las plantillas

Figura 7.2 Reutilización de elementos parciales en las plantillas

Al igual que las plantillas, los elementos parciales son archivos que se encuentran en el directorio templates/, y que contienen código HTML y código PHP. El nombre del archivo de un elemento parcial siempre comienza con un guión bajo (_), lo que permite distinguir a los elementos parciales de las plantillas, ya que todos se encuentran en el mismo directorio templates/.

Una plantilla puede incluir elementos parciales independientemente de que estos se encuentren en el mismo módulo, en otro módulo o en el directorio global templates/. Los elementos parciales se incluyen mediante el helper include_partial(), al que se le pasa como parámetro el nombre del módulo y el nombre del elemento parcial (sin incluir el guión bajo del principio y la extensión .php del final), tal y como se muestra en el listado 7-7.

Listado 7-7 - Incluir elementos parciales en una plantilla del módulo mimodulo

// Incluir el elemento pacial de miaplicacion/modules/mimodulo/templates/_miparcial1.php
// Como la plantilla y el elemento parcial están en el mismo módulo,
// se puede omitir el nombre del módulo
<?php include_partial('miparcial1') ?>
 
// Incluir el elemento parcial de miaplicacion/modules/otromodulo/templates/_miparcial2.php
// En este caso es obligatorio indicar el nombre del módulo
<?php include_partial('otromodulo/miparcial2') ?>
 
// Incluir el elemento parcial de miaplicacion/templates/_miparcial3.php
// Se considera que es parte del módulo 'global'
<?php include_partial('global/miparcial3') ?>

Los elementos parciales pueden acceder a los helpers y atajos de plantilla que proporciona Symfony. Pero como los elementos parciales se pueden llamar desde cualquier punto de la aplicación, no tienen acceso automático a las variables definidas por la acción que ha incluido la plantilla en la que se encuentra el elemento parcial, a no ser que se pase esas variables explícitamente en forma de parámetro. Si por ejemplo se necesita que un elemento parcial tenga acceso a una variable llamada $total, la acción pasa esa variable a la plantilla y después la plantilla se la pasa al helper como el segundo parámetro de la llamada a la función include_partial(), como se muestra en los listado 7-8, 7-9 y 7-10.

Listado 7-8 - La acción define una variable, en mimodulo/actions/actions.class.php

class mimoduloActions extends sfActions
{
  public function executeIndex()
  {
    $this->total = 100;
  }
}

Listado 7-9 - La plantilla pasa la variable al elemento parcial, en mimodulo/templates/indexSuccess.php

<p>¡Hola Mundo!</p>
<?php include_partial('miparcial',
array('mitotal' => $total)
) ?>

Listado 7-10 - El elemento parcial ya puede usar la variable, en mimodulo/templates/_miparcial.php

<p>Total: <?php echo $mitotal ?></p>

Truco Hasta ahora, todos los helpers se llamaban con la función <?php echo nombreFuncion() ?>. Por el contrario, el helper utilizado con los elementos parciales se llama mediante <?php include_partial() ?>, sin incluir el echo, para hacer su comportamiento más parecido a la instrucción de PHP include(). Si alguna vez se necesita obtener el contenido del elemento parcial sin mostrarlo, se puede utilizar la función get_partial(). Todos los helpers de tipo include_ de este capítulo, tienen una función asociada que comienza por get_ y que devuelve los contenidos que se pueden mostrar directamente con una instrucción echo.

7.2.2. Componentes

En el Capítulo 2, el primer script de ejemplo se dividía en dos partes para separar la lógica de la presentación. Al igual que el patrón MVC se aplica a las acciones y las plantillas, es posible dividir un elemento parcial en su parte de lógica y su parte de presentación. En este caso, se necesitan los componentes.

Un componente es como una acción, solo que mucho más rápido. La lógica del componente se guarda en una clase que hereda de sfComponents y que se debe guardar en el archivo actions/components.class.php. Su presentación se guarda en un elemento parcial. Los métodos de la clase sfComponents empiezan con la palabra execute, como sucede con las acciones, y pueden pasar variables a su presentación de la misma forma en la que se pasan variables en las acciones. Los elementos parciales que se utilizan como presentación de un componente, se deben llamar igual que los componentes, sustituyendo la palabra execute por un guión bajo. La tabla 7-1 compara las convenciones en los nombres de las acciones y los componentes.

Tabla 7-1. Convenciones en el nombrado de las acciones y de los componentes

Convención Acciones Componentes
Archivo de la lógica actions.class.php components.class.php
Clase de la que hereda la lógica sfActions sfComponents
Nombre de los métodos executeMiAccion() executeMiComponente()
Nombre del archivo de presentación miAccionSuccess.php _miComponente.php

Truco De la misma forma que es posible separar los archivos de las acciones, la clase sfComponents dispone de una equivalente llamada sfComponent y que permite crear archivos individuales para cada componente siguiendo una sintaxis similar.

Por ejemplo, se puede definir una zona lateral que muestra las últimas noticias de un determinado tema que depende del perfil del usuario y que se va a reutilizar en varias páginas. Las consultas necesarias para mostrar las noticias son demasiado complejas como para incluirlas en un elemento parcial, por lo que se deben incluir en un archivo similar a las acciones, es decir, en un componente. La figura 7-3 ilustra este ejemplo.

Uso de componentes en las plantillas

Figura 7.3 Uso de componentes en las plantillas

En este ejemplo, mostrado en los listados 7-11 y 7-12, el componente se define en su propio módulo (llamado news), pero se pueden mezclar componentes y acciones en un único módulo, siempre que tenga sentido hacerlo desde un punto de vista funcional.

Listado 7-11 - La clase de los componentes, en modules/news/actions/components.class.php

<?php
 
    class newsComponents extends sfComponents
    {
      public function executeHeadlines()
      {
        $c = new Criteria();
        $c->addDescendingOrderByColumn(NewsPeer::PUBLISHED_AT);
        $c->setLimit(5);
        $this->news = NewsPeer::doSelect($c);
      }
    }

Listado 7-12 - El elemento parcial, en modules/news/templates/_headlines.php

<div>
      <h1>Últimas noticias</h1>
      <ul>
      <?php foreach($news as $headline): ?>
        <li>
          <?php echo $headline->getPublishedAt() ?>
          <?php echo link_to($headline->getTitle(),'news/show?id='.$headline->getId()) ?>
        </li>
      <?php endforeach ?>
      </ul>
    </div>

Ahora, cada vez que se necesite el componente en una plantilla, se puede incluir de la siguiente forma:

<?php include_component('news', 'headlines') ?>

Al igual que sucede con los elementos parciales, se pueden pasar parámetros adicionales a los componentes mediante un array asociativo. Dentro del elemento parcial se puede acceder directamente a los parámetros mediante su nombre y en el componente se puede acceder a ellos mediante el uso de $this. El listado 7-13 muestra un ejemplo.

Listado 7-13 - Paso de parámetros a un componente y a su plantilla

// Llamada al componente
    <?php include_component('news', 'headlines', array('parametro' => 'valor')) ?>
 
    // Dentro del componente
    echo $this->parametro;
     => 'valor'
 
    // Dentro del elemento parcial _headlines.php
    echo $parametro;
     => 'valor'

Se pueden incluir componentes dentro de otros componentes y también en el layout global como si fuera una plantilla normal. Al igual que en las acciones, los métodos execute de los componentes pueden pasar variables a sus elementos parciales relacionados y pueden tener acceso a los mismos atajos. Pero las similitudes se quedan solo en eso. Los componentes no pueden manejar la seguridad ni la validación, no pueden ser llamados desde Internet (solo desde la propia aplicación) y no tienen distintas posibilidades para devolver sus resultados. Por este motivo, los componentes son más rápidos que las acciones.

7.2.3. Slots

Los elementos parciales y los componentes están especialmente diseñados para reutilizar código. Sin embargo, en muchas ocasiones se necesitan fragmentos de código que rellenen un layout con más de una zona variable. Por ejemplo se puede necesitar añadir etiquetas personalizadas en la sección <head> del layout en función del contenido de la acción. También se puede dar el caso de un layout que tiene una zona de contenidos dinámicos que se rellena con el resultado de la acción y muchas otras zonas pequeñas que tienen un contenido por defecto definido en el layout pero que puede ser modificado en la plantilla.

En los casos descritos anteriormente la solución más adecuada es un slot. Básicamente, un slot es una zona que se puede definir en cualquier elemento de la vista (layout, plantilla o elemento parcial). La forma de rellenar esa zona es similar a establecer el valor de una variable. El código de relleno se almacena de forma global en la respuesta, por lo que se puede definir en cualquier sitio (layout, plantilla o elemento parcial). Se debe definir un slot antes de utilizarlo y también hay que tener en cuenta que el layout se ejecuta después de la plantilla (durante el proceso de decoración) y que los elementos parciales se ejecutan cuando los llama una plantilla. Como todo esto suena demasiado abstracto, se va a ver su funcionamiento con un ejemplo.

Imagina que se dispone de un layout con una zona para la plantilla y 2 slots: uno para el lateral de la página y otro para el pie de página. El valor de los slots se define en la plantilla. Durante el proceso de decoración, el layout integra en su interior el código de la plantilla, por lo que los slots se rellenan con los valores que se han definido anteriormente, tal y como muestra la figura 7-4. De esta forma, el lateral y el pie de página pueden depender de la acción. Se puede aproximar a la idea de tener un layout con uno o más agujeros que se rellenan con otro código.

La plantilla define el valor de los slots del layout

Figura 7.4 La plantilla define el valor de los slots del layout

Su funcionamiento se puede comprender mejor viendo algo de código. Para incluir un slot se utiliza el helper include_slot(). El helper has_slot() devuelve un valor true si el slot ya ha sido definido antes, permitiendo de esta forma establecer un mecanismo de protección frente a errores. El listado 7-14 muestra como definir la zona para el slot lateral en el layout y su contenido por defecto.

Listado 7-14 - Incluir un slot llamado lateral en el layout

<div id="lateral">
    <?php if (has_slot('lateral')): ?>
      <?php include_slot('lateral') ?>
    <?php else: ?>
      <!-- código del lateral por defecto -->
      <h1>Zona cuyo contenido depende del contexto</h1>
      <p>Esta zona contiene enlaces e información sobre
      el contenido principal de la página.</p>
    <?php endif; ?>
    </div>

Las plantillas pueden definir los contenidos de un slot (e incluso los elementos parciales pueden hacerlo). Como los slots se definen para mostrar código HTML, Symfony proporciona métodos útiles para indicar ese código HTML: se puede escribir el código del slot entre las llamadas a las funciones slot() y end_slot(), como se muestra en el listado 7-15.

Listado 7-15 - Redefiniendo el contenido del slot lateral en la plantilla

...
    <?php slot('lateral') ?>
      <!-- Código específico para el lateral de esta plantilla -->
      <h1>Detalles del usuario</h1>
      <p>Nombre:  <?php echo $user->getName() ?></p>
      <p>Email: <?php echo $user->getEmail() ?></p>
    <?php end_slot() ?>

El código incluido entre las llamadas a los helpers del slot se ejecutan en el contexto de las plantillas, por lo que tienen acceso a todas las variables definidas por la acción. Symfony añade de forma automática en el objeto response el resultado del código anterior. No se muestra directamente en la plantilla, sino que se puede acceder a su código mediante la llamada a la función include_slot(), como se muestra en el listado 7.14.

Los slots son muy útiles cuando se tienen que definir zonas que muestran contenido que depende del contexto de la página. También se puede utilizar para añadir código HTML al layout solo para algunas acciones. Por ejemplo, una plantilla que muestra la lista de las últimas noticias puede necesitar incluir un enlace a un canal RSS dentro de la sección <head> del layout. Esto se puede conseguir añadiendo un slot llamado feed en el layout y que sea redefinido en la plantilla del listado de noticias.