Symfony2: Selects dependientes mediante eventos

Selects dependientes, selects relacionados, selects anidados, combos o combobox dependientes, hay muchas formas de llamarlos y siempre surgen dudas de cómo implementarlos, en este artículo vamos a ver una forma de implementarlo con formularios  y mediante eventos en Symfony2.

Para este ejemplo tenemos 3 entities típicas, Country, Province y City. City tiene una relación de muchos a uno con Province mediante el atributo $province y Province tiene una relación de muchos a uno con Country mediante el atributo $country. Creo que no es necesario pegar aquí el código de las entities ya que se haría demasiado largo y se puede ver en Github.

Nos creamos un modelo llamado Location que será sobre el que haremos el formulario:

Como se puede ver tiene un atributo $address que será un string y $city será un objeto de tipo City. Creamos el formulario asociado:

Si no has visto nada de eventos en los formularios se recomienda leer la receta de Dynamic Form Generation, nosotros vamos a usar dos eventos, PRE_SET_DATA al que se le llama justo antes de poblar el formulario con los datos del modelo y el evento PRE_BIND al que se le llama justo antes de poblar el formulario con los datos que llegan de la vista.

En el código anterior vemos que se crean 3 suscriptores y el campo address, vamos a ver los suscriptores.

AddCityFieldSubscriber:

Cuando se le llama a preSetData lo que se hace es comprobar si $data, que en este caso será un objeto de tipo Location, tiene asociado una ciudad para poder obtener la provincia, si tiene asociada una provincia entonces las ciudades que deben aparecer en el select son sólo las de esa provincia. En el método preBind se hace lo mismo, pero los datos llegan en forma de array.

Estos dos métodos llaman a addCityForm que se encarga de añadir al formulario el campo city y las ciudades que aparecerán serán en base a la provincia.

AddProvinceFieldSubscriber:

Para añadir el campo province es similar a city, en preSetData comprobamos que $data tiene el atributo city y recuperamos la provincia a la que pertenece la ciudad y el país al que pertenece la provincia. En preBind hacemos lo mismo que antes, $data es un array y obtenemos province y country. Finalmente en estos dos métodos llamamos a addProvinceForm que añade el campo province al formulario. El campo province tiene dentro de sus opciones mapped => false que significa que no está asociado al objeto que maneja el formulario, es decir al objeto de tipo Location, por esto mismo el formulario no sabe cuál es la provincia seleccionada y hay que pasársela como parámetro cuando se crea (tercer parámetro del createNamed).

AddCountryFieldSubscriber:

En este caso hacemos lo mismo que en provincia, pero al no tener un campo que limita la lista de países que aparecen sólo nos hace falta el $country seleccionado o que viene dado por la ciudad seleccionada.

Bernhard Schussek el lead developer del componente de formularios y validación comentaba que están trabajando en hacerlo más sencillo, así que está previsto. Parte del código de este artículo está basado en una pregunta en StackOverflow.

Faltaría la parte del controller y la vista (ajax para que al seleccionar un país aparezcan sólo las provincias de ese país, etc), pero para eso hemos creado unos ejemplos y se puede ver ahí:

Ver Demo

Código en Github