jueves, 10 de mayo de 2012

Crear filtros con paginador en Symfony 1.4

Bien recientemente me vi en la necesidad de aplicar filtros a una lista paginada, bien el filtro funciona bien simpre y cuando no cambies de pagina en la lista. para solucionar esto realice algunos ajustes:
//Nota: el siguiente ej. es parte de un sistema de registro de actividades
en el action:
public function executeIndex(sfWebRequest $request)
  {
    $query = Doctrine_Core::getTable('Actividad')
            ->createQuery('a');
    $this->filtro = new ActividadFormFilter();
    if($request->isMethod('post')) {
        $this->filtro->bind($request->getParameter($this->filtro->getName()));
        if ($this->filtro->isValid()){
            $query =  $this->filtro->buildQuery($this->filtro->getValues());
        }
    }
    $this->pager = new sfDoctrinePager('Actividad',sfConfig::get('app_nb_results_per_page'));
    $this->pager->setQuery($query);
    $this->pager->setPage($request->getParameter('page', 1));
    $this->pager->init();
  }
app_nb_results_per_page se define en proy/apps/app/config/app.yml de la siguiente manera
all:
  nb_results_per_page: 50
en el template lo que sigue...
formulario del filtro:


Script:
 
vinculos del paginador:
<?php if ($pager->haveToPaginate()): ?>

<?php endif; ?>
Páginas :<?php echo $pager->getPage() ?> / <?php echo $pager->getLastPage()?>
Total de registros :<?php echo $pager->getNbResults() ?>

y para finalizar ajusto los widgets que necestio:


class ActividadFormFilter extends BaseActividadFormFilter
{
  public function configure()
  {
      
        //lista de widgets que quieres mostrar
      $this->wantedFields = array(
           'usuario_list',
           'fecha_inicio',
           'fecha_fin',
           'estatus_id',
           'centro_id',);
      foreach ($this as $fieldName => $widget){
        if (!in_array($fieldName, $this->wantedFields)){
            //unset a los widgets que no estan en el arreglo
            unset($this->widgetSchema[$fieldName]);
            unset($this->validatorSchema[$fieldName]);
        }
      }
        
      $years = range(date('Y'), 2010 );
      $this->widgetSchema['fecha_inicio'] = new sfWidgetFormFilterDate(array(
                 'from_date' =>new sfWidgetFormI18nDate(array('years' => array_combine($years, $years),
                                            'format' => '%day% %month% %year%',
                                            'culture' => 'es',
                                            'empty_values' => array('day' => '<- Día ->', 'month' => '<- Mes ->', 'year' => '<- Año ->'),
                                     )),
                 'to_date' => new sfWidgetFormI18nDate(array('years' => array_combine($years, $years),
                                            'format' => '%day% %month% %year%',
                                            'culture' => 'es',
                                            'empty_values' => array('day' => '<- Día ->', 'month' => '<- Mes ->', 'year' => '<- Año ->'),
                                     )),
                 'with_empty' => 0,                   // Para que no salga el checkbox
                 'template' => 'desde : %from_date% 
hasta :  %to_date%'
                ));
      $this->widgetSchema['fecha_fin'] = new sfWidgetFormFilterDate(array(
                 'from_date' =>new sfWidgetFormI18nDate(array('years' => array_combine($years, $years),
                                            'format' => '%day% %month% %year%',
                                            'culture' => 'es',
                                            'empty_values' => array('day' => '<- Día ->', 'month' => '<- Mes ->', 'year' => '<- Año ->'),
                                     )),
                 'to_date' => new sfWidgetFormI18nDate(array('years' => array_combine($years, $years),
                                            'format' => '%day% %month% %year%',
                                            'culture' => 'es',
                                            'empty_values' => array('day' => '<- Día ->', 'month' => '<- Mes ->', 'year' => '<- Año ->'),
                                     )),
                 'with_empty' => 1,                   // Para que salga el checkbox
                 'empty_label' =>'Sin Egreso',
                 'template' => 'desde : %from_date% 
hasta :  %to_date%'
                ));
     
     $this->widgetSchema['centro_id'] = new sfWidgetFormDoctrineChoice(array(
            'model'     => 'Centro',
            'add_empty' => '<- Seleccione Centro ->',
            'label' => 'Estado de Procedencia',
        ));
      
      
      $this->widgetSchema['usuario_list'] = new sfWidgetFormDoctrineChoice(
              array('multiple' => true,
                  'expanded' => true,
                  'model'=>  'Usuario',
                  'table_method'=>'getUsuariosByCoordinacion',
                  'key_method'=>'getId',
                  ));
      
      
      $this->widgetSchema->setLabels(array(
        'fecha_inicio'    =>'Fecha de inicio :',
        'fecha_fin' =>'Fecha de culminacion :',
        'tipo_actividad'    =>'Tipo de actividad :',
        'estatus_id'   =>'Estatus :',
        'lugar_actividad' =>'Lugarde la actividad :',
        'centro_id'    =>'Centro :',
        'recurso_humano' =>'Personas externas involucradas :',
        'institucion_list'    =>'Instituciones :',
        'usuario_list'   =>'Participantes :',
        'observacion_analista' =>'Observaciones del analista:',
        'descripcion'   =>'Titulo de la actividad :',
        'observacion' =>'Objetivo y Observaciones de la actividad:',
	));  
  }
}
Resumen: se crea el filtro como normalmente lo harias, al igual que el paginado, luego modificas los vinculos para navegar entre paginas eliminando el href y agregando onClick; agregas el javascript y listo!!
2 ejemplos en 1: filtros y paginador...
nota: al inicio de la clase para los filtros agregue una funcion muy util para hacer unset a los widgets que no necesitamos usar, hace un tiempo la consegui en un post y no he podido conseguirlo nuevamente... asi q ahi se los dejo...

lunes, 24 de octubre de 2011

sfDependentSelectPlugin en relaciones Muchos a Muchos M:N

Bien.. imaginemos que tenemos 2 tablas que se relacionan de M:N es decir, exite una tabla intermedia con las llaves primarias de las 2 tablas principales. en mi caso: centro y motivo_egreso y la tabla intermedia motivo_egreso_centro.


para usar sfDependentSelectPlugin y mostrar en mi caso, motivo de egreso y que se listen los centros asociados a través de una tabla intermedia , la única forma que conseguí de hacerlo es la siguiente: en lib/form/Doctrine/TuClaseForm.class.php
     public function configure()
     {
     ................. 
     ................. 
     ................. 
      $this->widgetSchema['motivo_egreso_id'] = new sfWidgetFormDoctrineChoice(array(
            'model'     => 'MotivoEgreso',
            'add_empty' => '<- Seleccione ->',
            'method' => 'getDescripcion',
            'label' => 'Motivo de Egreso :',
        ));

        $this->validatorSchema['motivo_egreso_id'] = new sfValidatorDoctrineChoice(array(
            'model' => 'MotivoEgreso',
            'column' => 'id',
            'required' => true
        ),array('required'=>'Campo requerido'));

        $this->widgetSchema['centro_id_egreso'] = new sfWidgetFormArrayDependentSelect(array(
            'callable' => array('CentroTable', 'getCentrosByMotivoEgreso'),
            'depends' => 'motivo_egreso_id',
            'add_empty' => '<- Seleccione ->',
        ));

         $this->validatorSchema['centro_id_egreso'] = new sfValidatorChoice(array(
            'choices' => CentroTable::getCentrosByMotivoEgreso(),
            'required' => true
        ),array('required'=>'Campo requerido'));
     ................. 
     ................. 
     ................. 
Luego defines el metodo getCentrosByMotivoEgreso() en mi caso en lib/model/doctrine/CentroTable.class.php
class CentroTable extends Doctrine_Table
{
    public static function getInstance()
    {
        return Doctrine_Core::getTable('Centro');
    }
     ................. 
     ................. 
     .................    
    
    public static function getCentrosByMotivoEgreso()
    {
        $motivos = Doctrine_Core::getTable('MotivoEgreso')
                    ->createQuery('m')
                    ->innerJoin('m.MotivoEgresoCentro mc')
                    ->innerJoin('mc.Centro c')
                    ->orderBy('m.id,c.id asc' )
                    ->execute();
        $arr = array();$arr_centro=array();
        foreach($motivos as $motivo){
            foreach($motivo->getCentro() as $centro)
                $arr_centro = $arr_centro + array($centro->getId() => $centro->getDescripcion());    
            $arr = $arr + array($motivo->getId()=>$arr_centro);
            $arr_centro=array();
        }
        return $arr;
    }
     ................. 
     ................. 
     ................. 
}
Bien, la idea que conseguí la obtuve de http://www.symfony-project.org/plugins/sfDependentSelectPlugin, en la seccion de "Using arrays only" decidí crear un array con la consulta que necesitaba para realizar los combos dependientes... repito es la única forma que conseguí de hacer eso, quizás exista una mejor manera, espero sus comentarios! Pd: el widget que deben usar en el form es sfWidgetFormArrayDependentSelect y ahi si utilizas el arreglo generado! Listo a probar...

lunes, 25 de julio de 2011

sfDependentSelectPlugin - problema con nombre de los campos

Cuando el combo superior no cumple con el estandar de nombre....

Ejemplo:
si tenemos un campo de referencia en una tabla que no cumple con el nombre estandar ej. direccion_id y por el contrario tenemos algo como receptor_direccion_id
los combos dependientes no funcionan.. al menos no a mi... hay q hacer 3 sencillos pasos...


1.- debes modificar 2 cosas proy/plugins/sfDependentSelectPlugin/sfWidgetFormDependentSelect.class

// en la funcion configure agregar una nueva opcion widget
protected function configure($options = array(), $attributes = array())
{
$this->addOption('depends', '');
$this->addOption('widget', '');//nueva opcion widget
$this->addOption('add_empty', true);
// ajax
$this->addOption('ajax', false);
$this->addOption('cache', true);
$this->addOption('params', array());
$this->addOption('url', sfContext::getInstance()->getController()->genUrl('sfDependentSelectAuto/_ajax'));
// source
$this->addRequiredOption('source_class', '');
$this->addOption('source_params', array());

parent::configure($options, $attributes);
}
2.- luego
//en la funcion render modificar la opcion 'dependiente'
public function render($name, $value = null, $attributes = array(), $errors = array()) 
{
.....
......
...........
$config = array(
'id'          => $this->generateId($name),
'opciones'    => $values,
'vacio'       => true === $this->getOption('add_empty') ? '' : $this->getOption('add_empty'),
'ajax'        => $this->getOption('ajax'),
'url'         => $this->getOption('url'),
'cache'       => $this->getOption('cache'),
'dependiente' => ($this->getOption('widget') == '' ? $this->getOption('depends') : $this->getOption('widget')),//validamos el valor q viene en la opcion
'varref'      => '_ds_ref',
'varsoloref'  => '_ds_get_ref_value',
'params'      => array_merge($this->getOption('params'), array(
'_ds_id'            => $this->generateId($name),
'_ds_source_class'  => $sourceClass,                                    
'_ds_source_params' => $this->getOption('source_params'),
)),            
);

.....
......
...........

return $widget . $script;
}
3.- por ultimo en /proy/lib/form/doctrine/CorrespondenciaForm.class agregas la opcion q acabas de crear... y listo! cuando no necesites
eso solo usas la opcion depends
$this->validatorSchema['receptor_direccion_id'] = new sfValidatorDoctrineChoice(array(
'model' => $this->getRelatedModelName('Direccion'),
'required' => true), array('required' => 'Campo Obligatorio'));

$this->widgetSchema['receptor_persona'] = new sfWidgetFormDoctrineDependentSelect(array(
'model'     => 'Persona',
'depends'   => 'Direccion',
'widget'   => 'correspondencia_receptor_direccion_id', // nombre del campo renderizado en el form (combo superior)
'add_empty' => '<- Seleccione receptor ->',
'method' => '__toString',
));

y listo!!

miércoles, 29 de junio de 2011

Unificar Documentos de identidad en un solo campo

Luego de varios intentos de registrar en BD documentos de identidad un buen amigo me paso un código que probablemente solucione el tema de unificar criterios en cuanto a números de documentos de identificación para una estructura de datos que contendrá información de personas.. Típico que necesitas registrar en tu aplicación datos personales, incluyendo documentos legales de identificación, normalmente podrías generar un solo campo tipo text para meter ahí lo que sea, o crear diferentes campos para guardar la info del documento de identidad (Cédula de identidad, pasaporte, etc)

Bien hace un tiempo me mandaron esta solución pero no sabia donde la había dejado, la volví a encontrar me pareció interesante y la voy a aplicar en un proyecto nuevo.. prueben este PHP y verifiquen lo generado, la idea es generar un único campo para guardar el dato de la identificación de la persona que se genera...
$str = "V158202995";
echo 'cadena enviada= '.$str;
$arr2 = str_split($str, 1);
foreach ($arr2 as $array):
       echo 'valor= '.$array.' codigo ascii= '.ord($array);
       $asci.=ord($array);
endforeach;
echo 'cadena resultante en codigo ascii= '.$asci;
$asci2 = str_split($asci, 2);
foreach ($asci2 as $array):
       echo 'valor en codigo ascii= '.$array.' valor normal= '.chr($array);
       $asc.=chr($array);
endforeach;
echo 'cadena reconstruida= '.$asc; 

el resultado de lo ejecutado sera asi:
cadena enviada= V158202995
valor= V codigo ascii= 86
valor= 1 codigo ascii= 49
valor= 5 codigo ascii= 53
valor= 8 codigo ascii= 56
valor= 2 codigo ascii= 50
valor= 0 codigo ascii= 48
valor= 2 codigo ascii= 50
valor= 9 codigo ascii= 57
valor= 9 codigo ascii= 57
valor= 5 codigo ascii= 53
cadena resultante en codigo ascii= 86495356504850575753
valor en codigo ascii= 86 valor normal= V
valor en codigo ascii= 49 valor normal= 1
valor en codigo ascii= 53 valor normal= 5
valor en codigo ascii= 56 valor normal= 8
valor en codigo ascii= 50 valor normal= 2
valor en codigo ascii= 48 valor normal= 0
valor en codigo ascii= 50 valor normal= 2
valor en codigo ascii= 57 valor normal= 9
valor en codigo ascii= 57 valor normal= 9
valor en codigo ascii= 53 valor normal= 5
cadena reconstruida= V158202995
Luego se guarda en BD el ascii generado... Podrias generar un metodo para hacer la transformacion y el reverso del numero y lo invocas antes de llamar al metodo save()

Y listo!

martes, 14 de junio de 2011

Exportar a Excel en Symfony

Típico que necesitas exportar a una hoja de calculo el resultado de alguna consulta..
bien, es simple, solo debes indicar en el action que no usaras el layout y que vas a cambiar el tipo de salida de la respuesta..
Primero, supongamos que vamos a enviar a una hoja de calculo nuestro Index, podriamos generar un action para realizar esta tarea.
En el index agragamos un vinculo a nuestra accion

<a class="ods" href="<?php echo url_for('item/indexExport') ?>">Exportar</a>

Y la accion seteamos el layout en false y modificamos el contentype del response...
public function executeIndexExport(sfWebRequest $request)
  {
   $this->items = Doctrine_Core::getTable('Item')
      ->createQuery('a')
      ->orderBy('a.categoria_id,a.descripcion')
      ->execute();
   $this->setLayout(false);
   $this->getResponse()->setContentType('application/msexcel');
   $this->setTemplate('index');
  }

luego... ejecutas, pruebas y guardas el archivo generado y listo!

lunes, 6 de junio de 2011

Comenzar un proyecto en Symfony 1.4 (part.2)

bien.. ya sabemos como comenzar un proyecto Symfony
Ahora veamos el resto de los pasos.. comencemos desde el inicio:
$ cd /var/www/
$ mkdir -p proy
$ cd proy/
$ mkdir -p lib/vendor
$ cd lib/vendor
$ tar zxpf /home/usuario/Escritorio/symfony-1.4.4.tgz 
$ mv symfony-1.4.4/ symfony
$ symfony -V
$ symfony generate:project proy
Verifica el post para crear el Comendo Symfony
PARA SEGURIDAD CONTRA ATAQUES XSS Y CSRF
symfony generate:app --escaping-strategy=on --csrf-secret=ClaveUnicaSecreta frontend
Ahora creamos el Virtual Host para nuestro proyecto proy

En el navegador
http://localhost.proy/
y para ver la aplicacion funcionando en modo desarrollo
http://localhost.proy/frontend_dev.php

Creando al estructura de datos
$ sudo su
# su - postgres
$ psql
postgres=#  create database proy_db with owner u_test;
postgres=#  CREATE TABLE persona
(
  id serial NOT NULL,
  nacionalidad character(1),
  ci numeric(10),
  nombre character varying(100) NOT NULL,
  apellido character varying(100) NOT NULL,
  sexo character(1),
  fecha_nac date,
  created_at timestamp without time zone,
  updated_at timestamp without time zone,
  CONSTRAINT persona_pkey PRIMARY KEY (id),
  CONSTRAINT persona_ci_key UNIQUE (ci)
);
ALTER TABLE persona OWNER TO u_test;
postgres=#  \q
$ exit
# exit
/// Estando nuevamente en /var/www/proy ///
$ symfony configure:database "pgsql:host=localhost;dbname=proy_db" u_test 123456
$ symfony doctrine:build-schema
//SI TIENES CAMPOS AUTO ADMINSTRABLES created_at updated_at
//debes ejecutar el comando para el schema
//buscar en el schema los campos created_at updated_at ELIMINALOS y colocar al inicio de la clase del schema actAs: { Timestampable: ~ }
Persona:
connection: doctrine
tableName: persona
actAs: { Timestampable: ~ }
columns:
id:.....
.....
luego ejecutas:
$ symfony doctrine:build --model
$ symfony doctrine:build --filters
$ symfony doctrine:build --forms
o en todo caso
$ symfony doctrine:build --all --no-confirmation
//modificar proy/lib/form/doctrine/PersonaForm.class.php y agregas lo siguiente dentro del metodo configure()
public function configure()
  {
 unset($this['created_at']);
        unset($this['updated_at']);
        .....
        ...
        ..
  }

De la sigueinte manera se crean CRUD (CREATE,RETRIEVE,UPDATE,DELETE) para los modulos de la aplicacion
// symfony doctrine:generate-module --with-show --non-verbose-templates aplicacion modulo Modelo
en nuestro caso el CRUD para la clase persona
$ symfony doctrine:generate-module --with-show --non-verbose-templates frontend persona Persona

y ahora a probar...
http://proy.localhost/frontend_dev.php/persona

Comenzar un proyecto en Symfony 1.4

Aqui dejo unos sencillos pasos para comenzar a configurar un proyecto con symfony 1.4 con ORM Doctrine

Descargar la version de symfony que desee utilizar
ej. symfony 1.4.4

Url de consulta
http://www.symfony-project.org/jobeet/1_4/Doctrine/es/01

Para descargar la version 1.4.4
http://www.symfony-project.org/installation/1_4
Descargar symfony-1.4.4.tgz

Crear proyecto
$ cd /var/www/
$ mkdir proy
$ cd /proy
$ mkdir -p lib/vendor
$ tar zxpf /home/usuario/Escritorio/symfony-1.4.4.tgz
$ mv symfony-1.4.4/ lib/vendor/symfony
Verifica la version y la configuracion
$ php lib/vendor/symfony/data/bin/symfony -V
$ php lib/vendor/symfony/data/bin/check_configuration.php

Para acceder al symfony q estamos instalando es necerio ejecutar
$ php lib/vendor/symfony/data/bin/symfony -- COMANDOS --
Para mejorar esto crearemos un comando en el shell:
$ vim $HOME/.bashrc
// o usas gedit
$ gedit $HOME/.bashrc

agregamos al final del archivo
alias symfony='./lib/vendor/symfony/data/bin/symfony'
Guardamos los cambios y vamos a OTRA CONSOLA.. para verificar los cambios
NOTA: esta linea de comando funciona solo para los proyectos q posean la carpeta lib/vendor/symfony

para probar el comando
$ cd /var/www/proy
$ symfony -V
Ahora puedes comenzar a hacer todo lo correspondiente con tu proyecto...

NOTA: la versión q se instala por defecto utiliza el ORM Doctrine