Doctrine Migrations Bundle o actualizaciones de datos incrementales

Uno de los problemas que surgen cuando usamos DataFixtures o datos de pruebas es que creamos muchas fixtures para que la base de datos tenga la informacion necesaria antes de lanzar la aplicacion a producción y que pueda funcionar todo a la perfección para aquellas tablas  que solo contienen informacion acotada para campos <select> por ejemplo. Hasta aquí todo bien, es el procedimiento normal.

Ahora bien, imagínate que al cabo del tiempo y una vez has desplegado tu aplicacion en producción, necesitas modificar un valor de un campo y no puedes borrar el esquema de la base de datos porque obviamente ya existen datos que no podemos perder de nuestros usuarios etc.

Aquí entra en juego el bundle de Doctrine para hacer “migraciones” incrementales y os voy a explicar brevemente como usarlo.

-Instalar bundle

>>php composer.phar require doctrine/doctrine-migrations-bundle

-Inicializar bundle(AppKernel.php)

new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),

-Crear migraciones

>> php app/console doctrine:migrations:generate

este comando lo que hace basicamente es crear una clase PHP dentro del directorio por defecto app/DoctrineMigrations/ cuyo nombre empieza por Version y añade la fecha y hora del momento de creacion. Ademas, la clase incluye 2 metodos up() y down() donde implementaremos el cambio(up) y la forma de deshacerlo(down) por si quisieramos volver atras. Podemos implementar la interfaz para hacer uso del contenedor de dependencias.

-Ejemplo:

class Version20160523164637 extends AbstractMigration implements ContainerAwareInterface
{
    /** @var ContainerInterface $container */
    private $container;

    /**
     * @param ContainerInterface $container
     */
    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    /**
     * @param Schema $schema
     */
    public function up(Schema $schema)
    {
        // this up() migration is auto-generated, please modify it to your needs
        $documentationTypeManager = $this->container->get('documentation_type_manager');
        $docTypeDni = $documentationTypeManager->findOneByName('DNI');

        if($docTypeDni){
            $docTypeDni->setName('DNI/NIF');
            $documentationTypeManager->persistAndFlush($docTypeDni);
        }
    }

    /**
     * @param Schema $schema
     */
    public function down(Schema $schema)
    {
        // this down() migration is auto-generated, please modify it to your needs
        $documentationTypeManager = $this->container->get('documentation_type_manager');
        $docTypeDni = $documentationTypeManager->findOneByName('DNI/NIF');

        if($docTypeDni){
            $docTypeDni->setName('DNI');
            $documentationTypeManager->persistAndFlush($docTypeDni);
        }
    }
}

Aqui podemos hacer cambios de 3 formas distintas:

  • Usando servicios que trabajan con objetos directamente(Managers)
  • Usando el QueryBuilder del EntityManager
  • Usando el metodo $this->addSql(‘UPDATE table SET x=1’); . Esto vendra bien si tenemos procedimientos almacenados en nuestra BD y queremos llamarlos.

-Ejecutar una migración

>> php app/console doctrine:migrations:migrate

Si todo es correcto, a continuación nos aparecerá lo siguiente:

Ejecucion de una migracion

Si investigamos en la base de datos, nos habrá creado una nueva tabla donde aparecen las versiones que tenemos y que hemos aplicado en nuestra aplicación.

-Ver el estado de las migraciones

>> php app/console doctrine:migrations:status

Con este comando podemos ver la información de las migraciones en nuestra aplicación así:

estado

-Desahacer una migración

>> php app/console doctrine:migrations:execute YYYYMMDDHHIISS –down

De esta forma deshacemos una migración cambiando YYYYMMDDHHIISS por el valor que pone en CurrentVersion del comando status. Por eso es muy importante implementar el método down() de la clase, para que sepa como deshacer una migración y que cambios tendría que aplicar para que este como estaba antes de hacer la migración.

Resumen

Con esta herramienta tendremos un histórico de aquellos cambios en los datos de nuestra esquema que han sufrido cambios a posteriori de instalar nuestros fixtures al desplegar nuestra aplicacion. También sabremos quien a cambiado que, ya que al crearse una nueva clase en nuestro proyecto, sabremos quien la ha creado y el motivo.

Documentación

Si os ha gustado, no dudéis en compartir este post.

Saludos SymfoyDevs ¡

Enviar email desde SwiftMailer con Hotmail

Despues de buscar informacion por todos los lados no he encontrado apenas documentacion acerca de configurar SwiftMailer para el envio de emails desde nuestra cuenta de Hotmail.

La unica forma de hacerlo que he hecho funcionar es la siguiente:

– En el action del controlador donde queremos enviar el email debemos hacerlo así:

$transport = \Swift_SmtpTransport::newInstance(‘smtp.live.com’, 587, ‘tls’)
->setUsername(‘USUARIO@hotmail.com’)
->setPassword(‘PASSWORD’);

$mailer = \Swift_Mailer::newInstance($transport);
$message = \Swift_Message::newInstance()
->setSubject($subject)
->setFrom($sendFrom)
->setTo($sendTo)
->setBody($body);

$mailer->send($message);

Desconozco de que forma se puede meter esta configuración en el archivo config.yml ya que despues de hacer algunas pruebas no me ha llegado a funcionar. Si alguien lo prueba y lo consigue, que lo publique en los comentarios y lo añadire a esta entrada.

Saludos SymfonyDevs ¡

Compartid este articulo ¡

Formularios: Coleccion no mapeada en la entity usando un DTO

Si no has leido mi anterior “trick” acerca del uso de los DTOs en los formularios, deberias visitar antes este link.

En este ejemplo, necesitaba varios campos que no estan mapeados en la entity de la cual nace el formulario. Para ello, no he tenido mas remedio que añadir un nuevo campo “No mapeado”. Este campo es una coleccion, por lo que se puede añadir o eliminar elementos.

De acuerdo con esto, en mi formulario añado ese campo asi:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        //OTROS CAMPOS DE LA ENTIDAD
        //...
        ->add('enrolUserToOrg', CollectionType::class, array(
             'label' => false,
             'entry_type' => new EnrolUserToOrgType(),
             'mapped' => false,
             'allow_add' => true,
             'attr' => array(
                     'class' => 'enrolUserOrgWrapper'
             )
      ));
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'efor\AppBundle\Entity\Organization',
    ));
}

Os he marcado en color aquello que hay que tener en cuenta.

  • CollectionType::class: Definimos el campo de tipo coleccion para indicarle a Symfony que podremos añadir/quitar elementos
  • ‘entry_type’ => new EnrolUserToOrgType() : Instanciamos el sub-formulario que contendra los campos para cada fila de la coleccion
  • ‘mapped’ => false : Le indicamos que no esta mapeado en la entity para que no valide nada
  • ‘allow_add’ => true : Indicamos que podremos añadir elementos

Ahora lo que haremos sera crear un DTO(en otros sitios lo llaman ‘Domain Model’) que contendra los elementos del “subformulario” el cual hablaba antes y que representaran una fila de cada coleccion. Este DTO contendra 2 campos: usuario y rol, y quedaria asi:

<?php
namespace efor\AppBundle\Model;

use efor\AppBundle\Entity\Role;
use efor\UsuarioBundle\Entity\User;

class EnrolUserToOrg
{
    /** @var User $user */
    private $user;

    /** @var Role $role */
    private $role;

    /**
     * @return User
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * @param User $user
     */
    public function setUser($user)
    {
        $this->user = $user;
    }

    /**
     * @return Role
     */
    public function getRole()
    {
        return $this->role;
    }

    /**
     * @param Role $role
     */
    public function setRole($role)
    {
        $this->role = $role;
    }
}

Ahora creamos el subformulario y le indicamos que la clase en la que se tiene que basar es el anterior DTO:

public function buildForm(FormBuilderInterface $builder, array $options)
{
        $builder
            ->add('user', EntityType::class, array(
                'label' => false,
                'class' => 'UsuarioBundle:User',
                'empty_value' => 'Selecciona el/los responsable/s',
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('users');
                },
                'attr' => array(
                    'class' => 'col-md-5 select-user'
                )
            ))
            ->add('role', ChoiceType::class, array(
                'label' => false,
                'empty_value' => 'Seleccionar un Rol',
                'choices' => $this->roleChoices,
                'attr' => array(
                    'class' => 'col-md-5 select-role'
                )
            ))
            ->add('Quitar', ButtonType::class, array(
                'attr' => array(
                    'class' => 'col-md-2 btn-danger button-remove-enrol'
                )
            ))
       ;
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'efor\AppBundle\Model\EnrolUserToOrg'
    ));
}

Llegados a este punto, vamos a por la vista. Como es una coleccion he seguido la guia de Symfony para pintar esos campos en el formulario y la he ajustado a mi gusto, y quedaria asi:

<ul class="enrolUserToOrg" 
    data-prototype="{{ form_widget(form.enrolUserToOrg.vars.prototype) | e }}">
</ul>

Despues añadiriamos la logica necesaria en jQuery para poder añadir y eliminar filas de esa coleccion:

var $collectionHolder;

// setup an "add a enrolUserToOrg" link
var $buttonAdd = '<span class="input-group-btn">'+
    '<a class="btn btn-success btn-add add_enrol_link"><span class="glyphicon glyphicon-plus"></span> ' +
    '<span>Añadir nuevo responsable</span></a>'+
    '</span>';
var $addLink = $($buttonAdd);
var $newLinkLi = $('<li class="text-center"></li>').append($addLink);

jQuery(document).ready(function() {
    // Get the ul that holds the collection of authorized
    $collectionHolder = $('ul.enrolUserToOrg');

    // add the add li object to the tags ul
    $collectionHolder.append($newLinkLi);

    // count the current form inputs we have (e.g. 2), use that as the new
    // index when inserting a new item (e.g. 2)
    $collectionHolder.data('index', $collectionHolder.find(':input').length);

    $addLink.on('click', function(e) {
        // prevent the link from creating a "#" on the URL
        e.preventDefault();

        // add a new tag form (see next code block)
        addAuthorizedUserForm($collectionHolder, $newLinkLi);
    });

    //initialize the collection with one element
    if($collectionHolder.find('li').length == 1){
        $addLink.click();
    }

    $(document).on('click', '.button-remove-enrol', function () {
        var contElems = $collectionHolder.find('li').length - 1;

        if(contElems > 1 ){
            $(this).closest('li').remove();
        }
    });

});

function addAuthorizedUserForm($collectionHolder, $newLinkLi)
{
    // Get the data-prototype explained earlier
    var prototype = $collectionHolder.data('prototype');

    // get the new index
    var index = $collectionHolder.data('index');

    // Replace '__name__' in the prototype's HTML to
    // instead be a number based on how many items we have
    var newForm = prototype.replace(/__name__/g, index);

    // increase the index with one for the next item
    $collectionHolder.data('index', index + 1);

    // Display the form in the page in an li, before the "Add a tag" link li
    var $newFormLi = $('<li></li>').append(newForm);
    $newLinkLi.before($newFormLi);
}

Y para la parte del controlador, podremos recoger los datos de la coleccion del campo no mapeado de la siguiente forma:

/**
 * @Route("/create", name="organization_create")
 */
public function createOrganizationAction(Request $request)
{
    $organization = new Organization();
    $form = $this->createForm(new OrganizationType(), $organization, array(
        'action' => $this->generateUrl('organization_create'),
    ));

    $form
        ->add('ok', 'submit', array(
            'attr' => array('class' => 'btn btn-primary pull-right'),
            'label' => 'Crear',
        ))
        ->add('cancel', 'button', array(
            'attr' => array('class' => 'return-button pull-right'),
            'label' => 'Volver atrás',
        ));

    $form->handleRequest($request);
    if ($form->isValid()) {
        $usersEnroled = $form->get('enrolUserToOrg')->getData();

        //DO SOMETHING YOU NEED WITH THIS ARRAY
        //...example
        // /** @var EnrolUserToOrg $usersEnroled */
        //foreach($usersEnroled as $userEnroled){
             //$user = $userEnroled->getUser();
             //$role = $userEnroled->getRole();
             //...
        //}  
        
       $this->addFlashMessage(CoreController::FLASH_TYPE_SUCCESS, 'La organización ha sido creada con exito'); return $this->redirect($this->generateUrl('organization_list')); } return $this->render('AppBundle:Organization:createOrganization.html.twig', array( 'form' => $form->createView() )); }

De esta forma, nuestra accion sera la que inicialice y procese el formulario a la vez, y asi podremos recoger la coleccion no mapeada y hacer lo que necesitemos.

Os dejo un pequeño video de como quedaria y el efecto de añadir / quitar elementos de la coleccion.

Coleccion no mapeada en Symfony 2

Espero que os guste y por favor, compartid este “trick”.

Cualquier duda, podeis consultarme o mirar en la documentación que os he puesto en los links de arriba y aqui.

Saludos Sf-Devs¡¡

Formularios: Sobrescribir template de un widget – Personalización

En esta ocasión os voy a hablar sobre como sobrescribir un form_widget de los que se usa en los formularios para personalizarlo a nuestro gusto.

En mi caso, tengo configurado para que Symfony coja las plantillas definidas en sus Form Themes,las cuales meten elementos HTML definidos para que se ajusten a un estilo concreto.

Eso se puede configurar en el app/config/config.yml:

twig:
   //...
    form_themes:
        - 'bootstrap_3_layout.html.twig'

Aqui podemos elegir entre varios form_themes que Symfony trae pre-definidos:

Symfony comes with some built-in form themes that define each and every fragment needed to render every part of a form:

form_div_layout.html.twig, wraps each form field inside a element.

form_table_layout.html.twig, wraps the entire form inside a element and each form field inside a element.

bootstrap_3_layout.html.twig, wraps each form field inside a element with the appropriate CSS classes to apply the default Bootstrap 3 CSS framework styles.

bootstrap_3_horizontal_layout.html.twig, it’s similar to the previous theme, but the CSS classes applied are the ones used to display the forms horizontally (i.e. the label and the widget in the same row).

foundation_5_layout.html.twig, wraps each form field inside a element with the appropriate CSS classes to apply the default Foundation CSS framework styles.

En mi aplicacion me ocurre que esto me inserta una etiqueta ‘div class=form-control’ al insertar un elemento de mi FormType y justamente necesito que ese “div” no exista para que se alinee todo horizontalmente, sino la clase form-control me coge todo el ancho y no permite elementos contiguos.

Para ello, no me queda mas remedio que buscar el form_theme y en concreto el widget que “pinta” ese div y “sobrescribirlo”.

vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig

{% block form_row -%}
    {{- form_label(form) -}} 
    {{- form_widget(form) -}} 
    {{- form_errors(form) -}}
{%- endblock form_row %}
 Ese es el bloque que mete el div que necesito quitar. Justamente el widget que pinta un row del formulario.

1- Creamos un template Twig en nuestro proyecto y lo llamamos ‘formRowCustom.html.twig’ y en el metemos lo siguiente:

{% block form_row -%}
        {{- form_label(form) -}}
        {{- form_widget(form) -}}
        {{- form_errors(form) -}}
{%- endblock form_row %}

2- A continuacion para poder usar nuestro “widget theme”, nos vamos al formulario donde lo queremos y lo usaremos asi:

{% form_theme form.user 'AppBundle:Form:formRowCustom.html.twig' %}
{{ form_widget(form.user) }}

De esta forma, cuando se vaya a ejecutar la funcion de Twig llamada “form_widget()”, en lugar de “pintar” el estilo del Form Theme, nos mostrara  nuestro estilo personalizado.

Con este “trick” podemos personalizar todos los elementos de los formularios para que se ajusten a nuestro diseño.

Para más información os dejo el enlace de la documentación oficial para que veais mas ejemplos o formas de sobrescribir los elementos de los form themes.

Si te a gustado y lo encuentras util, no dudes en compartir este truco en las redes sociales.

Hasta la proxima Sf dev¡¡

 

Uso del operador IN en consultas DQL de Doctrine y otros operadores SQL

A menudo tengo la necesidad de usar el operador IN dentro del WHERE de una consulta DQL con Doctrine y casi siempre se me olvida como usarlo.

Para ello voy a explicar como usarlo a continuación con un ejemplo:

– Imaginamos que tenemos un array con los posibles valores que queremos seleccionar en la parte del where de la consulta tal que asi:

$filters = array(1, 2, 3, 4, 10, 15, 25);

-Ahora, hacemos nuestra consulta de la siguiente forma:

$users = $qb
    ->addSelect('user')
    ->from('UsuarioBundle:User', 'user')
    ->join('user.userGroup', 'userGroup')
    ->where($qb->expr()->in('userGroup.id', $filters))
    ->getQuery()
    ->getResult();

 De esta forma, usando el expr() del QueryBuilder podemos usar los operadores IN, NOT IN, BETWEEN, MAX, MIN, etc. Podeis ver toda la documentacion y los operadores disponibles en la documentacion oficial de Doctrine sobre el querybuilder:

-http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/query-builder.html#high-level-api-methods

Ale, espero que os sirva de ayuda.

Saludos Devs¡

Herencia entre entidades y consultas DQL relacionadas

herencia entre entidades y consultas DQL
Herencia entre entidades

Una de las características que tiene Doctrine es que se puede usar la herencia de clases para representar aquellas tablas que heredan de una tabla “base”.

Pongamos un ejemplo:

  • Tenemos una tabla Rol (“padre”) y de ella heredan 3 tipos de roles distintos(RolPlataforma, RolOrganizacion, RolEdicion), pero que comparten los mismos atributos que la tabla Rol “padre”, excepto que se cada “entidad” hija se relacionan con entidades diferentes. Aqui el diagrama UML que representa el caso:
  • umlAhora la parte de las entidades:
    • Entity Padre Role
  • /**
     * @ORM\Table(name="role")
     * @ORM\InheritanceType("JOINED")
     * @ORM\DiscriminatorColumn(name="discr", type="string")
     * @ORM\DiscriminatorMap({
     *     "role" = "Role",
     *     "role_edition" = "RoleEdition",
     *     "role_org" = "RoleOrganization",
     *     "role_plat" = "RolePlatform"
     * })
     */
    class Role 
    {
     const ROLE_EDITION = 'RoleEdition';
     const ROLE_ORGANIZATION = 'RoleOrganization';
     const ROLE_PLATFORM = 'RolePlatform';
     const ROLE = 'Role';
    
     /**
     * @ORM\Id
     * @ORM\Column(type="integer", nullable=false)
     * @ORM\GeneratedValue()
     */
     protected $id;
    
     /**
     * @ORM\Column(name="rol_borrado", type="boolean", nullable=false)
     */
     private $deleted = false;
    
     /**
     * @ORM\ManyToMany(targetEntity="efor\UsuarioBundle\Entity\User", mappedBy="roleUser")
     */
     private $user;
    
    //...
    
    //OTROS CAMPOS NO RELEVANTES PARA EL EJEMPLO
    //SETTERS Y GETTERS Y OTROS METODOS.
  • Con la anotacion estamos indicando que se va a hacer herencia de tablas y que los tipos de discriminador van a ser los que pone en el “DiscriminatorMap”. De esta entidad nunca se van a instanciar objetos, siempre se instanciaran objetos de las entidades hijas, por lo que podriamos definirla como Abstracta.
  • Entity descendiente RolePlatform
    • /**
       * @ORM\Table(name="rol_plataforma")
       * @ORM\Entity
       */
      class RolePlatform extends Role
      {
          protected $discr = self::ROLE_PLATFORM;
      
          /**
           * @ORM\ManyToOne(targetEntity="efor\AppBundle\Entity\Platform", inversedBy="rolePlatform")
           */
          private $platform;
      
          //otros campos
      }
  • Entity descendiente RoleOrganization
  • /**
     * @ORM\Table(name="rol_organizacion")
     * @ORM\Entity
     */
    class RoleOrganization extends Role
    {
        protected $discr = self::ROLE_ORGANIZATION;
    
        /**
         * @ORM\ManyToOne(targetEntity="efor\AppBundle\Entity\Organization", inversedBy="roleOrganization", cascade={"persist"})
         */
        private $organization;
    
        //otros campos
    }
  • Entity descendiente RoleEdition
  • /**
     * @ORM\Table(name="rol_edicion")
     * @ORM\Entity
     */
    class RoleEdition extends Role
    {
        protected $discr = self::ROLE_EDITION;
    
        /**
         * @ORM\ManyToOne(targetEntity="efor\AppBundle\Entity\Edition", inversedBy="roleEdition")
         */
        private $edition;
    
        //otros campos
    }

Si os fijas cada entidad hija tiene una relacion distinta a otra entidad, por eso necesitabamos usar la herencia entre entidades, porque podemos crear objetos que se relacionan con entidades distintas en funcion del tipo de relacion que queramos tener.

En la documentación de Doctrine explica los tipos de herencia entre entidades y algunas consideraciones a tener en cuenta cuando se usa la herencia. Os dejo aqui la documentacion:

-http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#class-table-inheritance

-http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#single-table-inheritance

-http://stackoverflow.com/questions/7504680/doctrine-2-how-to-write-a-dql-select-statement-to-search-some-but-not-all-the

Consultas DQL filtradas por el campo discriminador:

Una de las dudas que surgen al trabajar con la herencia en Doctrine es saber como puedes filtrar las consultas por el campo discriminatorio. En mi caso, necesitaba filtrar los resultados por aquellos objetos que eran de una determinada clase del discriminador(RoleOrganization) y despues de buscar, encontre que se puede hacer algo asi:

public function findUsersFilteredByRoleOrganization(User $loggedUser, Role $selectedRole)
{
    $qb = $this->getEntityManager()->createQueryBuilder();
    $usersResult = $qb
    ->select('user')
    ->from('UsuarioBundle:User', 'user')
    ->join('user.roleUser', 'roleUser')
    ->where('roleUser INSTANCE OF AppBundle:RoleOrganization')
    ->getQuery();

    return $usersResult->getResult();
}

Si os dais cuenta, hemos hecho uso del INSTANCE OF dentro del Where de la consulta DQL, la cual se traduce por esto en SQL:

SELECT t0_.*
INNER JOIN rol_usuario t2_ ON t0_.id = t2_.user_id
INNER JOIN rol t1_ ON t1_.id = t2_.role_id
LEFT JOIN rol_edition t3_ ON t1_.id = t3_.id
LEFT JOIN rol_org t4_ ON t1_.id = t4_.id
LEFT JOIN rol_plataforma t5_ ON t1_.id = t5_.id
WHERE t1_.discr IN (‘role_org’)

De esta forma solo obtenemos los objetos del tipo “RoleOrganization” que queremos.

Espero que os sirve de ayuda, ya que la documentacion de Doctrine no es muy explicita en este aspecto y no lo explica muy bien que digamos.

Recordad compartir este articulo para que llegue al maximo de amigos de Symfony.

Saludos ¡¡

 

 

Uso de Data Transfer Objects (DTO) en formularios

Uno de los problemas que a veces nos encontramos en los formularios a menudo, es que necesitamos mostrar campos que no existen en nuestra entidad directamente ya que puede que necesitemos esos campos para construir un determinado objeto en funcion de las opciones que haya elegido.

En mi caso, tengo un formulario en el que aparecen 4 campos de tipo <select> y que son dependientes entre sí, y en funcion de las opciones el usuario tendra asignado unas cosas o otras, y 3 de estos campos no estan mapeados en la Entity.

Para ello, debemos hacer uso de los DTO (Data Transfer Object) que no son mas que “Objetos de Transferencia de Datos” o objetos “pasarela” para recoger la informacion del formulario y hacer a posteriori nuestras cosas.

Enctonces, imaginamos que tenemos una entidad Organizacion pero necesitamos 4 campos mas que no estan en nuestra entity pero que los necesitamos para el formulario:

<?php
namespace myApp\AppBundle\Model;

use Symfony\Component\Validator\Constraints as Assert;

class Enrol
{
    protected $organization;

    protected $plan;

    protected $edition;

    protected $group;

    /**
     * @Assert\NotBlank()
     */
    protected $roleUser;

    /**
     * Enrol constructor.
     */
    public function __construct()
    {
    }

    /**
     * @return mixed
     */
    public function getOrganization()
    {
        return $this->organization;
    }

    /**
     * @param mixed $organization
     */
    public function setOrganization($organization)
    {
        $this->organization = $organization;
    }

    /**
     * @return mixed
     */
    public function getPlan()
    {
        return $this->plan;
    }

    /**
     * @param mixed $plan
     */
    public function setPlan($plan)
    {
        $this->plan = $plan;
    }

    /**
     * @return mixed
     */
    public function getEdition()
    {
        return $this->edition;
    }

    /**
     * @param mixed $edition
     */
    public function setEdition($edition)
    {
        $this->edition = $edition;
    }

    /**
     * @return mixed
     */
    public function getGroup()
    {
        return $this->group;
    }

    /**
     * @param mixed $group
     */
    public function setGroup($group)
    {
        $this->group = $group;
    }

    /**
     * @return mixed
     */
    public function getRoleUser()
    {
        return $this->roleUser;
    }

    /**
     * @param mixed $roleUser
     */
    public function setRoleUser($roleUser)
    {
        $this->roleUser = $roleUser;
    }
}

Este es el modelo en el que se basara nuestro formulario para “hidratar” los campos del objeto que le pasemos.

Nuestro formulario quedaría así:

<?php

namespace myApp\AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class EnrolType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('organization', 'entity', array(
                'label' => 'Seleccionar una Organización',
                'class' => 'AppBundle:Organization',
                'required' => false
            ))
            ->add('plan', 'entity', array(
                'label' => 'Seleccionar un Plan',
                'class' => 'AppBundle:Plan',
                'required' => false
            ))
            ->add('edition', 'entity', array(
                'label' => 'Seleccionar una Edición',
                'class' => 'AppBundle:Edition',
                'required' => false
            ))
            ->add('group', 'entity', array(
                'label' => 'Seleccionar un Grupo',
                'class' => 'AppBundle:Group',
                'required' => false
            ))
            ->add('roleUser', 'entity', array(
                'label' => 'Seleccionar un Rol',
                'class' => 'AppBundle:Role',
                'required' => false
            ));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'myApp\AppBundle\Model\Enrol'
        ));
    }

    public function getName()
    {
        return 'myapp_bundle_enrol_type';
    }
}

Creamos el action:

/**
 * @Route("/organizations-form", name="org_form")
 */
public function orgFormAction(Request $request)
{
    $enrol = new Enrol();
    $form = $this->createForm(new EnrolType(), $enrol, array());
    $form->add('submit', 'submit');

    $form->handleRequest($request);
    if($form->isValid()){
        //Aqui harás lo que necesites con los datos del formulario
        //ya que tienes el DTO $enroll y con sus métodos puedes
        //recoger la informacion seleccionada en el formulario.
    }

    return $this->render('UserBundle::orgForm.html.twig', array(
        'form' => $form->createView()
    ));
}

Ya tenemos nuestro formulario, ahora la vista(puedes hacerla mas bonita si quieres):

{{ form_start(form) }}
{{ form_row(form.organization) }}
{{ form_row(form.plan) }}
{{ form_row(form.edition) }}
{{ form_row(form.group) }}
{{ form_row(form.roleUser) }}
{{ form_end(form) }}

Y con esto ya tendrias un formulario que no se a basado en una Entity y que contiene los campos que a ti te interesan y que despues procesaras en el Action de la forma que creas conveniente.

Espero que te haya servido, y si es asi, puedes compartir este articulo en redes sociales pulsando en ellas.

En otro articulo, explicare como hacer selects dependientes o anidados a traves de AJAX, otra de las necesidades que todo tenemos hoy en dia para los formularios. Pero eso sera en otro capitulo 🙂

Gracias y saludos a todos.