Friday, January 15, 2010

Modificar widgets defaults del módulo sfGuardUser (sfDoctrineGuardPlugin)

Sigo con la onda symfony pues estoy trabajando en un proyecto con el framework.

En este caso estaba usando el plugin sfDoctrineGuardPlugin el cúal supongo, si están leyendo estas líneas, conocen para que funciona.

En este caso tengo un formulario de registro de usuarios y requería que el campo para permisos del usuario, que por default es:

//lib/form/doctrine/sfDoctrineGuardPlugin/BasesfGuardUserForm.class.php
       ...
      'permissions_list' => new sfWidgetFormDoctrineChoiceMany(array('model' => 'sfGuardPermission'))
       ...


y su validator:
      ...
      'permissions_list' => new sfValidatorDoctrineChoiceMany(array('model' => 'sfGuardPermission', 'required' => false)),
      ...


cambiase la validación a un campo requerido, es decir
'required' => true.

Dado que la idea NO es modificar el plugin, de manera que se mantenga el código mantenible y el plugin actualizable (Principios básicos de calidad del software), intenté sobreescribir el validator desde la clase sfGuardUser.class.php, que es creada automáticamente en el directorio /lib/form/doctrine/sfDoctrineGuardPlugin. Procedí a añadir dentro de la función configure, la siguiente línea:

     $this->validatorSchema['permissions_list']->setOption('required', true);

Sin embargo esto no funciona. Ningún tipo de modificación que hiciera sobre la lista de widgets autogenerados (username, group_list, is_active, permissions_list, etc..) funciona.

Solución

Googleando y preguntando en los foros, encontré que la solución es hacer las modificaciones sobre el archivo sfGuardAdminUser.class.php en lugar de sfGuardUser.class.php. Este se encuentra en el directorio del plugin. Pueden copiarlo y agregarlo a la ubicación /lib/form/doctrine. De esta manera cualquier modificación realizada sobre alguno de los widgets SÍ tendrá efecto.

Una segunda opción:

Copiar el generator.yml  de
/plugins/sfDoctrineGuardPlugin/modules/sfGuardUser/config/generator.yml
a
/apps/tu_app/modules/sfGuardUser/config/generator.yml

Luego cambiar el form class en el generator.yml:

     form:
            class: sfGuardUserAdminForm


Crear una nueva clase form así:

//lib/form/doctrine
      class sfGuardMyUserAdminForm extends sfGuardUserAdminForm
     {
        public function configure()
       {
            $this->validatorSchema['permissions_list']->setOption('required', True);
       }
     }

Eso es todo:

Fuente:
-
Modify sfGuardUserForm fields


Monday, January 4, 2010

Symfony 1.2 Forms Autocompletion con Doctrine 1.0 (Parte I)

    El siguiente tutorial fue probado sobre una aplicación con Symfony 1.2.10 (mantenido hasta Febrero del 2010). Aunque no he realizado pruebas con Symfony 1.3 y 1.4 debería funcionar correctamente:

Primer Paso: Instalacion del Plugin sfFormExtraPlugin

     $ php symfony plugin:install sfFormExtraPlugin

     $ php symfony cc

    Con estos paso habremos instalado este plugin que agrega varios widgets útiles.

Segundo Paso: Trabajemos en el Backend con el formulario al cual le agregaremos un campo con autocomplete

    En el ejemplo a continuación tengo un formulario que representa el perfil de un Usuario y deseo agregar un campo de Estado (estados de Venezuela), para su dirección, que sea con autocomplete. En la base de datos existe un campo 'estado' dentro la tabla Usuario, que representa un foreign key al id de la tabla Estado.

//lib/form/doctrine/UsuarioForm.php

     <?php

     /**
      * Usuario form.
      *
      * @package    form
      * @subpackage Usuario
      * @author José A.
      */
     class UsuarioForm extends BaseUsuarioForm
     {
           public function configure()
          {
                ...

                $context = sfContext::getInstance();

               // Widget de Estado con Autocomplete
               $this->widgetSchema['estado']->setOption('renderer_class',
                    'sfWidgetFormJQueryAutocompleter');
               $this->widgetSchema['estado']->setOption('renderer_options', array(
                    'url' => $context->getController()->genUrl('@ajax_getstates')
                ));

                ...
           }
      }


    Acá primero se define una variable ($context), para determinar el contexto en donde se encuentra la instancia y que permitirá obtener la URL de la acción que symfony requiere para ejecutar el autocomplete.

    Posteriormente, se modifica el widget 'estado' tal como se indica en el tutorial de symfony make your choice, con la diferencia de que en este caso usaremos un widget distinto del sfFormExtraPlugin, pues el ORM es Doctrine y no Propel.

    En la opción 'renderer_class', asignaremos el widget sfWidgetFormJQueryAutocompleter. Luego en 'renderer_options' debemos especificar el atributo 'url', al cual a través de la variable $context, le asignaremos la URL '@ajax_getstates' de la acción  que crearemos más tarde.

Tercer Paso: Activación del campo en el Generator

    Para que el campo 'estado' se muestre en el formulario, debe ser activado en el archivo generator.yml

//apps/backend/modules/module/config/generator.yml

     ...

     form:
             class: UsuarioForm
             display:
                 Usuario:   [ username, password, password_again, permissions_list, estado]


     ...

Cuarto Paso: Crear la acción en el action.class

//apps/backend/modules/module/actions/action.class.php

    class moduleActions extends autoModuleActions
    {
      ...
      public function executeGetStates(sfWebRequest $request)
      {
           $this->getResponse()->setContentType('application/json');
          // Parametro 'q', contiene lo que fue introducido en el campo por teclado
          $string = $request->getParameter('q');

          // Consulta al modelo Estado
          $rows = Doctrine::getTable('Estado')->getStatesWith($string);

          $states = array();
          foreach ($rows as $row)
         {
             $states[$row->getId()] = $row->getNombre();
          }
   
          return $this->renderText(json_encode($states));
  
       }
      ...
    }


     Se utiliza JSON como formato de codificación para devolver los resultados de la acción. La acción es simple, pues sólo obtiene de la sesión el valor introducido por el usuario en el campo y lo utiliza para la consultar en el modelo. Luego cada uno de esos valores retornados por el modelo, son colocados en un arreglo que será codificado en formato JSON y devuelto como resultado.

Quinto Paso: Consulta en el modelo de la tabla Estado

//lib/model/doctrine/EstadoTable.php
     <?php
     ...
    class EstadoTable extends Doctrine_Table
    { 
         ...
        public function getStatesWith($string)
       {
            $query = Doctrine_Query::create()
               ->from('Estado e')
               ->where('LOWER(e.nombre) LIKE ?', '%'.$string.'%')
               ->orderBy('e.nombre ASC')
               ->execute();
                     
             return $query;
        }
    }


Sexto Paso: Agregar la acción en el routing.yml

//apps/backend/routing.yml
      ...
     ajax_getstates:
       url:   /getStates
       param:  { module: sfGuardUser, action: getStates }

    ...

     Como se aprecia, la etiqueta debe coincidir con el nombre de la URL que se colocó al widget 'Estado', es decir @ajax_getstates

Séptimo Paso
: Incluir los CSS y JS correspondientes al widget de autocomplete de sfFormExtraPlugin

Esto se puede hacer de dos formas diferentes:
a) Activando el CSS y Javascript para toda la aplicación backend.
Agregando esto en el archiv view.yml
//apps/backend/config/view.yml
      ...
     stylesheets:    [main.css, /sfFormExtraPlugin/css/jquery.autocompleter.css]
      javascripts:     [/js/jquery/jquery-1.3.js, /sfFormExtraPlugin/js/jquery.autocompleter.js]

     ...

b) Activando sólo para el módulo en donde es requerido
Agregando estás líneas en _form.php

//apps/backend/modules/module/templates/_form.php
      ...
     <?php use_javascript('jquery-1.3.2.min.js') ?>
     <?php use_javascript('/sfFormExtraPlugin/js/jquery.autocompleter.js') ?>
     <?php use_stylesheet('/sfFormExtraPlugin/css/jquery.autocompleter.css') ?>

      ...

     En este caso agregamos estas líneas al principio del partial _form.php. En ambas opciones lo que se hace es incluir el estilo del campo del campo de autocomplete, y los respectivos JavaScript para hacer el request.

     Con esto debería funcionar el autocomplete para el campo dado.



     Un único problema resta al momento de guardar el formulario. En el campo de Estado se guarda el id de éste en vez del nombre del Estado. Desafortunadamente el sfFormExtraPlugin, no incluye un widget específico de autocomplete para Doctrine como lo hace con Propel. En la segunda parte de este tutorial mostraré una posible solución al problema.

Referencias:
- symfonyguide (Francés)
- particul.es (Francés)
- symfony forum (Inglés)