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

Puesto que no se muy bien como justificar una introducción a este post (de hecho tampoco sé si el título es el más adecuado), voy a poneros simplemente en situación.

Tenemos instalado el plugin sfDoctrineGuardPlugin y necesitamos guardar información “extra” sobre nuestros usuarios, por ejemplo, el email y el nombre. En este caso, y de acuerdo con las indicaciones de @jwage, crearíamos una nueva tabla Profile que guarda una relación 1:1 con sfGuardUser, tal que así:

Profile:
  columns:
    sf_guard_user_id: integer(4)
    full_name: string(255)
    email_address: string(255)
  relations:
    User:
      class: sfGuardUser
      local: sf_guard_user_id
      foreign: id
      foreignType: one

Hasta aquí, nada nuevo bajo el sol. Ahora, supongamos que queremos acceder al nombre de un usuario, el código para esto sería:

  $user->Profile->full_name;

Sencillo, ¿no? Pero, puesto que la relación es de 1:1, en el fondo de nuestro corazón nos gustaría que esto,

  $user->full_name;

bastase para obtener ese valor. ¿Cómo lo conseguimos?

Solución rápida y para toda la familia
La forma más rápida y obvia de conseguir lo anterior es también la más sencilla, basta con esto

// lib/model/doctrine/sfDoctrineGuardPlugin/sfGuardUser.class.php
 
  public function getFullName()
  {
    return $this->Profile->full_name;
  }

pero, ¿qué ocurre si también queremos recoger el email?¿y una dirección?¿y el teléfono?¿y …? De acuerdo a este planteamiento tendremos que crear un nuevo getter en el objeto sfGuardUser por cada atributo en el objeto Profile. Por suerte, existe una solución “mejor”.

La solución “mágica”
PHP dispone de algo muy interesante llamado métodos mágicos. La solución que se muestra más abajo está basada en la utilización que hace Doctrine, en su objeto Doctrine_Record, del método mágico __get($fieldName).

// lib/model/sfGuardUser.class.php
class sfGuardUser extends sfDoctrineRecord
{
  public function get($fieldName, $type = true)
  {
    try{
      parent::get($fieldName, $type = true);
    }catch(exception $e){
      $this->Profile->$fieldName;
    }
  }
}

Aunque no entraré a explicar en detalle que es todo lo que ocurre, grosso modo, cada vez que se pide un atributo al objeto sfGuardUser busca entre los suyos propios y en caso de que no lo encuentre, lo intentará buscar entre los del su Profile. En caso de que tampoco los encuentre, saltará una excepción.

Algunas ventajas
Aparte de las ventajas obvias de poder obtener el valor de los atributos del objeto Profile de manera transparente, también podemos utilizar en el admin generator para mostrar estos campos, por ejemplo, en el listado:

  list:
    display: [username, full_name, email]
Symfony y Desarrollo Web © Copyright 2009, All Rights Reserved.