Curso Symfony 2: Validación de datos y creación de formularios
Ahora que ya hemos trabajado con la base de datos y con los datos en sí vamos a tocar dos temas que serán de mucha ayuda. El primero es como el framework nos ayuda a validar los datos que vamos a grabar en nuestra base de datos y el segundo es como crear los formularios para que los usuarios finales ingresen los datos en la aplicación.
Validación de datos
Como ya hemos visto cada tabla de nuestra base de datos es representada por medio de un Entity en el que usamos annotations para definir los metadatos. Para usar estos annotations importamos el paquete “use Doctrine\ORM\Mapping as ORM;” arriba del archivo y usamos los mismos por medio del alias ORM de esta manera “@ORM\Id”.
Para la validación de los datos importaremos otro namespace: “use Symfony\Component\Validator\Constraints as Assert;” y por medio del alias Assert, utilizando annotations definiremos los validadores para los campos que queramos. El listado completo de validadores o también llamados constraints los puedes ver en la documentación oficial.
Algo que resulta muy importante entender es que la definición de los metadatos que escribimos usando el alias @ORM no tiene el mismo propósito que cuando usamos el @Assert. Por ejemplo, en el caso de nuestro Entity Article, hemos definido que la propiedad (campo/columna) $title no permite nulos. Esto lo hicimos porque dejamos su mapeo como @ORM\Column(name=”title”, type=”string”, length=255) donde por defecto es not null pero esto no implica la validación de los datos ya que lo que acabamos de escribir es que para la creación de la tabla se debe tener en cuenta que no es nulo y esto sirve para generar correctamente el CREATE TABLE necesario.
Para asegurarnos de que no sea nulo a la hora de ingresar los datos debemos usar el @Assert\NotNull() cuyo objetivo es realmente decirle al validador de datos que efectivamente al intentar grabar datos por medio del entity este debe ser validado como que no permite valores nulos.
Estos annotations tienen la misma forma que los anteriores. Son llamadas a métodos que pueden recibir parámetros opcionales. Por ejemplo, si ponemos:
/** * @var string $title * * @ORM\Column(name="title", type="string", length=255) * @Assert\NotNull() */ private $title;
estamos diciendo que la propiedad title no debe permitir valores nulos y al momento de validarlos saldrá una mensaje diciendo eso en ingles, si queremos cambiar el mensaje por defecto lo hacemos agregando el argumento a la invocación de esta manera:
/** * @var string $title * * @ORM\Column(name="title", type="string", length=255) * @Assert\NotNull(message="Debe escribir un titulo") */ private $title;
Si quisiéramos validar que un campo debe ser de tipo email usaremos el annotation @Assert\Email() de esta manera:
/** * @Assert\Email( * message = "El mail '{{ value }}' ingresado no tiene el formato correcto.", * ) */ protected $email;
Haciendo referencia a {{ value }} va a mostrar el valor ingresado como parte del mensaje.
Como último ejemplo, si quisiéramos validar la máxima cantidad de caracteres ingresados, podríamos usar el @Assert\MaxLength():
/** * @var string $title * * @ORM\Column(name="title", type="string", length=255) * @Assert\NotNull(message="Debe escribir un titulo") * @Assert\MaxLength(255) */ private $title;
Y si quisiéramos además de la máxima cantidad de caracteres, controlar la mínima cantidad simplemente lo agregamos también:
/** * @var string $title * * @ORM\Column(name="title", type="string", length=255) * @Assert\NotNull(message="Debe escribir un titulo") * @Assert\MaxLength(255) * @Assert\MinLength(5) */ private $title;
Con esto ya estamos controlando que mínimamente debemos escribir 5 caracteres en el título y como máximo 255.
[tipexperto titulo = “Nota”]Cuando usamos el @Assert\MaxLength(), la cantidad de caracteres que permitimos debe ser menor o igual al length definido en el @ORM\Column() ya que de lo contrario la aplicación dejaría pasar valores mayores y al llegar a la base de datos nos devolvería un error pero del motor de datos.[/tipexperto]
Como ya había mencionado más arriba, en la documentación oficial tenemos los constraints soportados y si damos click sobre cada uno de ellos veremos como se utilizan con un ejemplo. Entre ellos encontrarán NotNull, NotBlank, Email, MinLength y MaxLength (para cantidad de caracteres), Max y Min (para valores numéricos), Date, DateTime, Time, Choice (para campos que serán ingresados por medio de un combo de valores por ejemplo).
Al escribir los Asserts en realidad estamos configurando las validaciones que queremos tener pero para validar los datos debemos invocar al validador. Para esto usemos como base el ejemplo que teníamos para la inserción de artículos del capítulo anterior:
public function crearAction() { $articulo = new Articles(); $articulo->setTitle('Articulo de ejemplo 1'); $articulo->setAuthor('John Doe'); $articulo->setContent('Contenido'); $articulo->setTags('ejemplo'); $articulo->setCreated(new \DateTime()); $articulo->setUpdated(new \DateTime()); $articulo->setSlug('articulo-de-ejemplo-1'); $articulo->setCategory('ejemplo'); $em = $this->getDoctrine()->getEntityManager(); $em->persist($articulo); $em->flush(); return $this->render('MDWDemoBundle:Articulos:articulo.html.twig', array('articulo' => $articulo)); }
En código anterior, sin validar nada y pasando por alto los constraints de nuestro Entity intenta grabar los datos y si por ejemplo no cargamos un dato obligatorio como el título, el error devuelto será el que la misma base de datos valida ya que la columna fue creada como not null pero lo que queremos es poder obtener la validación en la aplicación, antes que llegue el insert a la base de datos, y esto lo haremos por medio del validador agregando el siguiente código antes de la invocación al EntityManager:
$errores = $this->get('validator')->validate($articulo);
Por medio del objeto $this->get(‘validator’) le decimos que valide la entidad $articulo, quién ya sabe como validarse por si misma ya que los annotations están dentro de la misma. Este método validate() nos devolverá un array de errores que podemos iterar y obtenerlos por medio del método getMessage():
public function crearAction() { $articulo = new Articles(); //-- No cargamos el dato para title $articulo->setAuthor('John Doe'); $articulo->setContent('Contenido'); $articulo->setTags('ejemplo'); $articulo->setCreated(new \DateTime()); $articulo->setUpdated(new \DateTime()); $articulo->setSlug('articulo-de-ejemplo-1'); $articulo->setCategory('ejemplo'); $errores = $this->get('validator')->validate($articulo); if(!empty($errores)) { foreach($errores as $error) echo $error->getMessage(); return new Response(); } $em = $this->getDoctrine()->getEntityManager(); $em->persist($articulo); $em->flush(); return $this->render('MDWDemoBundle:Articulos:articulo.html.twig', array('articulo' => $articulo)); }
En el código de arriba hemos borrado la línea del setTitle(). Esto nos mostrará en pantalla el mensaje “Debe escribir un titulo” y si tenemos más errores los mensajes por cada error.
Ahora bien, realmente no tiene mucho sentido mostrar de esta manera los mensajes de errores ya que finalmente ni siquiera cargamos los datos a mano como lo estamos haciendo, sino que son ingresados por un formulario y es aquí donde pasamos al siguiente tema, la creación de formularios.
Creación de Formularios
La posibilidad de crear formularios es uno de los temas que más me gusta de Symfony ya que los mismos no se escriben en HTML sino que son programados como objetos y el mismo framework se encarga de hacer render del HTML necesario y asegurándonos que serán escritos de la mejor manera posible incluso utilizando las validaciones de HTML5.
Un formulario siempre debería ser representado por un objeto que se lo conoce como Type. Este objeto hace referencia a otro que puede ser un Entity (del que ya hablamos en los capítulos anteriores) o un POPO (Plain Old PHP Object).
[tipexperto titulo = “Nota”]Un POPO (Plain Old PHP Object) es simplemente una clase con propiedades y métodos tradicionales, es decir que es muy parecido a los Entities pero sin los annotations. Por ejemplo en caso de ser una clase que representará a un formulario para contacto donde tendremos simplemente una propiedad asunto, email, nombre y texto con sus respectivos setters y getters.[/tipexperto]
Un objeto Type se debe tomar como la definición del formulario. Este objeto recibirá cual es el Entity o POPO en el cual se almacenan los datos cargados en el formulario. Podríamos tener más de un Type para un mismo objeto ya que dependiendo de ciertos perfiles por ejemplo, podríamos querer mostrar algunos campos u otros dependiendo de que el usuario sea operador normal o administrador.
Definición de nuestro formulario
Para nuestro ejemplo tomaremos en cuenta el Entity Article que venimos usando y crearemos un objeto Type para representar a este formulario. Los formularios se deben crear dentro de nuestro Bundle en una carpeta Form por lo que crearemos el archivo ArticleType dentro de nuestra carpeta src/MDW/DemoBundle/Form:
[tipexperto titulo = “Actualización”] Al momento de realizar la guía se trabajó en la versión 2.0, para quienes trabajan en la versión 2.1 hay una modificación al realizar la definición de nuestro formulario:
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
Ya no se debe usar la clase FormBuilder sino la interfaz FormBuilderInterface.
[/tipexperto]
<?php namespace MDW\DemoBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class ArticleType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('title') ->add('author') ->add('created'); } public function getName() { return 'article_form'; } }
Como podemos ver en el ejemplo, el nombre de la clase esta formado por un nombre que yo he elegido concatenado con el sufijo Type y debe heredar de AbstractType para contener las funcionalidades base.
En el método buildForm(), por medio del $builder, que se recibe como argumento, agregamos los campos que vamos a usar. Estos campos son los nombres de los campos que tendrá el formulario y deben coincidir con las propiedades de nuestro Entity Article aunque todavía no hemos dicho que el formulario representará a Article ya que eso lo hacemos en la invocación desde el controller. El argumento options nos servirá para crear el formulario con otras opciones de personalización.
El método getName() deberá retornar el identificador de nuestro formulario y este String puede tomar el nombre que queramos siempre y cuando sea único. Tenemos que tener en cuenta que este nombre será usado para los atributos “name” de los componentes de formularios. Por ejemplo vemos que tenemos un componente llamado “title” que hemos agregado en el método buildForm() por lo que la etiqueta creada será:
<input type="text" name="article_form[title]" />
Hay que notar que esta es la sintaxis para cargar datos en un array (usando los corchetes) por lo que “article_form” será simplemente un array con una clave asociativa “title” que contendrá el valor ingresado por el usuario. Esta sintaxis nos permite tener en un array todos los datos del formulario al hacer el submit.
Con esto lo que hemos hecho es crear la representación básica de nuestro formulario, diciéndole cual es el identificador del formulario y los campos que deberá contener.
[tipexperto titulo = “Nota”]Escribiendo los objetos Type NO definimos como será visualmente el formulario sino como será CONCEPTUALMENTE.[/tipexperto]
Invocación y Visualización del formulario
Para poder mostrar el formulario HTML en nuestra página debemos invocar a nuestra clase ArticleType desde nuestro controlador, o más específicamente desde el action que llama a nuestra página, para esto vamos a crear un action nuevo dentro de nuestro ArticulosController al que vamos a llamar newAction.
Primeramente creamos nuestra ruta en el archivo routing.yml
articulo_new: pattern: /articulo/new defaults: { _controller: MDWDemoBundle:Articulos:new }
Una vez creada nuestra ruta iremos a crear nuestro newAction en src\MDW\DemoBundle\Controller\ArticulosController.php (MDWDemoBundle:Articulos:new). Para esto agregamos el siguiente código:
//-- Al estar utilizando la clase ArticleType dentro de nuestro método no debemos olvidar importar el namespace al principio del archivo use MDW\DemoBundle\Form\ArticleType; //-- Agregar este método como uno nuevo public function newAction() { $articulo = new Articles(); $form = $this->createForm(new ArticleType(), $articulo); return $this->render('MDWDemoBundle:Articulos:new.html.twig', array( 'form' => $form->createView(), )); }
[tipexperto titulo = “Nota”]Un dato importante es que en el código de arriba hemos creado un nuevo $articulo desde un objeto vacío lo cual hará que el formulario se muestre vacío. Si queremos, por ejemplo en un formulario de modificación de registro, mostrar ya los datos del artículo a modificar esto simplemente implicaría obtener los datos desde la base de datos utilizando un DQL o el método find() que vimos en el capítulo anterior antes de pasarlo al método createForm().[/tipexperto]
El código que debe contener nuestro action es muy sencillo. Primeramente creamos un objeto Article y luego, por medio del método $this->createForm() invocamos a nuestro objeto ArticleType pasándole nuestro objeto recién creado $articulo, devolviéndonos un objeto de tipo formuario. Finalmente invocamos a la vista como siempre hacemos y pasamos como parámetro el resultado de ejecutar $form->createView().
Con esto ya seremos capaces de ver el código de nuestra vista MDWDemoBundle:Articulos:new.html.twig que de acuerdo a este nombre lógico debemos crear el archivo new.html.twig dentro de la carpeta src/MDW/DemoBundle/Resources/views/Articulos/ con el siguiente código:
<form action="{{ path('articulo_new') }}" method="post"> {{ form_widget(form) }} <input type="submit" /> </form>
La creación de la etiqueta formulario la hacemos normalmente así como también el botón de submit. Lo único importante aquí es que el action del form debe apuntar a la misma página por lo que creamos el link por medio de path(‘articulo_new’).
La parte mágica está en {{ form_widget(form) }} donde, por medio de form_widget y Twig, pasamos como argumento la variable que nuestro action nos ha enviado y se imprime en la página el código necesario para nuestro formulario. Es decir que veremos el formulario al ingresar a la dirección: http://localhost/Symfony/web/app_dev.php/articulo/new
Si miramos el código HTML veremos lo siguiente:
<form action="/Symfony/web/app_dev.php/articulo/new" method="post"> <div id="article_form"> <input type="hidden" id="article_form__token" name="article_form[_token]" value="62bc1a503b32de46b8755e9a5f5d8855bc8eb877" /> <div> <label for="article_form_title" class=" required">Title</label> <input type="text" id="article_form_title" name="article_form[title]" required="required" maxlength="20" /> </div> <div> <label for="article_form_author" class=" required">Author</label> <input type="text" id="article_form_author" name="article_form[author]" required="required" maxlength="255" /> </div> <div> <label class=" required">Created</label> <div id="article_form_created"> <select id="article_form_created_year" name="article_form[created][year]" required="required"> <option value="2007">2007</option> <option value="2008">2008</option> ... </select> </div> </div> </div> <input type="submit" /> </form>
[tipexperto titulo = “Nota”]Muy importante es notar que a parte de los campos que hemos agregado para que sean mostrados en el $builder, también se muestra un campo article_form[_token] con un valor aleatorio. Esto lo hace automáticamente para luchar contra uno de los ataques más usados por los hackers llamado CSRF. Con eso ya vemos como Symfony nos propone ya un estándar de seguridad. A esta seguridad también se suma que por medio de Doctrine también tenemos validado los problemas de SQL Injection.[/tipexperto]
Si miramos el código podemos notar los atributos “name” como los explicamos arriba y también vemos que mágicamente el campo “created” se muestra como un campo para seleccionar una fecha. Esto es debido a que el framework reconoce el tipo de input a mostrar ya que sabe, por medio del objeto Articles, que esa propiedad es una fecha. Esto es tremendamente útil ya que muchas veces podría ya reconocer que type agregarle a las etiquetas input, pero si necesitamos definir por nosotros mismos el atributo type lo hacemos agregando un segundo argumento al momento de agregar el campo al $builder:
public function buildForm(FormBuilder $builder, array $options) { $builder->add('title') ->add('author', 'checkbox') ->add('created'); }
Mientras que si necesitamos hacer que un campo no sea obligatorio lo hacemos enviando un array como tercer argumento ya que por defecto todos los campos son puestos como requeridos con validaciones HTML5:
public function buildForm(FormBuilder $builder, array $options) { $builder->add('title') ->add('author', 'text', array('required' => false)) ->add('created'); }
Como vemos el formulario HTML es impreso directamente en la página usando el {{ form_widget(form) }} incluyendo divs que nos ayudarán a formatear por medio de CSS y mejorar la estructura de mismo pero en caso de querer crear formularios más complejos en diseño también se cuentan con las siguientes opciones que para no extender mucho este capítulo lo veremos quizá en otra entrega:
- form_errors(form): Renderiza lo errores que se encuentren en el formulario.
- form_rest(form): Renderiza los campos de formulario que no hayan sido agregados manualmente con el form_row.
- form_row(form.field): Renderiza un campo específico dentro de un div.
- form_errors(form.field): Renderiza el error para un campo específico.
- form_label(form.field): Renderiza la etiqueta label para un campo específico.
- form_widget(form.field): Renderiza un campo específico.
Procesamiento del Formulario
Ahora que ya vimos como mostrar el formulario en la página y habiendo dicho el action de un form va al mismo action para ser procesado, entremos en detalle de las modificaciones que tenemos que tener en cuenta en el código original dentro del método newAction().
Lo primero que tenemos que pensar es que si para procesar el formulario llamamos al mismo action, ¿Cómo sabemos cuándo mostrar el formulario y cuándo procesarlo?. La respuesta es bien sencilla, cuando el request fue de tipo GET lo deberíamos de mostrar pero en caso de que se haya dado click en el botón submit se ejecuta un request de tipo POST y por lo tanto se debería procesar. Veamos el código modificado de nuestro newAction():
public function newAction() { //-- Obtenemos el request que contendrá los datos $request = $this->getRequest(); $articulo = new Articles(); $form = $this->createForm(new ArticleType(), $articulo); //-- En caso de que el request haya sido invocado por POST // procesaremos el formulario if($request->getMethod() == 'POST') { //-- Pasamos el request el método bindRequest() del objeto // formulario el cual obtiene los datos del formulario // y los carga dentro del objeto Article que está contenido // también dentro del objeto Type $form->bindRequest($request); //-- Con esto nuestro formulario ya es capaz de decirnos si // los dato son válidos o no y en caso de ser así if($form->isValid()) { //-- Procesamos los datos que ya están automáticamente // cargados dentro de nuestra variable $articulo, ya sea // grabándolos en la base de datos, enviando un mail, etc //-- Finalmente, al finalizar el procesamiento, siempre es // importante realizar una redirección para no tener el // problema de que al intentar actualizar el navegador // nos dice que lo datos se deben volver a reenviar. En // este caso iremos a la página del listado de artículos return $this->redirect($this->generateURL('articulos')); } } return $this->render('MDWDemoBundle:Articulos:new.html.twig', array( 'form' => $form->createView(), )); }
Como vemos en las explicaciones del código casi todo es automáticamente realizado por el objeto ArticleType quién al conocer el request ya nos devuelve el mismo objeto original $articulo que le fue entregado en el createForm(new ArticleType(), $articulo);.
En caso de que los datos no sean válidos y el método isValid() retorne false seguirá el hasta mostrar nuevamente el formulario llamando al método $this->render() y el {{ form_widget(form) }} puesto en nuestra misma vista se encargará de mostrar los errores de validación.
[tipexperto titulo = “Nota”]Symfony2 agrega las validaciones de los formularios en HTML5 y del lado del servidor. Si el navegador no soporta las validaciones por medio de HTML5 el método isValid() lo valida en el servidor y al retornar la respuesta por el método render() se mostrarán los mensajes de validación del servidor. Puede que tu navegador ya acepte las validaciones HTML5 por lo que al intentar enviar los datos no notes la validación del lado del servidor aunque lo mismo se están realizando.
Por ejemplo el campo $title está puesto como <input type=”text” id=”article_form_title” name=”article_form[title]” required=”required” maxlength=”255″ pattern=”.{10,255}” /> donde se puede ver que las validaciones de HTML5 fueron ya puestas.
Si no tienes un navegador que NO soporte HTML5 para probar como se muestran los mensajes de validación del servidor puedes, utilizando el Firebug del Firefox, eliminar el texto required=”required” maxlength=”255″ pattern=”.{10,255}” de la etiqueta input y luego presionar el botón de submit
Como verás, los hackers que quieren usar esta técnica también serán detenidos por las validaciones del servidor.[/tipexperto]
Resumen Final
En este capítulo hemos trabajado muchísimo viendo dos temas sumamente importantes: la validación de los Entities y los formularios.
Para las validaciones hemos hablado sobre los @Asserts, simples anotaciones que realizan validaciones poderosas con poco código y vemos que Symfony2 ya nos provee de la gran mayoría que necesitaremos usar.
Hablando sobre los formularios hemos notado la gran diferencia de diseñar los formularios y programar los formularios por medio de clases. Me gusta decir que en Symfony, el concepto de un formulario NO es simplemente introducción de texto sino introducción de texto VÁLIDO para la aplicación, libre de los problemas que hoy se tienen al crear un formulario a mano y tener que recordar pelear con ataques CSRF, XSS, SQL Injection y cambios en caliente con herramientas como Firebug.
El sub-framework de formularios es uno de los que más me hicieron sentir la diferencia entre usar un framework y no hacerlo y todavía hay muchas otras herramientas que nos permite usar como los formularios embebidos.
En el primer capítulo de esta guía hablamos sobre que uno de los objetivos de Symfony es plantear que cada cosa debe ir en su lugar, respetando el concepto del MVC. Con esto podemos ver que no solo podríamos tener un equipo de desarrollo, con personas expertas en cada área, trabajando con el modelado, otras con los controladores y a los diseñadores en la vista, sino que también podríamos hablar de personas que trabajen netamente en la creación de los formularios de la aplicación.
En el siguiente capítulo hablaremos sobre la integración de Ajax en nuestras aplicaciones hechas con Symfony2.
Me he quedado por el 3º tutorial, porque no tengo mucho tiempo y me gusta leerlo y entenderlo tranquilamente. Por lo que voy viendo por encima esto tiene un potencial increíble. Ya había oído lo que estoy diciendo, pero como en todo hasta que no lo ves no lo terminas de creer.
Muchas gracias por tu esfuerzo y dedicación y lo bien explicado que está. Da gusto encontrar material de este calibre por internet 😀
Gracias por el comentario jask. Es así mismo como lo dices, la forma de trabajo que propone es muy interesante y justamente en este capítulo se nota la parte de seguridad también.
Nuevamente muy buen capítulo para seguir despejando varias dudas en Symfony2
Muy buen manual, esperando la integracion de ajax
[…] de formularios VN:F [1.9.16_1159]please wait…Rating: 0.0/5 (0 votes cast)Fue publicado el décimo capítulo de la Guía de Symfony2 en maestros del Web y en este capítulo les hablo sobre uno de los temas […]
Me gusta mucho mas como explicas tu… es muy entendible la guia. Muchas felicidades, pero tengo una duda.
Si en Entity/Article pongo los Assert’s en private title como en el ejemplo
* @Assert\NotNull(message=”Debe escribir un titulo”)
* @Assert\MaxLength(255)
* @Assert\MinLength(5)
Al correr la aplicacion me marca el siguiente error:
[Semantical Error] The annotation “@Assert\NotNull” in property MDW\DemoBundle\Entity\Articles::$title was never imported. Did you maybe forget to add a “use” statement for this annotation?
Si se lo quito, corre sin problemas… Que podria ser??? muchas gracias.
Ricardo que bueno que lo estes entendiendo. Debes estar olvidando agregar la importación del @Assert:
use Symfony\Component\Validator\Constraints as Assert;
Tienes que ponerlo arriba con los otros “use”.
Listo Muchas gracias….. Espero termines tu con los otros temas, saludos.
Buen trabajo, esperamos el de integración de Ajax
Muy buen manual!!! una duda que tengo, al crear los formularios, el identificador y el nombre de los input siempre se forma concatenando el nombre del form y el suyo propio?? o hay alguna forma de crearlos sin que se concatene. Saludos
En versiones anteriores a la 2 se podía sin problema directamente dejando de agregar el método getName() que dice cual será el identificador del formulario y ese identificador se concatena para formar un array. Esto se vio que era sumamente útil ya que los parámetros no viajaban dentro request separados como si fueran independientes sino que van todos juntos (dentro de un array) por ser parte del mismo objeto inicial (El formulario).
Para la versión 2 de Symfony vemos que el ArticleType hereda de AbstractType que a su vez implementa la interfaz FormTypeInterface que tiene el método getName() por lo que no puedes dejar de agregar el método.
En conclusión, el framework está preparado para concatenarlo siempre basado en una muy buena práctica que demostró su utilidad desde siempre y de ahí la decisión.
Espero haber contestado tu pregunta.
Me encanta este manual, empecé sin saber nada de Symfony y ahora gracias a ustedes entiendo mucho más. estoy a años luz de ser un experto, pero voy por buen camino gracias a esta guía.
Una cosa sobre este capitulo, me tope con que el new.html.twig tiene que ir en la carpeta default ya que asi se invoca en el controlador, sin embargo en la guía ustedes dicen que tiene que ir en la carpeta articulos (siempre hablando de la carpeta views), puede ser que este malinterpretando mal algo? yo lo pongo en default y me funciona.
Muchas gracias y sigo esperando los demás capitulos para ir avanzando más en mi aprendizaje, mientras me entretengo con los que ya están publicados.
Mil disculpas Nicolás. Fue un error mío al copiar el código. En realidad está bien que el archivo new.html.twig lo crees dentro de src\MDW\DemoBundle\Resources\views\Articulos\new.html.twig.
Mi error fue en el método newAction() del ArticulosController.php, donde en el return $this->render(‘MDWDemoBundle:Default:new.html.twig’,… estaba apuntando a Default y debería ser Articulos de esta manera $this->render(‘MDWDemoBundle:Articulos:new.html.twig’,….
Ya está corregido, gracias por avisar
Estimado muy buen tutorial, he venido usando Symfony2 desde hace un tiempo y de verdad es muy potente, pero en el tema de los formularios no he podido hacer lo siguiente:
1.- Select dependientes, por ejemplo el clásico Estado y ciudad, donde dependiendo del estado que elija aparece otro select con las ciudades de dicho estado.
2.- Pre procesamiento de los datos, por ejemplo poner todo el texto en minúsculas antes de guardarlo en la base de datos
saludos!!
Mario Valdivia, buscando rápidamente en google encontré en el grupo oficial de Symfony una discusión sobre el tema https://groups.google.com/forum/?fromgroups#!topic/symfony2-es/KGftw03bSeA quizá te sirva. Para los combos dependientes lo ideal sería que entiendas como funciona el Ajax y te será sencillo.
Para el segundo punto lo debes hacer en el método que tienes para guardar los datos, despues del isValid y antes de guardarlos.
Me pierdo al intentar algo, cuando en el controlador proceso el formulario, cómo hago por ejemplo para tomar el nombre del artículo e ignorar la fecha(porq por su defecto ya viene cargado con una fecha), y buscar dicho artículo cuando presiono enviar consulta. Yo pensaba de hacer un find con el titulo, pero no se bien como poner el codigo.
Otra cosa, cómo puedo hacer para que al abrir el directorio new , ya me venga cargado los artículos en el formulario, para por ejemplo ya seleccionar un titulo cargado y me muestre todos los datos.
Gracias por la ayuda, espero se haya entendido.
Nicolás con relación al segundo tema. Para mostrar ya cargado el formulario, en lugar de hacer $articulo = new Articles(); cargas ese $articulo buscando primeramente el artículo con un find utilizando su id por ejemplo y ya.
Con relación al primer tema no estoy entendiendo a que te refieres con “Tomar el nombre del articulo”. Te refieres a $articule->getTitle()?
Con respecto al find, hice q en el link se ponga por ejemplo articulos/new/1 por ejemplo para que cuando cargue el formulario ya tenga los datos del articulo 1. pero me tira el siguiente error (An exception has been thrown during the rendering of a template (“The “articulo_new” route has some missing mandatory parameters (“id”).”) in MDWDemoBundle:Articulos:new.html.twig at line 1. ) Me fije y es el path ({{ path(‘articulo_new_a’) }}) pero no se como poner para que me tome el id. Lo que me gustaria hacer en realidad es que al cargar el formulario lo haga con todos los articulos que tenga en la base de datos, no solo uno en particular, pero ahora ni siquiera me sale eso,si me ayudas en las dos cosas mejor
Con lo segundo, lo que decia es, que cuando pongo en el formulario el nombre del articulo y toco enviar, quiero que me muestre el articulo en cuestion, si hay mas de un articulo con el mismo nombre, que muestre ambos.
Perdon por lo largo del comentario.
Muchas gracias por tu tiempo
La verdad que es un poco complicado darte una mano porque no se que código estás usando pero te doy unos tips que por ahí te ayuden
1. Cuando vas a necesitar pasar un parámetro por URL, en el routing tienes que usar los comodines que se colocan dentro de las llaves {}. Fíjate esto en el capítulo 3 – Creando páginas con Symfony 2 en el paso 1 del segundo ejemplo.
2. Para obtener ese parámetro en el controller para poder usarlo lo recibes como argumento del action o invocas al objeto Request. Esto está en el paso 2 del mismo ejemplo.
3. Para obtener de la base de datos todos los artículos usas el método findAll() y para obtener uno solo find(__ID__) pasándole el id que obtuviste en el paso anterior.
4. El error “The “articulo_new” route has some missing mandatory parameters (“id”).”: traducido significa que en tu routing definiste un ruta con un parámetro y en el link no estás poniendo el parámetro
5. Con relación a lo que dices de cargar un formulario con TODOS los artículos de la base de datos no me da mucho sentido ya que un formulario debería tener un artículo para modificar por cada vez.
6. Con relación a lo último, si le das enviar a un formulario al final del método que procesa el formulario le das redirect y vas a una página que reciba el ID como el ejemplo de la página articulo.html.twig que se muestra en el capítulo 9 – Manipulando datos con Doctrine
Mi recomendación es que vayas por parte, entiende bien cada capítulo y haz pruebas para cada uno de ellos. La curva de aprendizaje de un framework tan robusto lleva su tiempo por lo que no te apures y con la práctica lo entenderás
Querido!!! muy buen post, sin saber nada de symfony, este post me a ayudado un monton. Muchisimas Gracias!!!1.
Tengo una consulta como hago para cambiar el nombre en esto:
$builder->add(‘title’)
->add(‘author’)
por ejemplo no quiero que sea title sino titulo, por que este es el mismo nombre de bd y me sale un error como que no se encuentra.
muchas gracias
Sergio usas el nombrede tu Entity. En todo caso al crear el Entity le creas el nombre en castellano a la propiedad
hola Sergio para cambiar en el Formulario el nombre por default que se establece en Entity, lo que debes hacer es agregar un array con el label:
$builder->add(‘title’, ‘text’, array(‘label’ => ‘Titulo’))
de esta forma no modificas al Entity!.
Espero haberte ayudado!!!
Excelente Articulo!!!! Gracias Juan Ardissone! ahora a PRACTICAR!
Hola, exelente tutorial, te queria preguntar, podes agregarle algo acerca del upload de archivos, o si tenes alguna recomendacion de libro o info sobre eso.
Hola Nahuel. Te dejo algunos enlaces
http://symfony.com/doc/current/reference/forms/types/file.html –> forma normal
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html –> utilizando doctrine
Antes de nada felicitarte por el tutorial. En cuanto a los formulario en el ejemplo queda bastante claro la forma de hacer formularios basados en Entidades. Pero ¿qué diferencia hay para formularios que no dependen de una entidad? Por ejemplo, para el clásico formulario de contacto de una Web…
Excelente tutorial. Me preguntaba si tenias alguna recomendacion sobre la creacion de formularios que no dependan de un solo entity sino de multiples! gracias d antemano
Genial el capitulo, estoy enganchadísimo y espero terminarme mañana lo que me queda y a practicar y practicar. Respecto al tema del layout del formulario, me gustaría saber si puedo obtener por separado los inputs y demás elementos del formulario para poderles dar formato, por ejemplo para meterlos en una lista desordenada (o en una tabla), Es decir en vez de escribir el formulario de este modo:
{{ form_widget(form) }}
Poder imprimir cada elemento del mismo por separado, porque sino no se como hacer para tabular cada item con o con o por ejemplo meter una clase a un item en concreto, por ejemplo al input “title” o a los títulos de los campos etc etc…
Gracias por este gran aporte.
Si rob. Es lo que comentaba con el widget form_row(form.field) y los otros que figuran en este capítulo
Gracias Juan Ardissone , no me había fijado en esa parte. Gracias por esta guía , ya que es la introducción perfecta que estaba buscando para inciarme a saco en el mundo Symfony. Sino es mucho pedir, ¿recomiendas algún libro sobre Symfony2?.
Gracias
Buenos dias y Saludos, En verdad antes de nada debo felicitarlo por tu excelente manera de enseñar, en verdad no sabia nada pero nada, pero he llegado hasta este nivel sin problema hasta que me esta dando un error cuando intento accesar a:
http://localhost/Symfony/web/app_dev.php/articulo/new
me da el siguiente error :Cannot import resource “C:\wamp\www\Symfony\src\MDW\DemoBundle/Resources/config/routing.yml” from “C:\wamp\www\Symfony\app/config\routing.yml”
Cuando hablas sobre :
Si miramos el código HTML veremos lo siguiente:
………
A que archivo te refieres??? Ante nada disculpa a lo mejor es una tonteria pero en verdad no tengo nada de experiencia en Web
Buenas Tardes y Saludos, ya logre solucionar el error, aunque ya me aparece el formulario para ingresarle el titulo, el autor y la fecha, cuando le doy click a Submit Query, no graba los datos en la tabla, y ademas me sale un error de la pagina que no puede accesar…
Hola William.
Fijate en el action de tu form si estás poniendo bien la dirección a que página ir. Lo que puedes hacer también es ir sacando código y probando de a poco (como si fuera un debug) para entender que está pasando. Si pones algún mensaje de error por ahí pueda darte una mano.
Buenas tardes, tengo unas consultas:
La primera es referido a la validación de campos, yo tengo hecho mis entidades y en muchas no les puse ningun asset, es decir que cuando me fijo en la base de datos tengo como campo predeterminado a “ninguna”. El tema es que cuando envio en formulario me manda el mensaje de “debe completar este campo”, no quiero q haga esto, vi por aca que defino en la creacion del formulario como no requerido y tengo q indicar el tipo de campo que es. El tema es que yo tengo en unos campos los siguiente:
->add(‘unidad_medida’,null,array(‘label’=> ‘unidad de medida’))
de esta forma puedo darle el nombre que quiero a mi label, ahora, como puedo poner el no requerido junto con esto?.
La segunda consulta es sobre los acentos y caracteres especiales, ya que en mi formulario se muestra lo que yo defini en el label, pero no puedo lograr ponerle un acento.
Muchas gracias por la ayuda, espero no haber sido muy complicado con la explicacion.
Hola Nicolás.
No entendí muy bien esta parte “en la base de datos tengo como campo predeterminado a ‘ninguna’“.
De todas maneras te comento que si no le especificas nada a los campos por defecto serán requeridos por lo que si no quieres que sea requerido tienes que decirle que no lo es. Fijate en el ejemplo que puse para ver como poner mensajes personalizados, los tienes que poner en el assert.
Por el lado de los acentos muchas cosas influyen. Puedes probar grabar el archivo con formato utf8. Algunos editores como el notepad++ tienen esa opción al darle “guardar como”.
Hola, Juan
Ya hace ratico que publicaron esta parte del tutorial pero hasta ahora es que yo la veo : )
Tengo la misma inquietud de william. A mí me funciona perfectamente y no me manda errores pero no guarda un nuevo artículo en la BD después de darle al “submit”.
De hecho en el “newAction” no veo ninguna sentencia que guarde los datos… Corrígeme si estoy mal.
Espero que me puedas colaborar!
Gracias de antemano ; )
Hola muy buen manual todo esta explicado genial las rutas funcionan perfecto pero me salido un problema
cuando quiero visualizar la pagina new/ me sale este error
The Symfony\Component\Locale\Stub\StubIntlDateFormatter::setLenient() is not implemented. Please install the ‘intl’ extension for full localization capabilities.
Pero el error quita cuando quito este codigo desde ArticlesType.php
$builder->add(‘title’)
->add(‘author’)
->add(‘created’);
ya lo he resuelto pero me salido otro problema que no se puede encontrar la ruta de (‘articulos’)
return $this->redirect($this->generateURL(‘articulos’)); he mirado todo y esta perfecto
Que bueno que ya terminaron la guía, felicidades.
Excelente manual de symfony2, muy sencillo, claro y conciso. Esto de los formularios de Symfony 2 e verdad que me ha parecido excelente, dado todas las ventajas del manejo de la seguridad y la automatización para las validaciones y las notificaciones de error al llenar el formulario.
También la forma en que esta esquematizado el curso es realmente excelente. Muchas gracias y mil éxitos.
Saludos desde Mérida – Venezuela.
Hi, yup this post is actually nice and I have learned lot of things
from it on the topic of blogging. thanks.
En Symfony 2.1 el codigo seria el siguiente .
add(‘title’)
->add(‘author’)
->add(‘created’);
}
public function getName()
{
return ‘article_form’;
}
}
Saludos espero les sirva .
namespace MDW\DemoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(‘title’)
->add(‘author’)
->add(‘created’);
}
public function getName()
{
return ‘article_form’;
}
}
William, poseo el mismo error, Cannot import resource “C:\wamp\www\Symfony\src\MDW\DemoBundle/Resources/config/routing.yml” from “C:\wamp\www\Symfony\app/config\routing.yml”, como es que lo has solucionado?
Muchas gracias,
Si quisieramos utilizar un formulario para enviar un email simplemente (sin nada que ver con la BBDD), entiendo que tendrías que crar una entidad de tipo ClienteComentario (por poner un ejemplo) con sus assets y tal de cara a validar el formulario. Pero cómo harías para que hacer el update de doctrine no se te guarde en la BBDD? Simplemente quitándole los parámetros @ORM? Muchas gracias por adelantado.
xbox360 comes to him who waits rofl
quisiera saber como hacer para insertar datos a la base de datos, que me tome los datos que ingreso en el formulario…
Disculpen, tengo un problema con la creacion del formulario, y no se muy bien que hice mal, me da este error
Item “id” for “” does not exist in MDWDemoBundle:Default:articulo.html.twig at line 1
500 Internal Server Error – Twig_Error_Runtime
al acceder a este http://localhost/Symfony/web/app_dev.php/articulo/new
Podrian colaborarme porfavor?
Hola de nuevo, en esta ocasión quiero mejorar el logueo de los usuarios, que a través de un formulario, el usuario introduzca sus credenciales y dependiendo si es un usuario, o un administrador, se dirija a su página correspondiente, para ello estoy haciendo una clase LoginListener, el problema lo tengo a la hora de configurar el fichero services.yml
services:
login_listener:
class: Gestion\OfertaBundle\Listener\LoginListener
arguments: [@security.context, @router]
tags:
– { name: kernel.event_listener, event: security.interactive_login }
– { name: kernel.event_listener, event: kernel.response }
el error es el siguiente:
ScannerException while scanning for the next token we had this found character @(64) that cannot start any token
en la línea arguments no reconoce @,
Llevo tiempo intentando solucionarlo y no lo consigo, el IDE con el que trabajo es NetBeans 7.1.2.
Me podrian ayudar.
Muchas Gracias, de nuevo gracias por su manual.
Hola, excelente este curso. Probando este capitulo tengo un problema que no se resolver… en ArticleType.php tengo
namespace MDW\DemoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ArticleType extends AbstractType{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add(‘title’)
->add(‘author’)
->add(‘created’);
}
public function getName()
{
return ‘article_form’;
}
}
y no me corre cuando lo abro … me da el siguiente error el wamp…
( ! ) SCREAM: Error suppression ignored for
( ! ) Fatal error: Declaration of MDW\DemoBundle\Form\ArticleType::buildForm() must be compatible with Symfony\Component\Form\FormTypeInterface::buildForm(Symfony\Component\Form\FormBuilderInterface $builder, array $options) in C:\wamp\www\Symfony\src\MDW\DemoBundle\Form\ArticleType.php on line 9
Call Stack
# Time Memory Function Location
1 0.0005 144224 {main}( ) ..\app_dev.php:0
2 0.0800 2401016 Symfony\Component\HttpKernel\Kernel->handle( ) ..\app_dev.php:28
3 0.2157 3736232 Symfony\Bundle\FrameworkBundle\HttpKernel->handle( ) ..\bootstrap.php.cache:612
4 0.2158 3737200 Symfony\Component\HttpKernel\HttpKernel->handle( ) ..\bootstrap.php.cache:1561
5 0.2158 3737232 Symfony\Component\HttpKernel\HttpKernel->handleRaw( ) ..\bootstrap.php.cache:1385
6 0.2897 5023952 call_user_func_array ( ) ..\bootstrap.php.cache:1421
7 0.2897 5024048 MDW\DemoBundle\Controller\ArticulosController->nuevoAction( ) ..\bootstrap.php.cache:1421
8 0.2940 5114640 Symfony\Component\ClassLoader\DebugClassLoader->loadClass( ) ..\bootstrap.php.cache:0
9 0.2943 5118328 require( ‘C:\wamp\www\Symfony\src\MDW\DemoBundle\Form\ArticleType.php’ ) ..\DebugClassLoader.php:82
no encuentro el problema, me podras dar una mano?
saludos y desde ya muchas gracias