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

Una situación muy típica a la hora de desarrollar una aplicación web es ofrecer al usuario la posibilidad de subir una imagen y, de manera automática, generar un thumbnail de ésta.

Si estamos intentando implementar esta funcionalidad con symfony nos encontramos con la agradable sorpresa de que ya existe un plugin que nos facilita la tarea, sfThumbnailPlugin, pero sólo se encarga de generar el propio thumbnail, la pregunta que surge es: ¿dónde integrar la lógica para que se genere el thumbnail cada vez que se añade una imagen a un formulario?

La respuesta a la última pregunta me la dió David Vega en su post sfThumbnailPlugin y AdminGenerator: redimensionar imágenes el cual recomiendo que leáis.

Reconozco que la solución de David Vega me dejó con la boca abierta por lo bien que “encuadró” el código dentro del framework (en symfony uno no sabe muchas veces dónde poner el código), aunque en mi opinión no aprovecha bien la herencia de la clase padre, así que esta es mi versión de su sfResizedFile, con su permiso, ;) :

class sfResizedFile extends sfValidatedFile
{
	public function save($file = null, $fileMode = 0666, $create = true, $dirMode = 0777)
	{
		$file = parent::save($file, $fileMode, $create, $dirMode);
 
                $thumbFile = $this->path.DIRECTORY_SEPARATOR.'thumb_'.$file;
		$thumbnail = new sfThumbnail(100, 100, true, true, 85);
		$thumbnail->loadFile($this->getTempName());
		$thumbnail->save($thumbFile, 'image/jpeg');
 
                chmod($thumbFile, $fileMode);
 
                return $file;
	}
 
}

A lo largo de las dos últimas semanas, por necesidades de un proyecto, me he visto obligado a trabajar con formularios en los que había bastantes campos de fecha.

Lo que pretendo hoy aquí es dar una serie de recetas rápidas que puedan solucionar esas preguntas “fáciles” que puedan surgir.

Cambiando el formato

Por defecto el formato es “año/mes/día”. En España el formato usual es “día/mes/año” así que para cambiarlo:

$format = '%day%/%month%/%year%';
 
$this->widgetSchema['date_field'] 
  = new sfWidgetFormDate(array('format' => $format));

Mostrando una fecha por defecto

Supongamos ahora que queremos una determinada fecha por defecto.

$default = array( 
                 'year'  => 2010, 
                 'month' => 2, 
                 'day'   => 12
              );
 
$this->widgetSchema['date_field'] 
  = new sfWidgetFormDate(array('default' => $default));

Mostrando hoy como la fecha por defecto

Si lo que queremos es que la fecha a mostrar por defecto sea el día actual

$today = array(
                'year'  => date('Y'),       
                'month' => date('n'),
                'day'   => date('j')
);
 
$this->widgetSchema['date_field'] = 
     new sfWidgetFormDate(array('default' => $today));

Eligiendo sólo unos determinados años

Supongamos que sólo nos interesa que muestre como posibles opciones de años, éste y los dos siguientes

$range  = range(date('Y'), date('Y')+2);
$years = array_combine($range,$range);
 
$this->widgetSchema['date_field'] = 
     new sfWidgetFormDate(array('years' => $years));

Validando que una fecha no es pasada

Si lo que nos interesa es que el usuario no pueda introducir una fecha pasada, por ejemplo para hacer una reserva.

$this->validatorSchema['date_field'] =
    new sfValidatorDate(array('min' => date('Y-m-d'));

Validando que una fecha no es futura

O bien nos interesa que la fecha no pueda ser futura, porque se trata de que esté diciendo cuando tuvo lugar un hecho.

$this->validatorSchema['date_field'] =
    new sfValidatorDate(array('max' => date('Y-m-d'));

Validando que una fecha es mayor que otra

Si tenemos dos campos de fecha, nos puede interesar que uno sea mayor que otro, por ejemplo la fecha de salida y llegada para una reserva de hotel.

      $this->validatorSchema->setPostValidator(
          new sfValidatorSchemaCompare('date_starts', '<', 'date_ends')
      );

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'
Symfony y Desarrollo Web © Copyright 2009, All Rights Reserved.