Desarrollo Web con Symfony2 integrando Sonata Project parte 2

De WikiSalud
Saltar a: navegación, buscar

Véase Desarrollo_Web_con_Symfony2_integrando_Sonata_Project_parte_1

Contenido

Estructura de carpetas de Symfony2

Symfony posee una estructura de carpetas como se muestra a continuación:

sonata
├── app
│   ├── AppCache.php
│   ├── AppKernel.php
│   ├── autoload.php
│   ├── bootstrap.php.cache
│   ├── cache
│   ├── check.php
│   ├── config
│   │   ├── config_dev.yml
│   │   ├── config_prod.yml
│   │   ├── config_test.yml
│   │   ├── config.yml
│   │   ├── parameters.yml
│   │   ├── routing_dev.yml
│   │   ├── routing.yml
│   │   └── security.yml
│   ├── console
│   └── logs
├── src
│   └── Application
│       └── CoreBundle
│           ├── ApplicationCoreBundle.php
│           ├── Controller
│           ├── Entity
│           ├── Form
│           ├── Resources
│           │   ├── config
│           │   │   └── services.xml
│           │   ├── public
│           │   │   ├── css
│           │   │   ├── js
│           │   │   └── images
│           │   └── views
│           │       ├── Demo
├── vendor
....
└── web
    ├── app_dev.php
    ├── apple-touch-icon.png
    ├── app.php
    ├── bundles
    │   ├── acmedemo
    │   │   ├── css
    │   │   ├── js
    │   │   └── images
    ├── config.php
    ├── favicon.ico
    └── robots.txt

Directorio app

La finalidad de este directorio es alojar a los archivos PHP encargados de los procesos de carga del Framework y a toda la configuración general de la aplicación. Los archivos de este directorio son los encargados de unir y dar cohesión a los distintos componentes del Framework.

├── app
│   ├── AppCache.php
│   ├── AppKernel.php
│   ├── autoload.php
│   ├── bootstrap.php.cache
│   ├── cache
│   ├── check.php
│   ├── config
│   │   ├── config_dev.yml
│   │   ├── config_prod.yml
│   │   ├── config_test.yml
│   │   ├── config.yml
│   │   ├── parameters.yml
│   │   ├── routing_dev.yml
│   │   ├── routing.yml
│   │   └── security.yml
│   ├── console
│   └── logs
  • autoload.php: mapea los espacios de nombres contra los directorios en los que residirán las clases pertenecientes a dichos espacios de nombre. De esa manera el proceso de autocarga de clases sabrá donde tiene que buscar las clases cuando se usen dichos espacios, sin necesidad de incluir explícitamente los archivos donde se definen las clases.
  • AppKernel.php: se declaran los bundles que se utilizarán en la aplicación. Como se explicará mas adelante, esta opción la realiza Symfony desde una instrucción desde consola.
  • config/: este directorio contiene todos los archivos de configuración del Framework. Algunos de los archivos son los que se describen a continuación.
    • config.yml: contiene la configuración general del Framework.
    • parameters.yml: contiene los parámetros de configuración de la base de datos.
    • routing.yml: contiene las rutas globales de los Bundles (este término se explicará a detalle más adelante) que contiene la aplicación.
    • routing_dev.yml:contiene las rutas globales de la aplicación como quien es raíz , la pagina de configuración, las paginas de ejemplos, entre otras.
    • security.yml:contiene las configuraciones necesarias para la seguridad de Symfony.
  • cache/ y logs/: Para acelerar la ejecución de los scripts, la configuración, el enrutamiento y las plantillas de twig son compiladas y almacenadas dentro del directorio cache/. En el directorio logs se almacenan los errores y otra información de interés acerca de eventos que ocurren cuando se ejecuta el framework.
  • console: esta es una aplicación que se ejecuta desde consola. Al ejecutarse sin parámetros se muestran todas las opciones disponibles para esta aplicación. Escribir en consola como usuario normal:
php app/console

Aparecerá algo parecido a lo siguiente:

Symfony version 2.1.3 - app/dev/debug
 
Usage:
  [options] command [arguments]
 
Options:
  --help           -h Display this help message.
  --quiet          -q Do not output any message.
  --verbose        -v Increase verbosity of messages.
  --version        -V Display this application version.
  --ansi              Force ANSI output.
  --no-ansi           Disable ANSI output.
  --no-interaction -n Do not ask any interactive question.
  --shell          -s Launch the shell.
  --process-isolation    Launch commands from shell as a separate processes.
  --env            -e The Environment name.
  --no-debug          Switches off debug mode.
 
Available commands:
......

Directorio src

Este directorio contiene todo el código creado por el desarrollador para la aplicación.

└── src
    └── Carpeta_Contenedora
        └── nombreBundle
            ├── Carpeta_ContenedoraNombreBundle.php
            ├── Controller
            │   └── nombrecontroladorController.php
            ├── Entity
            ├── Form
            └── Resources
                ├── config
                │   └── services.xml
                ├── public
                │   ├── css
                │   ├── js
                │   └── images
                └── views
                    └── Carpeta_Vistas
                        └── nombreVista.html.twig


Carpeta_Contenedora: debe nombrarse ya sea con el nombre de la institución o con el nombre del sistema ya que ella contendrá todos los Bundles que conformarán la aplicación.

  • nombreBundle/: este directorio es un Bundle. Siempre debe ir acompañado de la palabra Bundle luego del nombre.
  • Controller/: este directorio contendrá todos los controladores de ese bundle. Todos los controladores debe contener la palabra controller en el nombre de la clase.
  • Entity/: este directorio contendrá todas las entidades que son la representación de las tablas a nivel de objetos. Estas clases estarán conformadas por los campos de las tablas que serán los atributos de las clases y los métodos setter y getter.
  • Form/: este directorio contendrá los formularios realizados con Symfony.
  • Resorces/:este directorio contiene las configuraciones propias de cada bundle, en ese directorio se podrá guardar un rounting.yml propio para el bundle. En el directorio public estarán todos los issett que luego se harán público en la carpeta web. Y finalmente el directorio views contendrá todas las vistas que se necesiten en los controladores, estas pueden estar dentro de una carpeta contenedora o directamente sobre este directorio.

Directorio vendor

Este directorio contendrá todo aquel código que no pertenece al desarrollador de la aplicación; es lo que se conoce como librerías de terceros. También contiene los componentes de Symfony2, el ORM Doctrine2 y el sistema de plantillas twig.

Directorio web

Este directorio contiene el controlador frontal (app_dev.php y app.php) y todos los Assets de la aplicación: CSS's, Javascripts, imágenes, etc.

 └── web
    ├── app_dev.php
    ├── app.php
    ├── bundles
    │   ├── nombreBundle
    │   │   ├── css
    │   │   ├── js
    │   │   └── images
    └── config.php
  • app.php : es el controlador frontal de la aplicación, es decir, es el el que decide hacia donde deben ir las peticiones según los parámetros que esta posea. Un conjunto de parámetros determinado se denomina ruta. Por ejemplo:
 http://sonata.localhost/articulo/1

app.php, es el controlador frontal y articulo/1 es una ruta de la aplicación.

  • app_dev.php:es uno de los controladores frontales de la aplicación. Su función es la misma que el app_dev. Este controlador frontal es el utilizado para el desarrollo, para lo cual añade una barra de depuración que ofrece información sobre todo lo relacionado con la ejecución de la aplicación.
Barra de depuración

Al terminar el desarrollo de la aplicación este debe pasar al controlador frontal app.dev que es el controlador para producción.

  • config.php: ayuda a la configuración del Framework desde el navegador, no es necesario realizar la configuración desde esta página pero si es un usuario principiante es bueno realizarlo de esta manera para saber como configurar posteriormente el servidor. Tener en cuenta que se deben de tener los permisos en el directorio app/config para que los cambios puedan realizarse.
  • bundles/nombreBundle: este directorio contiene todas las css,javascript, imágenes, etc. de la aplicación ordenadas según como el programador haya decidido. nombreBundle serán todos los bundles que tengamos en nuestra aplicación que se requiera el uso de los assets; generalmente, solo será uno que es donde tendremos las plantillas de la aplicación.

Creación de un Bundle

Un bundle o paquete es nada más que un directorio que alberga todo relacionado con una función específica, incluyendo las clases PHP, configuración, e incluso hojas de estilo y archivos JavaScript.

La recomendación que se da para el espacio de nombres del bundle es el siguiente:Fabricante o Dueño del proyecto seguido del nombre del sistema, alguna categoría (opcional) y el nombre propio del paquete con el sufijo bundle. El espacio de nombre quedaría de la siguiente manera: Fabricante/Sistema/Categoria/nombreBundle. Ejemplo:

  • Minsal/Sigep/combustibleBundle
  • Minsal/Sidpla/rrhh/capacitacionBundle
  • Sigep/mantenimientoBundle
Nota: el espacio de nombre es decisión del desarrollador; esta es solo una recomendación.

Se procede a crear un nuevo bundle para el proyecto denominado minsal/academicaBundle. Abrir la consola y como usuario normal dirigirnos al directorio de nuestro proyecto, ejecutar el siguiente comando desde la consola:

php app/console generate:bundle

Al hacer esto en la consola aparecerá lo siguiente:

Creación Bundle Paso 1

El primer paso es seleccionar el namespace o espacio de nombre del bundle. Como se menciono anteriormente se llamará Minsal/CatalogosBundle asi que digitamos esto en la consola y presionamos enter.

Creación Bundle Paso 2

En la siguiente imagen muestra el nombre con que se conocerá el paquete creado. La opción predeterminada es la concatenacion de todo el espacio de nombre del bundle sin la pleca (/). Si se deseea cambiar este nombre solo se digita el nuevo nombre y presionar enter. En el caso que se desee el preterminado solo presionar enter.

Creación Bundle Paso 3

Ahora la aplicación console pregunta donde se quiere generar el paquete, se pude cambiar la ruta si es lo que se desea; si no es así presionar enter.


Console pregunta el formato con el que se desea crear la configuración de symfony. Los formatos disponibles son: yml, php, annotacion y xml. Es decisión del desarrollador que tipo de formato se utilizará. En este caso utilizaremos annotation y presionar enter.

Creación Bundle Paso 4


En la siguiente imagen pregunta que si se requiere generar la estructura completa.Esta opción generará algunos directorios y archivos extras que siguen las recomendaciones de Symfony2 para alojar código. Se generará la estructura completa, así que escribir yes.

Creación Bundle Paso 5

Se presenta un resumen del paquete que se creará; si se esta seguro de querer generar el paquete presionar enter, si no es así escribir no y se cancelará la generación.

Creación Bundle Paso 6

Al finalizar la ganeración console nos devolverá el bundle.

Al terminar la generación del bundle se presentan todos aquellos archivos que han sido modificados por la generación del bundle. Estos archivos son: appKernel.php y routing.yml. Ambos archivos se encuentran alojados en el directorio app/ de la aplicación.

Verificación de la creación

Para verificar si se ha creado el paquete o Bundle se revisarán el directorio de la aplicación y algunos archivos esenciales para la configuración. Para este ejemplo, se revisarán los archivos desde NetBeans pero puede realizarse con cualquier otra aplicación.

  • Verificar archivo appKernel.php : dentro del directorio app/ deberá de encontrarse el archivo appKernel.php, abrir el archivo y dentro de él deberá estar una linea siguiente:
new Minsal\CatalogosBundle\MinsalCatalogosBundle(),

dentro de un arreglo denominado bundles, así como se muestra en la siguiente figura:

appKernel.php
  • Verificar archivo routing.yml : dentro del directorio app/config/ deberá encontrarse el archivo routing.yml, al abrir este archivo encontraremos en la cabecera del archivo datos sobre la ruta que se dirigirá al paquete.
    • minsal_catalogos: Es el nombre que coloca para hacer referencia al paquete que se acaba de crear. Este nombre puede cambiarse según las necesidades de cada desarrollador.
    • resource: @MinsalCatalogosBundle/Controller/ esta línea indica que todas las rutas de este paquetes se encontraran dentro del controlador
    • type: annotation representa el tipo de configuración utilizada.
    • prefix: / representa un prefijo para acceder a este paquete por la URL.
routing.yml
  • Verificar la creación de los directorios : dentro de NetBeans, desplegar el directorio src/ y deberá encontrar el directorio Minsal/CatalogosBundle/, como se muestra en la siguiente figura:
Directorio MinsalCatalogosBundle
Probar la el nuevo bundle colocando en la URL la siguiente dirección: http://sonata.localhost/app_dev.php/hello/DTIC deberá aparecer lo siguiente
Prueba CatalogosBundle

A continuación se explicará la secuencia que Symfony realiza para poder mostrar una página.

  • Llama routing.yml: Lo primero que hace symfony al escribir una URL es buscar a que paquete debe llamar. Con la URL anterior desde el momento que se coloca /hello/DTIC se sabes que por la primera pleca '/' se esta llamando al paquete CatalgosBundle por el Controlador de Symfony. Ahora nos dirigimos al archivo DefaultController.php y ahí encontraremos una ruta en forma de comentario tal como se muestra acontinuación
DefaultController.php

Se estudiará las líneas que se muestran en la imagen:

@Route("/hello/{name}") indica que es una ruta que tiene como URL /hello/{name}, /hello/{name} significa que posee una variable de tipo GET.

@Template() si se desea indicar cual es la vista que se utilizará. Por defecto buscará dentro de resources un directorio denominado Defaul con un archivo llamado index.html.twig

  • Llama al controlador: el controlador que se llama para el ejemplo es el Default. Todos los controladores del paquete se encuentran ubicados en la carpeta Controller y el nombre de la clase controlador siempre debe tener la forma: nombreControladorController.php . La clase debe contener como mínimo ]

namespace Minsal\CatalogosBundle\Controller; es el directorio en donde se encuentra alojado el controlador.

use Symfony\Bundle\FrameworkBundle\Controller\Controller; con esta línea se esta importando la clase controller de Symfony para ser utilizada por el desarrollador.

class DefaultController extends Controller hereda de la clase controlador de symfony.

public function indexAction($name) método index del controlador. Todos los métodos que deban ser llamados por un controlador deberán llevar el sufijo Action luego del nombre.

return array('name' => $name); se esta llamando a la vista por defecto y se le envía la variable name.

  • Llamar a la vista: para este ejemplo la vista contiene lo siguiente:
index.html.twig

Todo lo que se encuentre entre doble llaves {{ }} son variables tipo Twig, estas variables son enviadas desde la url al controlador; y el controlador las envía a la vista para ser utilizadas.

Actualización de la base de datos de ejemplo

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

tar zxvf Maestros.tar.gz
psql -U Usuario -d BaseDatos -f maestros.sql > error.txt

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

Ahora ejecutar la siguiente instrucción:

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

Deberá aparecer en el directorio src/Minsal/CatalogosBundle/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/CatalogosBundle/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 MinsalCatalogosBundle annotation

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

php app/console doctrine:generate:entities MinsalCatalogosBundle

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

rm src/Minsal/CatalogosBundle/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\CatalogosBundle\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/CatalogosBundle/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.

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: @MinsalCatalogosBundle/Resources/config/services.yml }

Ahora agregaremos la nueva entidad a la configuración actual de menu. Para ello se debe de abrir el archivo src/Minsal/CatalagosBundle/Resources/config/services.yml y se debe cambiar la siguiente linea:

imports:
    #code...
    - {name: sonata.admin, manager_type: orm, group: admin, label: CtlPais}

Por

imports:
    #code...
    - {name: sonata.admin, manager_type: orm, group: "AD-1-Administración", label: Pais}

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_catalogos.admin.ctl_pais:
        class: Minsal\CatalogosBundle\Admin\CtlPaisAdmin
        arguments: [~, Minsal\CatalogosBundle\Entity\CtlPais, SonataAdminBundle:CRUD]
        tags:
            - {name: sonata.admin, manager_type: orm, group: "AD-1-Administración", label: Pais}


  • minsal_catalogos.admin.ctl_pais: Identificador del CRUD del sonata admin
  • class: Minsal\CatalogosBundle\Admin\CtlPaisAdmin: El nombre de la clase que se utiliza como clase Admin
  • arguments: [~, Minsal\CatalogosBundle\Entity\CtlPais, SonataAdminBundle:CRUD]: Contiene dos elementos importantes:
    • Minsal\CatalogosBundle\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: "AD-1-Administración", label: Pais}: 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\CatalogosBundle\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 de la clase CtlPaisAdmin.php

 $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. Agregar los siguientes metodos en la clase CtlPaisAdmin

    /*
     * 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('MinsalCatalogosBundle: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('MinsalCatalogosBundle: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('MinsalCatalogosBundle: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

AQUI ESTA EL CODIGO

 /**
     * @return \Sonata\AdminBundle\Datagrid\ProxyQueryInterface
     */
    public function createQuery($context = 'list') {
        $query = parent::createQuery($context);
 
        return new ProxyQuery(
                $query
                        ->where($query->getRootAlias() . 
                                '.activo = true')
        );
    }

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 sonata_db sonata_user

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/CatalogosBundle/Resources/config/doctrine/metadata/orm  --from-database --filter="DetalleDependenciaEstablecimiento" --force
 
    php app/console doctrine:mapping:import MinsalCatalogosBundle annotation --filter="DetalleDependenciaEstablecimiento"
    php app/console doctrine:generate:entities MinsalCatalogosBundle:DetalleDependenciaEstablecimiento

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/CatalogosBundle/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 MinsalCatalogosBundle:CtlDependenciaEstablecimiento --no-backup
php app/console doctrine:generate:entities MinsalCatalogosBundle: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\CatalogosBundle\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\CatalogosBundle\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/CatalogosBundles/Resource/config/ . Y agregar las siguientes líneas:

    minsal_maestros.admin.ctl_dependencia_establecimiento:
        class: Minsal\CatalogosBundle\Admin\CtlDependenciaEstablecimientoAdmin
        arguments: [~, Minsal\CatalogosBundle\Entity\CtlDependenciaEstablecimiento, SonataAdminBundle:CRUD]
        tags:
            - {name: sonata.admin, manager_type: orm, group: "AD-1-Administración", label: Estructura Organizativa}
 
    minsal_maestros.admin.detalle_dependencia_establecimiento:
        class: Minsal\CatalogosBundle\Admin\DetalleDependenciaEstablecimientoAdmin
        arguments: [~, Minsal\CatalogosBundle\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.

Añadiendo datepicker

Agregar a clase admin

   public function getTemplate($name) {
       switch ($name) {
           case 'edit':
               return 'MinsalCatalogosBundle:CtlDependenciaEstablecimiento:edit.html.twig';
               break;
           default:
               return parent::getTemplate($name);
               break;
       }
   }
    {% if app.user.getId() is defined %}
        <script type="text/javascript">
            jQuery('body .datepicker-plugin').each(function () {
                jQuery(this).datepicker();
            });
        </script>
    {% endif %}

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/CatalogosBundle/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\CatalogosBundle\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\CatalogosBundle\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 MinsalCatalogosBundle:CtlCatalogo --no-backup

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

<?php
 
namespace Minsal\CatalogosBundle\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\CatalogosBundle\Entity\CtlCatalogoDetalle $catalogoDetalle
     * @return CtlCatalogo
     */
    public function addCatalogoDetalle(\Minsal\CatalogosBundle\Entity\CtlCatalogoDetalle $catalogoDetalle)
    {
        $this->catalogoDetalle[] = $catalogoDetalle;
 
        return $this;
    }
 
    /**
     * Remove catalogoDetalle
     *
     * @param \Minsal\CatalogosBundle\Entity\CtlCatalogoDetalle $catalogoDetalle
     */
    public function removeCatalogoDetalle(\Minsal\CatalogosBundle\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\CatalogosBundle\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\CatalogosBundle\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()
        ;
    }
 
}

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

Agregando al controlador

 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());
    }

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\CatalogosBundle\Admin\CtlCatalogoAdmin
        arguments: [~, Minsal\CatalogosBundle\Entity\CtlCatalogo, SonataAdminBundle:CRUD]
        tags:
            - {name: sonata.admin, manager_type: orm, group: "AD-1-Administración", label: Catalogo}
 
    minsal_maestros.admin.ctl_catalogo_detalle:
        class: Minsal\CatalogosBundle\Admin\CtlCatalogoDetalleAdmin
        arguments: [~, Minsal\CatalogosBundle\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 controlador

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 ...
 
 public function createAction()
{
 
       $user = $this->container->get('security.context')->getToken()->getUser();
       $object->setIdUsuarioReg($user);
       $object->setFechaHoraReg(new \DateTime());
       foreach ($object->getCatalogoDetalle() as $catalogoDetalle) {
              $catalogoDetalle->setIdCatalogo($object);
              $catalogoDetalle->setIdUsuarioReg($user);
              $catalogoDetalle->setFechaHoraReg(new \DateTime());
       }
       $object = $this->admin->create($object);    
   ////CODE
}

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.

Agregando a clase Admin

 public function getTemplate($name) {
        switch ($name) {
            case 'edit':
                return 'MinsalCatalogosBundle:CtlCatalago:edit.html.twig';
                break;
            default:
                return parent::getTemplate($name);
                break;
        }
    }

Agregando Vista

{#

This file is part of the Sonata package.
 
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
 
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
 
#}

{% extends base_template %}
 
{% block title %}
    {% if admin.id(object) is not null %}
        {{ "title_edit"|trans({'%name%': admin.toString(object)|truncate(15) }, 'SonataAdminBundle') }}
    {% else %}
        {{ "title_create"|trans({}, 'SonataAdminBundle') }}
    {% endif %}
{% endblock%}
 
{% block navbar_title %}
    {{ block('title') }}
{% endblock %}
 
{% block actions %}
    <li>{% include 'SonataAdminBundle:Button:show_button.html.twig' %}</li>
    <li>{% include 'SonataAdminBundle:Button:history_button.html.twig' %}</li>
    <li>{% include 'SonataAdminBundle:Button:acl_button.html.twig' %}</li>
    <li>{% include 'SonataAdminBundle:Button:list_button.html.twig' %}</li>
    <li>{% include 'SonataAdminBundle:Button:create_button.html.twig' %}</li>
{% endblock %}
 
{% block tab_menu %}{{ knp_menu_render(admin.sidemenu(action), {'currentClass' : 'active', 'template': admin_pool.getTemplate('tab_menu_template')}, 'twig') }}{% endblock %}
 
{% use 'SonataAdminBundle:CRUD:base_edit_form.html.twig' with form as parentForm %}
{% import "SonataAdminBundle:CRUD:base_edit_form_macro.html.twig" as form_helper %}
 
{% block form %}
    {{ block('parentForm') }}
{% endblock %}
Herramientas personales
Espacios de nombres

Variantes
Acciones
Navegación
Herramientas