Al desarrollar un panel de administración con el Admin Generator de symfony, en ocasiones nos ocurre que desearíamos poder incrustar código PHP en el archivo generator.yml para cumplir nuestros objetivos. Por ejemplo, supongamos que quisiéramos mostrar un listado de elementos distintos en función de los permisos del usuario que está logueado. Nos encantaría poder hacer algo como esto

generator:
  config:
    list:
      <?php if(sfContext::getInstance()->getUser()->hasCredentials('manager')): ?>
      table_method: "doSelectForManagers"
      <?php else: ?>
      table_method: "doSelect"
      <?php endif; ?>

El código escrito no daría ningún error y funcionaría … aunque sólo la primera vez ya que una vez compilado este archivo (convertido en PHP) siempre se ejecutaría el método del “table_method” elegido la primera vez.

Una solución no muy buena
Una posible solución para logar nuestro objetivo sería llevar a cabo la lógica para comprobar los credenciales de usuario dentro del propio módelo, algo así:

// lib/model/doctrine/item.class.php
class Item extends BaseItem
{
  public function doSelect()
  {
    $query = Doctrine_Query()::create();
    if(sfContext::getInstance()->getUser()->hasCredentials('manager'))
    {
      // Añadir una seria de condiciones a la query
    }else{
     // Añadir otras condiciones a la query
    }
    return $query
  }	
}

El inconveniente de esta solución es que estaríamos acoplando muy fuertemente nuestro modelo de datos con el usuario de la sesión lo cual nos haría muy difícil hacer pruebas unitarias más adelante, por lo tanto no es muy aconsejable.

Mi solución
La solución que propongo pasa por hacer lo que se hizo al principio sólo que en lugar de sobre el archivo generator.yml sobre el auténtico objeto PHP en el que se convierte este archivo una vez compilado, la clase xxxGeneratorConfiguration, que es precisamente uno de los dos archivos generados bajo la carpeta “lib” en el módulo (si no sabías para que servían estos archivos, ya puedes hacerte una idea, ;) ).

De esta manera el código quedaría de la siguiente manera

// lib/xxxGeneratorConfiguration.class.php
class xxxGeneratorConfiguration extends BaseXxxGeneratorConfiguration
{
  public function getTableMethod()
  {
    $user = sfContext::getInstance()->getUser()->hasCredentials('manager')
    if($user->hasCredentials('manager')){
      return 'doSelectForManagers';
    }else{
      return 'doSelect';
    }
  }
}

Si quieres conocer algo más en profundidad sobre el admin generator y cómo funciona, a lo mejor te ayude algo la charla que dí en Castellón sobre esto mismo, http://vimeo.com/13325576

No hay duda de que una de las mejores características de symfony es el Admin Generator, gracias a él podemos tener un panel de administración listo para usar en cuestión de minutos y además, podemos personalizarlo todo lo que queramos.

Hoy veremos como añadir nuevas acciones sobre objetos (object_actions) a la vista del listado y cómo personalizarlas desde el archivo generator.yml

Las object_actions por defecto
Por defecto, symfony incluye dos acciones para cada elemento del listado, eliminar y editar. Estas dos acciones son un tanto especiales en el sentido de que están ya están “personalizadas”, mostrando un icono distinto e incluso saltando javascript al hacer click (el caso del botón eliminar). En el archivo generator.yml:

generator:
  param:
    config:
      list:
        object_actions:
          _delete: ~
          _edit:   ~

Si no queremos mostrar una de las acciones en el listado bastará con borrarla.

Añadiendo una object_action
Como siempre es más sencillo hablar con ejemplos concretos, supongamos que el listado que estamos mostrado es de noticias y queremos añadir una acción que sea “publicar” de manera que al hacer click sobre ella se publique la noticia. Lo primero que habría que hacer sería añadir esta acción al listado, así:

generator:
  param:
    config:
      list:
        object_actions:
          _delete: ~
          _edit:   ~
          publish: ~

De esta forma, aparecerá un nuevo enlace a “modulo/:id/ListPublish” y por tanto hará falta definir la acción “ListPublish” dentro del archivo actions.class.php

// /module/moduleName/actions/actions.class.php
public function executeListPublish(sfWebRequest $request)
{
  $this->getRoute()->getObject()->publish();
  $this->redirect('@moduleName');
}

Con esto estaría el trabajo hecho, pero siempre puede quedar algo más “bonito”, ;-) .

Personalizando el texto
¿Qué ocurre si queremos que el texto que aparezca sea “Publicar Noticia” en lugar de “Publish”?, muy sencillo:

generator:
  param:
    config:
      list:
        object_actions:
          publish: { label: "Publicar Texto" }

Personalizando el HTML generado
¿Y si queremos añadirle una clase de CSS o un ID al anchor tag? Más fácil:

generator:
  param:
    config:
      list:
        object_actions:
          publish: 
            label: "Publicar Texto"
            params: { class: 'myClass',  id: 'publish-button' }

¿Y para que salte un javascript como en el caso del botón eliminar?

generator:
  param:
    config:
      list:
        object_actions:
          publish: 
            label: "Publicar Texto"
            params:
              class: 'myClass'
              id: 'publish-button'
              onclick: 'return confirm('¿Estás seguro?');'

Notar que para cambiar la imagen del icono debemos de hacerlo a través de la hoja de estilos admin.css modificando el atributo background de

#sf_admin_container ul li.sf_admin_action_publish a

Personalizando la acción que se ejecuta
Por último, si por cualquier motivo queremos que se ejecute la acción “publish” en lugar de “ListPublish” bastará con

generator:
  param:
    config:
      list:
        object_actions:
          publish: 
            action: 'publish'

Como consecuencia de mi pequeño experimento de la semana pasada, aprendí alguna que otra cosa interesante sobre los logs de symfony.

En concreto, el núcleo de symfony viene con varios loggers interesantes, a saber:

  • sfNoLogger: Es el “anti-logger” ya que no hace nada. Es el utilizado por defecto en el entorno de producción.
  • sfVarLogger: Guarda los mensajes de log en una variable interna, llamada … logs! Se utiilza como clase padre para implementar sfWebDebugLogger
  • sfStreamLogger: Vuelca los mensajes de logs sobre un “stream” genérico. Se utiliza como clase padre para implementar sfConsoleLogger que utiliza como stream de salida php://stdout
  • sfFileLogger: Escribe los mensaje de log en un archivo que se da como parámetro a su constructor.

Además de estos, symfony añade otro más, sfAggregateLogger que es el utilizado por defecto en el entorno de desarrollo (dev). Este logger nos permite mostrar los logs de salida a través de tantos loggers como queramos simplemente añadiendo el nuevo log a lista, para ello, o bien utilizamos el método sfAggregateLogger::addLoggers($loggers) o bien los cargamos directamente mediante el archivo factories.yml:

#/app/config/factories.yml
 
logger:
  class: sfAggregateLogger
  param:
    loggers:
      sf_web_debug:
        class: sfWebDebugLogger
        param:
          level: debug
          condition:       %SF_WEB_DEBUG%
          xdebug_logging:  true
          web_debug_class: sfWebDebug
      f_twitty_logger:
        class: fTwittyLogger
        param:
          user: fTwittyLogger
          pass: symfony

En el ejemplo anterior estaríamos utilizando dos logger, sfWebDebugLogger y nuestro logger propio fTwittyLogger

Symfony y Desarrollo Web © Copyright 2009, All Rights Reserved.