Symfony2: Separar el Date y el Time en un formulario

Hay veces que tenemos un formulario en el que mostramos un DateTime y para más claridad lo dividimos en dos campos. En este artículo veremos cómo hacer esto mediante un DataTransformer de forma que podemos reutilizarlo.

Llevo un tiempo algo desconectado por falta de tiempo, pero el otro día vi un tweet de Asier Marqués sobre un plugin para seleccionar fechas y surgió el tema de separar un DateTime en dos campos de tipo string en un formulario. Vamos a ver paso a paso cómo hacer esto.

Vamos a ver primero toda la teoría necesaria, para una explicación más extensa sobre DataTransformers podéis ver la charla de Buenas Prácticas en Symfony2.

Formato de datos de un formulario

Cuando trabajamos con formularios tenemos 3 tipos de formatos, el formato del modelo que será el formato con el que trabaja la aplicación, por ejemplo un objeto User, un array, un string, etc. Por otro lado tenemos el formato de la vista que será el formato con el que le llegarán los datos a la vista para rellenar el formulario y también será el formato con el que nos llegarán los datos de la vista cuando el cliente envíe el formulario (array, string…) y finalmente tenemos el formato normalizado que es un formato que está en medio de estos dos y nos permite dado un evento por ejemplo obtener los datos siempre en el mismo formato (podría haber diferentes formatos del modelo y diferentes formatos de vista para el mismo tipo de campo, pero el normalizado sólo es uno, como por ejemplo DateTime).

Por lo tanto en nuestro caso el formato del modelo será DateTime, el formato de la vista será un array formado por dos campos, date y time y el formato normalizado mantendremos DateTime. Se recomienda en el formato normalizado que tenga la máxima información posible.

DataTransformers

Los DataTransformers van a ser los encargados de pasar los datos de un formato a otro. Existen los ModelTransformers para transformar entre el formado de Modelo <-> Normalizado y los ViewTransformers entre el formato Normalizado <-> Vista.

En nuestro caso y como hemos dicho antes que lo que va a cambiar es la vista usaremos un ViewTransformer.

DateTimeType o DatetTimeTypeExtension

Parece más o menos claro lo que necesitamos, pero nos falta crearnos una clase donde asociemos el DataTransformer a un Type para poder reutilizarlo. Tenemos dos opciones, podemos crear un nuevo Type con el name, por ejemplo, ‘datetime_separated’ o crear una Extension de DateTime de forma que añadimos esta funcionalidad al ya existente DateTimeType.

Bajo mi punto de vista sería mejor usar una extensión ya que sería una característica adicional para los tipos DateTime.

Y el caso es que iba a implementarlo cuando me he dado cuenta que además de que está la implementación del bundle que se comenta en el tweet, el formulario DateTimeType de Symfony permite separarlo en dos campos.

En la documentación vemos que hay 2 opciones, date_widget y time_widget, que podemos configurarlos como single_text:

De esta forma en la vista tendremos dos campos.

Hemos visto como una vez más es importante leer la documentación y también ha servido para pensar cómo se podría resolver el problema.

DateTypeType

Como la idea inicial era mostrar una forma de implementar una solución y como ya está hecho, vamos a ver el fichero DateTimeType.php de Symfony2 que es bastante interesante. Como es tan largo vamos a ver los métodos más importantes:

El método setDefaultOptions configura las opciones por defecto del formulario. Cuando configuramos el valor de widget a single_text estamos indicando que todo el DateTime se va a mostrar en un único input. Viendo el código vemos como empieza creando Closures para configurar distintos parámetros:

  • compound: Indica si el formulario va a estar compuesto por otros. En este caso, si es un single_text sólo va a tener un campo, si fuera otra cosa distinta, el formulario estará compuesto por varios campos (date y time).
  • date_widget y time_widget: Por defecto van a tener el mismo valor la opción widget.

Luego configura unos parámetros como opcionales y finalmente configura los distintos valores posibles que pueden tener algunos de estos parámetros.

Vamos a ver el método buildForm que es el más interesante y es el que construirá el formulario:

El método empieza guardando las partes que se van a mostrar según las opciones que se han indicado (L3-15). Luego configura los formatos y después viene la parte más interesante.

Si el widget está configurado como single_text (sin pararnos a mirar lo de HTML5), se añadirá un ViewTransformer para pasar de DateTime a string (L26-41). Si por el contrario no está configurado como single_text (L43-90), lo que se hace es en primer lugar separar las opciones para date y para time (L44-77), después se añade un ViewTransformer de tipo DataTransformerChain que está compuesto por un DataTransformer que pasará de DateTime a array y otro que cogerá ese array y lo transformará a “partes” que en este caso serán date y time. Finalmente añade los dos campos, date y time, que serán de tipo DateType y TimeType respectivamente.

Para acabar (L92-104), dependiendo del parámetro input, que indica en qué formato están los datos en el modelo, añade un ModelTransformer u otro. El hecho de añadir un ReversedTransformer es porque, por ejemplo, DateTimeToStringTransformer en su método transform pasa de DateTime a string, pero en el caso del ModelTransformer queremos lo contrario, pasar de string a DateTime.

Y hasta aquí el artículo de hoy, al final ha quedado bastante largo, pero bueno, hemos visto lo interesantes que son los DataTransformers y el juego que ofrecen.