Desarrollo web Symfony2 y Sonata Project parte 2

De WikiSalud
Saltar a: navegación, buscar
Symfony Sonata05.jpg

veasé Desarrollo web con Symfony2 y Sonata Project

Contenido

Creando Usuario y base de datos de ejemplo

Abrir una consola como usuario root y escribir:

su postgres

Crear el usuario de la base de datos para la utilización del sistema

createuser -DRSP sonata_user
Nota: Contraseña del usuario s0n4t4

Crear la base de datos a utilizar

createdb sonata_db -O sonata_user

Como usuario normal, cargar el script de la base de datos, descargarlo del siguiente enlace Script base

tar zxvf Maestros.tar.gz
psql -U sonata_user -d sonata_db -f maestros.sql

A continuación se presenta el modelo físico de la base de datos a utilizar.

Modelo Físico Catálogos

Mapeo de la base de datos

Como primer paso se debe de cambiar la configuración del proyecto en symfony. Esto se realiza editando el archivo parameters.yml; se deben sustituirse por el siguiente contenido:

parameters:
    database_driver: pdo_pgsql
    database_host: 127.0.0.1
    database_port: null
    database_name: sonata_db
    database_user: sonata_user
    database_password: s0n4t4
    mailer_transport: smtp
    mailer_host: 127.0.0.1
    mailer_user: null
    mailer_password: null
    locale: es_SV
    debug_toolbar: true
    debug_redirects: false
    use_assetic_controller: true
    secret: Esta-clave-debe-generarse-como-se-describe-a-continuación
  • Para generar una cadena de caracteres para usar como secret ejecutar lo siguiente como usuario normal:
mktemp |sha1sum

Aparecerá una cadena como la que se muestra a continuación:

21d0fdeb3ba911c8af21ebef790241c5664347c1  -

Nótese que al final hay un espacio y un guión, esto no se usará como parte del secret. Por tanto, el secret sería 21d0fdeb3ba911c8af21ebef790241c5664347c1

Ahora se procede a crear el bundle a utilizar para realizar el mapeo. Ejecutar como usuario normal la siguiente instrucción:

php app/console generate:bundle

Generarlo con los siguientes datos:

  • Bundle namespace: Minsal/MaestrosBundle
  • Bundle name: MinsalMaestrosBundle
  • Target directory: Dirección que da por defecto
  • Configuration format: annotation
  • Do you want to generate the whole directory structure?: no
  • Do you confirm generation?: yes

Al finalizar la generación deberá aparecer un mensaje como el siguiente:

Generación del bundle


Ahora se procede a generar otro directorio en donde se harán las traducciones específicas de la aplicación. Generar un directorio llamado translations en la ruta src/Minsal/MaestrosBundle/Resources/ con la siguiente instrucción:

mkdir src/Minsal/MaestrosBundle/Resources/translations

Descargar el siguiente archivo Archivo de traducción. Descargarlo dentro del directorio del proyecto y ejecutar la siguiente instrucción:

tar zxvf SonataAdminBundle.tar.gz -C src/Minsal/MaestrosBundle/Resources/translations; rm SonataAdminBundle.tar.gz


Ahora ejecutar la siguiente instrucción:

php app/console doctrine:mapping:convert xml ./src/Minsal/MaestrosBundle/Resources/config/doctrine/metadata/orm --from-database --force

Deberá aparecer en el directorio src/Minsal/MaestrosBundle/Resource/config/doctrine/metadata/orm/ todos los xml referentes a las tablas que tenemos en la base de datos. Se eliminaran los xml relacionados a las tablas FosUserUser y FosUserGroup que son los relacionados a las entidades de los usuarios. Esto lo hacemos con la siguiente instrucción:

rm src/Minsal/MaestrosBundle/Resources/config/doctrine/metadata/orm/FosUser*

Ahora se generaran las entidades o clases referentes a la base de datos. Esto se realiza con la siguiente instrucción:

php app/console doctrine:mapping:import MinsalMaestrosBundle annotation

Con la siguiente instrucción se crearán los métodos get y set de las entidades:

php app/console doctrine:generate:entities MinsalMaestrosBundle

Se deben de eliminar las entidades FosUserUser y FosUserGroup; esto lo realizaremos con la siguiente instrucción:

rm src/Minsal/MaestrosBundle/Entity/FosUser*

Desde NetBeans, se realizará la sustitución global de estas entidades para que redirijan a las del Application/Sonata/UserBundle/User. Para ello presionar Ctrl+Shift+H, aparecerá una ventana en donde se hará la sustitución global. Se deben colocar los siguientes parámetros:

  • Containing Text: targetEntity="FosUserUser"
  • Replace With: targetEntity="Application\Sonata\UserBundle\Entity\User"
  • Scope: Current Project (NOMBRE DEL PROYECTO)

Como se muestra en la siguiente imagen. Generación del bundle

Presionar Continuar y aparecerá una ventana en donde se muestra las coincidencias, se debe de presionar el botón Replace # matches y se realizará el reemplazo.

Repetir el paso anterior con la siguiente busqueda

  • Containing Text: \Minsal\MaestrosBundle\Entity\FosUserUser
  • Replace With: \Application\Sonata\UserBundle\Entity\User
  • Scope: Current Project (NOMBRE DEL PROYECTO)

Generación del bundle

Ahora iniciaremos con la generación de los CRUD o mantenimientos de las entidades

Realizando el CRUD de una tabla padre

Generación de la clase Admin

Se realizará la generación del CRUD con el generador de sonata que viene incluido con la herramienta app/console. Para ello desde la consola ejecutar lo siguiente:

php app/console sonata:admin:generate

La primera pregunta es a que entidad se desea realizar el CRUD. Como ejemplo utilizaremos: CtlPais'. Escribir lo siguiente:

The fully qualified model class: Minsal/MaestrosBundle/Entity/CtlPais

Selección de Entidad

Lo siguiente es el bundle en donde se generá dicha clase Admin, presionar enter para dejar la opción por defecto

Selección del bundle

Nombre de la clase Admin, presionar enter para dejar la opción por defecto

Nombre de la clase Admin

Pregunta si se desea generar el controller, en este momento todavía no se generará así que solo presionar enter para dejar la opción por defecto que es no

Generación del controlador

Pregunta si se desea actualizar el servicio en formato YML, presionar enter para dejar la opción por defecto que es yes

Actualización del service

El nombre del archivo del servicio, presionar enter para dejar la opción por defecto

Nombre del service

El ID del servicio; si se desea se puede cambiar, en este caso presionar enter para dejar la opción por defecto

ID de la Entidad

Esperar un momento hasta que el generador devuelva el prompt y muestre algo similar a la siguiente imagen:

Selección de Entidad

Esto genera un directorio Admin dentro del bundle seleccionado, también se genera un archivo denominado services.yml en donde se tiene la configuración de la rutas de la clase admin. Debe quedar algo similar a lo que se muestra a continuación:

Selección de Entidad

Configuración de la clase Admin

Se procede a agregar en el archivo config.yml dicha configuración, eso se realiza agregando la siguiente instrucción dentro de la variable imports:

imports:
    #code...
    - { resource: @MinsalMaestrosBundle/Resources/config/services.yml }

Iniciar sesión en la aplicación y deberá aparecer algo similar a lo siguiente: Selección de Entidad

Explicación de los archivos generados

services.yml

services:
    minsal_maestros.admin.ctl_pais:
        class: Minsal\MaestrosBundle\Admin\CtlPaisAdmin
        arguments: [~, Minsal\MaestrosBundle\Entity\CtlPais, SonataAdminBundle:CRUD]
        tags:
            - {name: sonata.admin, manager_type: orm, group: admin, label: CtlPais}


  • minsal_maestros.admin.ctl_pais: Identificador del CRUD del sonata admin
  • class: Minsal\MaestrosBundle\Admin\CtlPaisAdmin: El nombre de la clase que se utiliza como clase Admin
  • arguments: [~, Minsal\MaestrosBundle\Entity\CtlPais, SonataAdminBundle:CRUD]: Contiene dos elementos importantes:
    • Minsal\MaestrosBundle\Entity\CtlPais: Entidad que se utiliza
    • SonataAdminBundle:CRUD: Nombre del controlador que se utiliza para manejar la clase admin
  • {name: sonata.admin, manager_type: orm, group: admin, label: CtlPais}: Contiene 2 elementos que resaltar:
    • group: Nombre principal que aparece como padre en el menu.
    • label: Label que aparece en la opción del menu

CtlPaisAdmin.php

Posee las siguientes grandes áreas:

<?php
 
namespace Minsal\MaestrosBundle\Admin;
 
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
 
class CtlPaisAdmin extends Admin
{
    /**
     * @param DatagridMapper $datagridMapper
     */
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
           ...............
    }
 
    /**
     * @param ListMapper $listMapper
     */
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ..................
            ->add('_action', 'actions', array(
                'actions' => array(
                    'show' => array(),
                    'edit' => array(),
                    'delete' => array(),
                )
            ))
        ;
    }
 
    /**
     * @param FormMapper $formMapper
     */
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ....................
        ;
    }
 
    /**
     * @param ShowMapper $showMapper
     */
    protected function configureShowFields(ShowMapper $showMapper)
    {
        $showMapper
          ........................
        ;
    }
}
  • Lo primero que aparece en la clase es la parte de los USE que se utilizan para generar los filtros, formularios, el listar y el mostrar.
  • Luego se declara cada una de las partes que se detallan a continuación:
    • protected function configureDatagridFilters(DatagridMapper $datagridMapper): este contiene aquellos parámetros que se utilizan para filtrar el listar.
    • protected function configureListFields(ListMapper $listMapper): contiene los atributos a mostrar en la vista listar.
    • protected function configureFormFields(FormMapper $formMapper): contiene los campos a mostrar en el formulario de ingreso y edición.
    • protected function configureShowFields(ShowMapper $showMapper): contiene los campos a mostrar en la vista de detalle.

Tipos de datos para los formularios

Cuando se esta ingresando un formulario se debe de configurar que campos se necesitan mostrar y que otros no deben de salir. En el CRUD de Sonata Admin, el formulario aparece como se muestra en la siguiente figura:

Formulario de Ingreso

Como se muestra en la imagen, hay campos que se pueden generar automáticamente como lo es el ID, Fecha y Hora del Registro y Modificación y el Usuario que ingresa y Modfica'. También se deben de cambiar los label referentes a los campos. Para esto se debe de modificar el configureFormFields.

Dentro de la función configureFormFields se encuentra una variable denominada $formMapper, esta variable es la que construye el formulario y determina las propiedades que este tendrá. Para ir agregando los campos a mostrar se utiliza el tag ->add(....) con las siguientes propiedades:

->add('NOMBRE DEL CAMPO','TIPO DE CAMPO',array('ATRIBUTOS'=>'VALOR'))

A continuación se procede a modificar el formMapper de la entidad CtlPais

 $formMapper
                ->add('nombre', 'text', array(
                    'label' => 'Escriba el nombre del pais',
                    'max_length' => 150,
                    'required' => true
                ))
                ->add('isonumero', null, array(
                    'label' => 'Escriba el ISO 3166-1',
                    'required' => true
                ))
                ->add('dominio2', 'text', array(
                    'label' => 'Abreviatura según ISO 3166-1 alfa-2',
                    'max_length' => 2,
                    'required' => false
                ))
                ->add('dominio3', 'text', array(
                    'label' => 'Abreviatura de dominio3 según ISO 3166-1 alfa-3',
                    'max_length' => 3,
                    'required' => false
                ))
                ->add('activo', null, array(
                    'required' => false
                ))
        ;

Con este cambio el formulario quedará de la siguiente forma:

Formulario Actualizado

Nota: Visitar los siguientes enlaces: Tipos de Datos de Symfony y Otros tipos de datos

Opciones prePersist y preUpdate

El siguiente paso sería verificar si hay campos que hay que asignarle antes de una inserción o actualización. En este caso dos campos que hay que asignarle antes de la inserción son: La fecha y Hora de actualización y el usuario que lo esta haciendo. Para esto utilizaremos la función de prePersist que recibe como párametro la entidad que se esta insertando con los valores que trae del formulario. Así que se debe de agregar la siguiente función antes de la llave de cierre de la clase.

    /*
     * Método que se ejecuta antes de realizar una inserción.
     * Recibe como parámetro una entidad; en este caso de tipo CtlPais
     * con los valores del formulario.
     * 
     */
 
    public function prePersist($pais) {
        $user = $this->getConfigurationPool()->getContainer()->get('security.context')->getToken()->getUser();
        $pais->setIdUsuarioReg($user);
        $pais->setFechaHoraReg(new \DateTime());
    }
 
     /*
     * Método que se ejecuta antes de realizar una actualización.
     * Recibe como parámetro una entidad; en este caso de tipo CtlPais
     * con los valores del formulario.
     * 
     */
    public function preUpdate($pais) {
        $user = $this->getConfigurationPool()->getContainer()->get('security.context')->getToken()->getUser();
        $pais->setIdUsuarioMod($user);
        $pais->setFechaHoraMod(new \DateTime());
    }

Con las instrucciones anteriores lo que se esta realizando es setearle el usuario y la fecha tanto para la creación como la actualización.

Validaciones

Al momento de hacer las validaciones pueden hacerse bajo tres momentos:

  1. En la entidad
  2. En la clase admin
  3. Javascript

Las validaciones con la entidad se realiza igual como se explicaba en el articulo Desarrollo web Symfony2 parte 5, Sección 2.2 o el Sitio Oficial; al igual que con Javascript como se explica en el mismo articulo.

Para realizar las validaciones con la clase Admin, el primer paso es agregar el siguiente use a la clase CtlPaisAdmin.php:

use Sonata\AdminBundle\Validator\ErrorElement;

A continuación se procede a agregarle un método denominado validate, debe contener el siguiente código:

public function validate(ErrorElement $errorElement, $pais) {
 
        $repetido = $this->getModelManager()
                ->findBy('MinsalMaestrosBundle:CtlPais', array('isonumero' => $pais->getIsonumero()));
 
        if (count($repetido) > 0) {
            $errorElement->with('isonumero')
                    ->addViolation('Este número ya esta ingresado, debe de digitar otro')
                    ->end();
        }
        if (!is_null($pais->getDominio2())) {
            $repetido = $this->getModelManager()
                    ->findBy('MinsalMaestrosBundle:CtlPais', array('dominio2' => $pais->getDominio2()));
 
            if (count($repetido) > 0) {
                $errorElement->with('dominio2')
                        ->addViolation('Este dominio ya ha sido utilizado')
                        ->end();
            }
        }
        if (!is_null($pais->getDominio3())) {
            $repetido = $this->getModelManager()
                    ->findBy('MinsalMaestrosBundle:CtlPais', array('dominio3' => $pais->getDominio3()));
 
            if (count($repetido) > 0) {
                $errorElement->with('dominio3')
                        ->addViolation('Este dominio ya ha sido utilizado')
                        ->end();
            }
        }
        $errorElement->with('nombre')
                ->assertLength(array('min' => 5, 'minMessage' => 'El nombre debe de contener más de 5 carácteres',
                    'max' => 150, 'maxMessage' => 'El nombre debe de contener menos de 150 caractéres'
                ))
                ->end();
    }

Al ingresar los valores y dar clic en el botón Crear y Editar se deben de generar los error que se muestran a continuación: Error de formulario Error de formulario

Llenar el formulario con los siguientes valores:

  • Escriba el nombre del pais:Mi nuevo Pais
  • Escriba el ISO 3166-1: 895
  • Abreviatura según ISO 3166-1 alfa-2: np
  • Abreviatura de dominio3 según ISO 3166-1 alfa-3: mnp

Presionar el botón de Crear y Regresar el Listado y deberá aparecer la lista como se muestra en la siguiente imagen: Listar pais

Insertar Formularios con relación Uno a Uno Bidireccional

En la siguiente sección se describe cómo insertar un formulario dentro de otro con el Bundle de Sonata SonataAdminBundle. La incrustación se realizará para relaciones Uno a Uno y Uno a Muchos.

Tablas y relaciones de la base de datos

Las tablas que se utilizaran son las siguientes:

  1. Tabla padre ctl_dependencia_establecimiento
  2. Tabla hija detalle_dependencia_establecimiento
Tabla «public.ctl_dependencia_establecimiento»
       Columna        |            Tipo             | Modificadores 
----------------------+-----------------------------+---------------
 id                   | INTEGER                     | NOT NULL
 id_dependencia       | INTEGER                     | 
 id_dependencia_padre | INTEGER                     | 
 id_establecimiento   | INTEGER                     | 
 fecha_inicio         | DATE                        | NOT NULL
 fecha_fin            | DATE                        | 
 habilitado           | BOOLEAN                     | 
 fecha_hora_reg       | TIMESTAMP WITHOUT TIME zone | 
 fecha_hora_mod       | TIMESTAMP WITHOUT TIME zone | 
 id_usuario_reg       | INTEGER                     | NOT NULL
 id_usuario_mod       | INTEGER                     | 
Índices:
    "pk_ctl_dependencia_establecimiento" PRIMARY KEY, btree (id)
    "fki_establecimiento_dependencia_establecimiento" btree (id_establecimiento)
    "idx_bc5984322bf76b46" btree (id_dependencia_padre)
    "idx_bc5984327dfa12f6" btree (id_establecimiento)
    "idx_bc598432ca22cd3f" btree (id_dependencia)
Restricciones de llave FORánea:
    "fk_bc5984322bf76b46" FOREIGN KEY (id_dependencia_padre) REFERENCES ctl_dependencia(id)

Se procede a agregar la tabla detalle_dependencia_establecimiento. La cual no esta en el esquema actual. Para ello como usuario normal ejecutar

psql BASE_DATOS USUARIO_BASE

Y ejecutar lo siguiente

CREATE SEQUENCE detalle_dependencia_establecimiento_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;
 
CREATE TABLE detalle_dependencia_establecimiento (
    id INTEGER DEFAULT NEXTVAL('detalle_dependencia_establecimiento_id_seq'::regclass) NOT NULL,
    id_unidad INTEGER NOT NULL,
    id_emp_jefe INTEGER,
    id_emp_responsable INTEGER,
    telefono CHARACTER VARYING(15),
    descripcion CHARACTER VARYING(255)
);
 
CREATE SEQUENCE detalle_unidad_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;
 
ALTER SEQUENCE detalle_unidad_id_seq OWNED BY detalle_dependencia_establecimiento.id;
 
SELECT pg_catalog.SETVAL('detalle_unidad_id_seq', 1, TRUE);
 
ALTER TABLE ONLY detalle_dependencia_establecimiento
    ADD CONSTRAINT pk_detalle_unidad PRIMARY KEY (id);
 
ALTER TABLE ONLY detalle_dependencia_establecimiento
    ADD CONSTRAINT fk_detalle_dependencia_establecimiento FOREIGN KEY (id_unidad) REFERENCES ctl_dependencia_establecimiento(id) ON UPDATE CASCADE ON DELETE RESTRICT;

Generar la entidad correspondiente a la nueva tabla dentro de la base que fue cargada con el comando anterior.

php app/console doctrine:mapping:convert xml src/Minsal/MaestrosBundle/Resources/config/doctrine/metadata/orm/ --from-database --force
php app/console doctrine:mapping:import MinsalMaestrosBundle annotation

Eliminar la metadata y las entidades de Fosuser

rm src/Minsal/MaestrosBundle/Resources/config/doctrine/metadata/orm/FosUser*
rm src/Minsal/MaestrosBundle/Entity/FosUser*

Generar los metodos de get y set para la nueva entidad

php app/console doctrine:generate:entities MinsalMaestrosBundle --no-backup

Configuración de las Entidades

Lo primero que hay que realizar es modificar las entidades padres e hijas que que se encuentran dentro del directorio src/Minsal/MaestrosBundle/Entity/ para el ejemplo se tomará como referencia la entidad CtlDependenciaEstablecimiento como entidad padre y la entidad DetalleDependenciaEstablecimiento como entidad hija.

  • Configuración Entidad padre CtlDependenciaEstablecimiento.php

Agregar las siguientes linea después del ultimo atributo creado y antes de la primer función de get y set. Con ello se crea el mapeo uno a uno con la tabla hija.

    /**
     * @ORM\OneToOne(targetEntity="DetalleDependenciaEstablecimiento", mappedBy="idUnidad", cascade={"all"}, orphanRemoval=true)
     *
     */
    private $detalleDependenciaEstablecimiento;


Agregar el método __toString() antes de la llave de cierre de la entidad:

   public function __toString() {
        return $this->idDependencia ? (string) $this->idDependencia : ''; 
    }

En donde:

  • targetEntity: es el nombre de la Entity Hija, para el ejemplo es DetalleDependenciaEstablecimiento.
  • mappedBy: nombre del atributo dentro de la Entidad Hija la cual es la llave foránea dentro de la Entidad, es de tener claro que este atributo no es el nombre del atributo dentro de la base de datos, sino, dentro de la Entidad archivo .php


  • Configuración Entidad Hija DetalleDependenciaEstablecimiento.php

Verificar las siguientes líneas referentes a la llave foránea, no es necesario crearlas debido a que cuando se realiza el mapeo doctrine la genera, es necesario verificar que la relación sea OneToOne, si dice ManyToOne, cambiarlo a OneToOne, además es necesario agregar la sentencia inversedBy tal y como se muestra a continuación:

     /**
     * @var \CtlDependenciaEstablecimiento
     *
     * @ORM\OneToOne(targetEntity="CtlDependenciaEstablecimiento", inversedBy="detalleDependenciaEstablecimiento")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="id_unidad", referencedColumnName="id")
     * })
     */
    private $idUnidad;

En donde:

  • targetEntity: es el nombre de la Entity Padre, para el ejemplo es CtlDependenciaEstablecimiento
  • inverseBy: nombre del atributo dentro de la Entidad Padre a la cual hace referencia la llave foránea, es de tener claro que este atributo no es el nombre del atributo dentro de la base de datos, sino, dentro de la Entidad archvio .php
  • name: Nombre del Atributo (definido dentro de la Base de Datos), dentro de la tabla Hija que es la llave foránea y que servirá para hacer el join dentro de ambas tablas.
  • referencedColumnName: Nombre del atributo de la tabla padre que es llave primaria y que servirá para hacer join entre las tablas.


Agregar el metodo __toString a la entidad CtlEstablecimiento.php

CtlEstablecimiento.php
public function __toString() {
        return $this->nombre ? $this->nombre : '';
}

Actualizar los getter y setters de ambas entidades con los siguientes comandos:

php app/console doctrine:generate:entities MinsalMaestrosBundle:CtlDependenciaEstablecimiento --no-backup
php app/console doctrine:generate:entities MinsalMaestrosBundle:DetalleDependenciaEstablecimiento --no-backup

Verificar que se hayan creado los getters y setters para las entidad padre. Y proceder al cambio del FosUseUser al User realizado en la parte de Mapeo de la base de datos .

Creación de las clases Admin

Crear las clases Admin para ambas Entidades con el comando:

php app/console sonata:admin:generate

Como se realizo en la parte Generación de la clase Admin de este artículo. Debe de quedar algo similiar a las siguientes imagenes: Entidad CtlDependenciaEstablecimiento Entidad CtlDetalleDependenciaEstablecimiento

  • Modificacion de DetalleDependenciaEstablecimientoAdmin.php
<?php
 
namespace Minsal\MaestrosBundle\Admin;
 
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Doctrine\ORM\EntityRepository;
 
class DetalleDependenciaEstablecimientoAdmin extends Admin
{
    /**
     * @param DatagridMapper $datagridMapper
     */
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
    }
 
    /**
     * @param ListMapper $listMapper
     */
    protected function configureListFields(ListMapper $listMapper)
    {
    }
 
    /**
     * @param FormMapper $formMapper
     */
    protected function configureFormFields(FormMapper $formMapper)
    {        
        $formMapper
            ->add('telefono',null,array('label' => 'Teléfono','help'=>'Formato: #### - ####'))
            ->add('descripcion')
        ;
    }
 
    /**
     * @param ShowMapper $showMapper
     */
    protected function configureShowFields(ShowMapper $showMapper)
    {
    }
}
  • Modificar el archivo CtlDependenciaEstablecimientoAdmin.php

Para la clase padre CtlDependenciaEstablecimientoAdmin.php se debe de agregar el siguiente código el que permitirá la inserción del formulario

<?php
 
namespace Minsal\MaestrosBundle\Admin;
 
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Doctrine\ORM\EntityRepository;
 
class CtlDependenciaEstablecimientoAdmin extends Admin
{
    /**
     * @param DatagridMapper $datagridMapper
     */
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('idEstablecimiento',null,array('label'=>'Establecimiento'))
            ->add('idDependencia',null,array('label'=>'Dependencia'))
            ->add('idDependenciaPadre',null,array('label'=>'Unidad de la que Depende'))
            ->add('fechaInicio',null,array('label'=>'Fecha Inicial'))
            ->add('fechaFin',null,array('label'=>'Fecha Final'))
            ->add('habilitado')
        ;
    }
 
    /**
     * @param ListMapper $listMapper
     */
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->add('idEstablecimiento',null,array('label'=>'Establecimiento','route'=>array('name'=>'show')))
            ->addIdentifier('idDependencia',null,array('label'=>'Dependencia','route'=>array('name'=>'show')))
            ->add('idDependenciaPadre',null,array('label'=>'Unidad de la que Depende'))
            ->add('fechaInicio',null,array('label'=>'Fecha Inicial'))
            ->add('fechaFin',null,array('label'=>'Fecha Final'))
            ->add('habilitado')
        ;
    }
 
    /**
     * @param FormMapper $formMapper
     */
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('idEstablecimiento',null,array('label'=>'Establecimiento'))
            ->add('idDependencia',null,array('label'=>'Dependencia'))
            ->add('idDependenciaPadre',null,array('label'=>'Unidad de la que Depende'))
            ->add('fechaInicio',null,array('label'=>'Fecha Inicial'))
            ->add('fechaFin',null,array('label'=>'Fecha Final'))
            ->add('habilitado')
            ->add('detalleDependenciaEstablecimiento', 'sonata_type_admin', array('label'=>'Informacion de la Dependencia'), array('edit' => 'inline'))
        ;
    }
 
    /**
     * @param ShowMapper $showMapper
     */
    protected function configureShowFields(ShowMapper $showMapper)
    {
        $showMapper
            ->add('idEstablecimiento.nombre',null,array('label'=>'Establecimiento'))
            ->add('idDependencia',null,array('label'=>'Dependencia'))
            ->add('idDependenciaPadre.nombre',null,array('label'=>'Unidad de la que Depende'))
            ->add('habilitado',null,array('label'=>'Habilitado'))
            ->add('detalleDependenciaEstablecimiento.telefono',null,array('label' =>'Teléfono'))
            ->add('detalleDependenciaEstablecimiento.descripcion',null,array('label' =>'Descripcion'))
        ;
    }
 
    public function prePersist($depestab) {
        $user = $this->getConfigurationPool()->getContainer()->get('security.context')->getToken()->getUser();
        $depestab->setIdUsuarioReg($user);
        $depestab->setFechaHoraReg(new \DateTime());
    }
 
    public function preUpdate($depestab) {
        $user = $this->getConfigurationPool()->getContainer()->get('security.context')->getToken()->getUser();
        $depestab->setIdUsuarioMod($user);
        $depestab->setFechaHoraMod(new \DateTime());
    }
}

En donde:

  • detalleDependenciaEstablecimiento: Es el nombre del atributo declarado en la entidad padre, creado en el paso 2.
  • sonata_type_admin: Tipo de formulario declarado, este es útil para las relaciones de uno a uno, debido a que deja el manejo del formulario al admin class de la entidad hija.
  • array('edit'=>'inline'): con este comando se declara que la edición del formulario que se esta incrustando se realizará en la misma línea y se mostrará como otro formulario.

Configurando el Service

Abrir el archivo service.yml que se encuentra en el directorio src/Minsal/MaestrosBundles/Resource/config/ . Y agregar las siguientes líneas:

    minsal_maestros.admin.ctl_dependencia_establecimiento:
        class: Minsal\MaestrosBundle\Admin\CtlDependenciaEstablecimientoAdmin
        arguments: [~, Minsal\MaestrosBundle\Entity\CtlDependenciaEstablecimiento, SonataAdminBundle:CRUD]
        tags:
            - {name: sonata.admin, manager_type: orm, group: Administracion, label: Estructura Organica}
 
    minsal_maestros.admin.detalle_dependencia_establecimiento:
        class: Minsal\MaestrosBundle\Admin\DetalleDependenciaEstablecimientoAdmin
        arguments: [~, Minsal\MaestrosBundle\Entity\DetalleDependenciaEstablecimiento, SonataAdminBundle:CRUD]
        tags:
            - {name: sonata.admin, manager_type: orm, show_in_dashboard: false}

El parámetro show_in_dashboard: false, permite que omitir dentro del menu la clase admin declarada.

Configurando el prePersist

Para que la inserción se realice de manera exitosa es necesario crear una función llamada prePersist que permitirá setear el valor del atributo detalleDependenciaEstablecimiento, con lo que se permitirá establecer el valor de la llave foránea idUnidad de la entidad hija.

Para poder setear el valor es necesario editar la función, quedando de la siguiente manera.

<?php
//code ...
 
class CtlDependenciaEstablecimientoAdmin extends Admin
{
    //code…
 
    public function prePersist($depestab) {
        $detalle = $depestab->getdetalleDependenciaEstablecimiento();
        $detalle->setIdUnidad($depestab);
 
        //code...
    }
}

Administrando las rutas de la clase DetalleDependenciaEstablecimiento

Las siguientes lineas eliminaran las rutas de delete, list, edit y create, para que no puedan ser accedidos por el usuario y solamente por el admin.

<?php
//code ...
use Sonata\AdminBundle\Route\RouteCollection;
 
class DetalleDependenciaEstablecimientoAdmin extends Admin
{
    //code...
 
    protected function configureRoutes(RouteCollection $collection)
    {
        $collection->clear();
    }
}

Insertar Formularios con relación Uno a Muchos Bidireccional

Tablas y relaciones de la base de datos

Para esta parte se utilizaran las tablas ctl_catalogo y ctl_catalogo_detalle'

Tabla «public.ctl_catalogo»
    Columna     |            Tipo             | Modificadores 
----------------+-----------------------------+---------------
 id             | INTEGER                     | NOT NULL
 nombre         | CHARACTER VARYING(150)      | 
 descripcion    | CHARACTER VARYING(150)      | 
 fecha_hora_reg | TIMESTAMP WITHOUT TIME zone | 
 fecha_hora_mod | TIMESTAMP WITHOUT TIME zone | 
 id_usuario_reg | INTEGER                     | NOT NULL
 id_usuario_mod | INTEGER                     | 
Índices:
    "pk_ctl_catalogo" PRIMARY KEY, btree (id)
Restricciones de llave FORánea:
    "fk_fos_user_user_mod_canton" FOREIGN KEY (id_usuario_mod) REFERENCES fos_user_user(id) ON UPDATE CASCADE
    "fk_fos_user_user_reg_canton" FOREIGN KEY (id_usuario_reg) REFERENCES fos_user_user(id) ON UPDATE CASCADE
Referenciada por:
    TABLE "ctl_catalogo_detalle" CONSTRAINT "fk_6d553c5cb77787d0" FOREIGN KEY (id_catalogo) REFERENCES ctl_catalogo(id)
 
 
Tabla «public.ctl_catalogo_detalle»
    Columna     |            Tipo             | Modificadores 
----------------+-----------------------------+---------------
 id             | INTEGER                     | NOT NULL
 descripcion    | CHARACTER VARYING(100)      | 
 id_catalogo    | INTEGER                     | 
 fecha_hora_reg | TIMESTAMP WITHOUT TIME zone | 
 fecha_hora_mod | TIMESTAMP WITHOUT TIME zone | 
 id_usuario_reg | INTEGER                     | NOT NULL
 id_usuario_mod | INTEGER                     | 
Índices:
    "pk_ctl_catalogo_detalle" PRIMARY KEY, btree (id)
Restricciones de llave FORánea:
    "fk_6d553c5cb77787d0" FOREIGN KEY (id_catalogo) REFERENCES ctl_catalogo(id)
    "fk_fos_user_user_mod_canton" FOREIGN KEY (id_usuario_mod) REFERENCES fos_user_user(id) ON UPDATE CASCADE
    "fk_fos_user_user_reg_canton" FOREIGN KEY (id_usuario_reg) REFERENCES fos_user_user(id) ON UPDATE CASCADE
Referenciada por:
    TABLE "ctl_establecimiento" CONSTRAINT "fk_332bd42c8f14b160" FOREIGN KEY (id_cat_tipo_consumo) REFERENCES ctl_catalogo_detalle(id)
    TABLE "ctl_establecimiento" CONSTRAINT "fk_332bd42c952e1fd4" FOREIGN KEY (id_cat_tipo_farmacia) REFERENCES ctl_catalogo_detalle(id)
    TABLE "ctl_establecimiento" CONSTRAINT "fk_332bd42c9e67d4be" FOREIGN KEY (id_cat_tipo_expediente) REFERENCES ctl_catalogo_detalle(id)
    TABLE "ctl_establecimiento" CONSTRAINT "fk_332bd42ccc3ee19e" FOREIGN KEY (id_cat_pruebas) REFERENCES ctl_catalogo_detalle(id)
    TABLE "ctl_establecimiento" CONSTRAINT "fk_332bd42cf92045c1" FOREIGN KEY (id_cat_nivel_minsal) REFERENCES ctl_catalogo_detalle(id)

Configuración de las Entidades

Lo primero que hay que realizar es modificar las entidades padres e hijas que se encuentran dentro del directorio src/Minsal/MaestrosBundle/Entity/ para el ejemplo se tomará como referencia la entidad CtlCatalogo como entidad padre y la entidad CtlCatalogoDetalle como entidad hija.

  • Configuración Entidad Padre CtlCatalogo.php

Agregar las siguientes lineas despues del ultimo atributo creado y antes de la primer función de get y set. Con ello se crea el mapeo uno a muchos con la tabla hija.

<?php
 
namespace Minsal\MaestrosBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
 
/**
 * CtlCatalogo
 *
 * @ORM\Table(name="ctl_catalogo", ...)
 * @ORM\Entity
 */
class CtlCatalogo
 
{
 
    /* code... */
 
    /**
     *
     * @ORM\OneToMany(targetEntity="CtlCatalogoDetalle", mappedBy="idCatalogo", cascade={"all"}, orphanRemoval=true)
     *
     */
    private $catalogoDetalle;
 
 
    /* code ... */
}

En donde:

  • targetEntity: es el nombre de la Entity Hija, para el ejemplo es CtlCatalogoDetalle.
  • MappedBy: nombre del atributo dentro de la Entidad Hija la cual es la llave foránea dentro de la Entidad, es de tener claro que este atributo no es el nombre del atributo dentro de la base de datos, sino, dentro de la Entidad archivo .php


  • Configuración Entidad Hija CtlCatalogoDetalle.php

Verificar las siguientes líneas referentes a la llave foránea, no es necesario crearlas debido a que cuando se realiza el mapeo doctrine la genera, es necesario verificar que la relación sea ManyToOne, la declaración agregada ha sido inversedBy , el cual se explica a continuación:

<?php
 
namespace Minsal\MaestrosBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
 
/**
 * CtlCatalogoDetalle
 *
 * @ORM\Table(name="ctl_catalogo_detalle", ...)
 * @ORM\Entity
 */
class CtlCatalogoDetalle
 
 
{
 
    /* code... */
 
    /**
     * @var \CtlCatalogo
     *
     * @ORM\ManyToOne(targetEntity="CtlCatalogo", inversedBy="catalogoDetalle")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="id_catalogo", referencedColumnName="id")
     * })
     */
    private $idCatalogo;
 
 
    /* code ... */
}


En donde:

  • targetEntity: es el nombre de la Entity Padre, para el ejemplo es CtlCatalogo.
  • inverseBy: nombre del atributo dentro de la Entidad Padre a la cual hace referencia la llave foránea, es de tener claro que este atributo no es el nombre del atributo dentro de la base de datos, sino, dentro de la Entidad archivo .php (El cual fue agregado en la Configuracion de la Entidad Padre).
  • name: Nombre del Atributo (definido dentro de la Base de Datos), dentro de la tabla Hija que es la llave foránea y que servirá para hacer el join dentro de ambas tablas.
  • referencedColumnName: Nombre del atributo de la tabla padre que es llave primaria y que servirá para hacer join entre las tablas.


Actualizar los getter y setters de la entidad Padre con el siguiente comando:

php app/console doctrine:generate:entities MinsalMaestrosBundle:CtlCatalogo --no-backup

Verificar que se hayan creado los siguientes métodos en la entidad Padre:

<?php
 
namespace Minsal\MaestrosBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
 
/**
 * CtlCatalogo
 *
 * @ORM\Table(name="ctl_catalogo", ...)
 * @ORM\Entity
 */
class CtlCatalogo
 
{
 
    /* code... */
 
    /**
     * Constructor
     */
    public function __construct()
    {
        $this->catalogoDetalle = new \Doctrine\Common\Collections\ArrayCollection();
    }
 
    /**
     * Add catalogoDetalle
     *
     * @param \Minsal\MaestrosBundle\Entity\CtlCatalogoDetalle $catalogoDetalle
     * @return CtlCatalogo
     */
    public function addCatalogoDetalle(\Minsal\MaestrosBundle\Entity\CtlCatalogoDetalle $catalogoDetalle)
    {
        $this->catalogoDetalle[] = $catalogoDetalle;
 
        return $this;
    }
 
    /**
     * Remove catalogoDetalle
     *
     * @param \Minsal\MaestrosBundle\Entity\CtlCatalogoDetalle $catalogoDetalle
     */
    public function removeCatalogoDetalle(\Minsal\MaestrosBundle\Entity\CtlCatalogoDetalle $catalogoDetalle)
    {
        $this->catalogoDetalle->removeElement($catalogoDetalle);
    }
 
    /**
     * Get catalogoDetalle
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getCatalogoDetalle()
    {
        return $this->catalogoDetalle;
    }
 
 
    /* code ... */
}

En donde:

  • addCatalogoDetalle: Permite agregar los detalles a un catalogo.
  • removeCatalogoDetalle: Permite eliminar los detalles pertenecientes al catalogo
  • getCatalogoDetalle: Respectivo método para obtener los detalles del catalogo.
Nota: En la entidad hija, no se necesita actualizar ningún cambio.
Para mayor informacion sobre las configuraciones hechas en las entities, puede consultar: Mapeo de Asociaciones

Agregar el método __toString a la siguiente Entidad:

CtlCatalogo.php
public function __toString() {
    return $this->nombre ? $this->nombre : '';
}
 
CtlCatalogoDetalle.php
public function __toString() {
    return $this->descripcion ? $this->descripcion : '';
}

Creación de las clases Admin

Crear las clases Admin para ambas Entidades con el comando.

php app/console sonata:admin:generate

Entidad CtlCatalogo Entidad CtlCatalogoDetalle

Modificar la clase CtlCatalgoDetalleAdmin.php

<?php
 
namespace Minsal\MaestrosBundle\Admin;
 
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
 
class CtlCatalogoDetalleAdmin extends Admin
{
    /**
     * @param DatagridMapper $datagridMapper
     */
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
    }
 
    /**
     * @param ListMapper $listMapper
     */
    protected function configureListFields(ListMapper $listMapper)
    {
    }
 
    /**
     * @param FormMapper $formMapper
     */
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('descripcion',null,array('label' => 'Nombre del Elemento'))
        ;
    }
 
    /**
     * @param ShowMapper $showMapper
     */
    protected function configureShowFields(ShowMapper $showMapper)
    {
    }
}

Modificar la clase CtlCatalgoAdmin.php

<?php
 
namespace Minsal\MaestrosBundle\Admin;
 
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
 
class CtlCatalogoAdmin extends Admin
{
    /**
     * @param DatagridMapper $datagridMapper
     */
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('nombre',null,array('label'=>'Nombre'))
            ->add('descripcion',null,array('label'=>'Descripcion del Catalogo'))
        ;
    }
 
    /**
     * @param ListMapper $listMapper
     */
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('nombre',null,array('label'=>'Nombre'))
            ->add('descripcion',null,array('label'=>'Descripcion del Catalogo'))
        ;
    }
 
    /**
     * @param FormMapper $formMapper
     */
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('nombre',null,array('label'=>'Nombre'))
            ->add('descripcion',null,array('label'=>'Descripcion del Catalogo'))
            ->with('Elementos del Catalogo')
                ->add('catalogoDetalle','sonata_type_collection',array('label' =>'Elemento'),
                                                                 array('edit' => 'inline', 'inline' => 'table'))
            ->end()
        ;
    }
 
    /**
     * @param ShowMapper $showMapper
     */
    protected function configureShowFields(ShowMapper $showMapper)
    {
        $showMapper
            ->add('nombre',null,array('label'=>'Nombre'))
            ->add('descripcion',null,array('label'=>'Descripcion del Catalogo'))
            ->add('idUsuarioReg.name', null,array('label'=>'Usuario que Registro'))
            ->add('fechaHoraReg', null,array('label'=>'Fecha y Hora de Registro'))
            ->add('idUsuarioMod.name',null,array('label'=>'Usuario que Modifico'))
            ->add('fechaHoraMod',null,array('label'=>'Fecha y Hora de Modificacion'))
            ->with('Elementos del Catalogo')
                ->add('catalogoDetalle', 'sonata_type_collection', array('label'=>'Elemento','route' => array('name' => 'show')),
                                                                   array('edit' => 'inline','inline' => 'table'))
            ->end()
        ;
    }
 
    public function prePersist($catalogo) {
        $user = $this->getConfigurationPool()->getContainer()->get('security.context')->getToken()->getUser();
        $catalogo->setIdUsuarioReg($user);
        $catalogo->setFechaHoraReg(new \DateTime());
    }
 
    public function preUpdate($catalogo) {
        $user = $this->getConfigurationPool()->getContainer()->get('security.context')->getToken()->getUser();
        $catalogo->setIdUsuarioMod($user);
        $catalogo->setFechaHoraMod(new \DateTime());
    }
}

En donde:

  • with(‘Elementos del Catalogo’) … end(): Establecen un nuevo bloque dentro del formulario.
  • catalogoDetalle: Es el nombre del atributo declarado en la entidad padre previamente.
  • sonata_type_collection: Tipo de formulario declarado, este es útil para las relaciones de uno a muchos. Permitira manejar como una colleccion, el conjunto de elementos que pertenezcan a un catalogo.
  • array('edit'=>'inline'): con este comando se declara que la edición del formulario que se esta incrustando se realizará en la misma línea y se mostrará como otro formulario.
  • array(...,'inline'=>'table'): indica que los campos seran mostrados dentro de una tabla.
Para ver las las opciones y valores disponibles del array que se configura, puede consultar:Uso avanzado de la relacion OneToMany

Configurando el Service

Con lo anterior se crearon las clases Admin que permitirán realizar el mantenimiento de las Entidades antes mencionadas, a la vez que se actualizó el archivo services.yml que contiene la declaración de los servicios de las clases admin creadas.

    minsal_maestros.admin.ctl_catalogo:
        class: Minsal\MaestrosBundle\Admin\CtlCatalogoAdmin
        arguments: [~, Minsal\MaestrosBundle\Entity\CtlCatalogo, SonataAdminBundle:CRUD]
        tags:
            - {name: sonata.admin, manager_type: orm, group: Administracion, label: Catalogo}
 
    minsal_maestros.admin.ctl_catalogo_detalle:
        class: Minsal\MaestrosBundle\Admin\CtlCatalogoDetalleAdmin
        arguments: [~, Minsal\MaestrosBundle\Entity\CtlCatalogoDetalle, SonataAdminBundle:CRUD]
        tags:
            - {name: sonata.admin, manager_type: orm, show_in_dashboard: false}

El parámetro show_in_dashboard: false, permite que omitir dentro del menu la clase admin declarada.

Configurando el Prepersist y Preupdate

Para que la inserción y actualizacion de los elementos del catalogo se realicen de manera exitosa es necesario crear una función llamada prePersist y otra preUpdte, que permitirá setear el valor del atributo catalogoDetalle, con lo que se permitira establecer el valor de la llave foranea idCatalogo de la entidad hija.

Para poder setear el valor es necesario crear las funciones de la siguiente manera en la clase Admin/CtlCatalogoAdmin.php:

<?php
//code ...
 
class CtlCatalogoAdmin extends Admin
{
    //code…
 
    public function prePersist($catalogo) {
        //code ...
 
        foreach ($catalogo->getCatalogoDetalle() as $catalogoDetalle) {
            $catalogoDetalle->setIdCatalogo($catalogo);
            $catalogoDetalle->setIdUsuarioReg($user);
            $catalogoDetalle->setFechaHoraReg(new \DateTime());
 
        }        
    }
 
    public function preUpdate($catalogo) {
        //code ...
 
        foreach ($catalogo->getCatalogoDetalle() as $catalogoDetalle) {
            $catalogoDetalle->setIdCatalogo($catalogo);
            $catalogoDetalle->setIdUsuarioMod($user);
            $catalogoDetalle->setFechaHoraMod(new \DateTime());
        }
    }
}

Cabe destacar que en estos métodos se pueden setear cualquier valor que queramos para la tabla en la base de datos que corresponda al Admin que se esta trabajando.

Al terminar estos pasos, ya es posible ingresar y actualizar los catálogos junto con su detalle.

Herramientas personales
Espacios de nombres

Variantes
Acciones
Navegación
Herramientas