Noticias Weblogs Foros Wiki Código

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Sponsors

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin

programania

sfGuard – Filtrar con campos del perfil de usuario

Junio 23rd, 2010 - [Enlace local]

Advertencia: Lo que cuento en este post sirve solo para la versión de sfGuard para Propel y usando su versión 1.5 que incluye clases Query.

Debo confesar que aun soy novatillo con el plugin sfGuard de Symfony. Este plugin aporta el modelo de datos y las herramientas necesarias para implementar y gestionar en cualquier proyecto Symfony una capa de usuarios bastante maja.

El plugin no se preocupa de los perfiles de usuario y si queremos manejar datos como nombre, emails, fechas de nacimiento, etc. tendremos que crear una estructura de datos para alojarlos y relacionarlos con nuestros usuarios. Con un poco de integridad referencial y un pequeño cambio de configuración en el archivo app.yml de vuestra aplicación sfGuard será capaz de vincular un perfil con un usuario del sistema (más info).

Además, tendremos a nuestra disposición una serie de CRUDs automágicos para realizar operaciones de alta, baja, modificación y gestión de privilegios y grupos. Sin embargo, las capacidades de filtrado de resultados del listado de usuario se limitan a los campos de la tabla de usuarios, excluyendo los del perfil que hayamos construido encima de ella.

Es una pena, porque al final siempre querremos poder buscar filtrar por campos como el nombre, email, etc. Vamos, igual que cuando se edita un usuario se pueden modificar estos datos del perfil, es de esperar que también se pueda filtrar el listado por ellos, pero no.

Después de buscar por ahí, no he visto a nadie quejándose de esto en particular (¿tan raro es?) y he deducido que podría ser por:

  1. Se trata de un comportamiento por defecto que, debido a alguna metedura de pata por mi parte, no me funciona (muy probable)
  2. sfGuard no soporta esta funcionalidad en el filtrado

Tras buscar un poquito más en el código del plugin he descartado la primera opción aunque, hey, no sería la primera vez que uno mete la pata, así que si alguien me corrige, yo agradecido.

De todos modos no me ha gustado la solución a la que he llegado que es correcta pero no del todo elegante, porque he tenido que tocar las clases del plugin. Aunque he tocado las clases “tocables” (las que extienden de las clases base) el hecho de que éstas estén en la estructura de ficheros del plugin, denota que algo falla en el modelo de plugins de Symfony (al menos con este).

Es decir, bajo mi punto de vista no se debería tocar el plugin sino extenderlo fuera de él. Sin embargo, me cuesta pensar la manera de hacer esto en el contexto y la arquitectura actual de plugins (por lo menos con sfGuard). Espero que con los bundles de Symfony 2 la cosa cambie a mejor.

Para conseguir filtar por los campos del perfil de usuario, primero me he fijado cómo lo hace Propel en el formulario de edición que, al fin y al cabo, hace precisamente esto:

plugins/sfGuardPlugin/lib/form/sfGuardUserAdminForm.class.php

class sfGuardUserAdminForm extends BasesfGuardUserForm
{
  protected
    $pkName = null;
 
  public function configure()
  {
 
    [...]
 
    // profile form?
    $profileFormClass = sfConfig::get('app_sf_guard_plugin_profile_class', 'sfGuardUserProfile').'Form';
    if (class_exists($profileFormClass))
    {
      $profileForm = new $profileFormClass();
      unset($profileForm[$this->getPrimaryKey()]);
      unset($profileForm[sfConfig::get('app_sf_guard_plugin_profile_field_name', 'user_id')]);
 
      $this->mergeForm($profileForm);
    }
  }
 
  [...]
 
  protected function getPrimaryKey()
  {
    if (!is_null($this->pkName))
    {
      return $this->pkName;
    }
 
    $profileClass = sfConfig::get('app_sf_guard_plugin_profile_class', 'sfGuardUserProfile');
    if (class_exists($profileClass))
    {
      $tableMap = call_user_func(array($profileClass.'Peer', 'getTableMap'));
      foreach ($tableMap->getColumns() as $column)
      {
        if ($column->isPrimaryKey())
        {
          return $this->pkName = call_user_func(array($profileClass.'Peer', 'translateFieldname'), $column->getPhpName(), BasePeer::TYPE_PHPNAME, BasePeer::TYPE_FIELDNAME);
        }
      }
    }
  }
}

¿Qué estamos viendo aquí? Pues muy fácil: El método configure() busca en nuestro app.yml si hay referencia a una clase de perfil de usuario o si la de por defecto (sfGuardUserProfile) existe. Si es así, mergea su formulario y desactiva sus campos correspondientes al identificador de registro en ambas tablas. Sin más.

Si le echáis un vistazo a sfGuardUserFormFilter.class.php veréis que no hay nada de eso. Incorporando estos métodos y propiedades nuevas a nuestra clase sfGuardUserFormFilter bastaría para que los campos aparecieran en el formulario. La clase quedaría así:

plugins/sfGuardPlugin/lib/filter/sfGuardUserFormFilter.class.php

class sfGuardUserFormFilter extends BasesfGuardUserFormFilter {
 
    protected $pkName = null;
 
    public function configure() {
        unset($this['algorithm'], $this['salt'], $this['password']);
 
        // profile form?
        $profileFormClass = sfConfig::get('app_sf_guard_plugin_profile_class', 'sfGuardUserProfile') . 'FormFilter';
        if (class_exists($profileFormClass)) {
            $profileForm = new $profileFormClass();
            unset($profileForm[$this->getPrimaryKey()]);
            unset($profileForm[sfConfig::get('app_sf_guard_plugin_profile_field_name', 'user_id')]);
 
            $this->mergeForm($profileForm);
        }
 
        $this->widgetSchema['sf_guard_user_group_list']->setLabel('Groups');
        $this->widgetSchema['sf_guard_user_permission_list']->setLabel('Permissions');
    }
 
    protected function getPrimaryKey() {
        if (!is_null($this->pkName)) {
            return $this->pkName;
        }
 
        $profileClass = sfConfig::get('app_sf_guard_plugin_profile_class', 'sfGuardUserProfile');
        if (class_exists($profileClass)) {
            $tableMap = call_user_func(array($profileClass . 'Peer', 'getTableMap'));
            foreach ($tableMap->getColumns() as $column) {
                if ($column->isPrimaryKey()) {
                    return $this->pkName = call_user_func(array($profileClass . 'Peer', 'translateFieldname'), $column->getPhpName(), BasePeer::TYPE_PHPNAME, BasePeer::TYPE_FIELDNAME);
                }
            }
        }
    }
}

Como veis, me paso por el forro las convenciones de estilo de Symfony. Me parecen una aberración y además no me preguntaron cuando las impusieron, así que a mi plim. Además, con un ctrl+alt+f, cada cual que lo ponga a su manera.

Con esto ya podemos tener los campos en el formulario. Recomiendo un repaso a la sección filter del fichero generator.yml para retocar la presentación del formulario de filtrado. Por ahora el formulario provocará una excepción al procesarse porque Propel no sabrá aun cómo filtrar por los nuevos campos que hemos incluido.

Aquí es donde más dudas tengo, por que al igual que con sfGuard, soy novato aun con Propel 1.5. Seguramente haya alguna manera de hacer lo que voy a poner a continuación de algún modo más “nativo” de Propel 1.5 o elegante. Lo que tenemos que hacer es añadir una serie de métodos a la clase Query de sfGuardUser.

plugin/sfGuardPlugin/lib/model/sfGuardUserQuery.php

class sfGuardUserQuery extends BasesfGuardUserQuery {
    public function filterByFirstName($firstName) {
        if ($firstName['text'] != null) {
            $this->useUserProfileQuery()->filterByFirstName($firstName['text'])->endUse();
        }
    }
    public function filterByLastName($lastName) {
        if ($lastName['text'] != null) {
            $this->useUserProfileQuery()->filterByLastName($lastName['text'])->endUse();
        }
    }
    public function filterByEmail($email) {
        if ($email['text'] != null) {
            $this->useUserProfileQuery()->filterByEmail($email['text'])->endUse();
        }
    }
}

Gracias a la magia del método useUserProfileQuery() nuestro formulario de filtrado podrá procesarse y además aceptará wildcards como * y %.

Eso es todo. Tampoco es que sea la pera limonera, por que al final solo he copiado lo que hace el formulario de edición de manera nativa y lo he portado al de filtrado. La moraleja quizá es que cuando Google no te da la respuesta que buscas, puedes tener la suerte de tenerla a mano en tu propio código :)

Happy coding!


» Leer más, comentarios, etc...

Información legal y técnica