logo cosasdedevs
Parte 7: Cómo crear formularios con Symfony 6

Parte 7: Cómo crear formularios con Symfony 6



My Profile
Mar 14, 2023

En el tutorial de esta semana vamos a ver como crear formularios con Symfony 6, guardar información, validar datos y restringir para que solo lo puedan utilizar usuarios autenticados.

👋¡Hola! Ya estamos en la séptima parte de esta serie de tutoriales, la web ya va tomando forma y cada vez queda menos para dar por finalizada esta primera parte. Tengo más ideas pensadas para trabajar con este framework. Una serie para crear una API y otra para crear una web aplicando DDD, arquitectura hexagonal y CQRS así que estate atento para no perderte nada.

Antes de empezar 🛑 y como siempre, si te has perdido alguno de los tutoriales de esta serie, aquí te dejo todos los tutoriales escritos hasta ahora:

Crear un formulario para guardar comentarios

Symfony tiene un sistema para facilitarnos trabajar con formularios. Desde una clase PHP podemos configurar la entidad con la que trabaja, validación de datos y configuración para después renderizarla en una plantilla. Esto nos ahorrará el trabajo repetitivo que tendríamos que hacer si utilizamos formularios normales.

Una vez tengamos la clase, podemos enviarla por parámetro desde el controlador a la plantilla y se renderizará automáticamente.

En nuestro caso vamos a crear un formulario para que un usuario autenticado pueda crear comentarios en un post de tal forma que ese comentario quede relacionado tanto con el post como con el usuario.

Para ello, el primer paso es crear la clase para el formulario y esto lo haremos desde la terminal con el siguiente comando:

php bin/console make:form

Se nos abrirá el asistente y nos preguntará que nombre queremos darle al formulario. Lo llamaremos CommentType. Después nos preguntará con qué entidad estará relacionado, en este caso, con la entidad Comment.

The name of the form class (e.g. DeliciousGnomeType):
> CommentType

The name of Entity or fully qualified model class name that the new form will be bound to (empty for none):
> Comment

created: src/Form/CommentType.php

Al finalizar nos creará un archivo llamado src/Form/CommentType.php. Este archivo contendrá un método llamado buildForm el cual contiene una variable $builder con los campos de nuestra tabla Comment.

En esta variable es donde podremos configurar nuestro formulario para que por ejemplo un campo tenga atributos adicionales, modificar o eliminar la etiqueta por defecto, también validaciones y muchas más cosas que podéis ver en la documentación que os dejo aquí 👇

https://symfony.com/doc/current/forms.html

Abrimos el archivo CommentType y vamos a añadir las siguientes modificaciones:

...
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
...

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder
        ->add('content', null, ['label' => false])
        ->add('Enviar', SubmitType::class, [
            'attr' => ['class' => 'button']
        ])
    ;
}

Lo primero que he hecho es importar la clase SubmitType. Utilizaremos esta clase para poder crear un botón de enviar en el formulario.

Después, ya en el $builder he eliminado todos los campos que no necesitamos, ya que se asignarán automáticamente al crear el comentario.

En add('content') he añadido unas pequeñas modificaciones. Como segundo parámetro dentro del add envío null. Esto significa que al convertirlo en formulario de HTML asignará el tipo de input por defecto a un campo de este tipo en la base de datos. Por ejemplo, para el content como en la base de datos es un longtext, Symony lo renderizará como un textarea aunque nosotros podríamos indicar otro tipo cambiando el null por el tipo que queramos.

Te dejo un enlace a los distintos tipos 👇

https://symfony.com/doc/current/reference/forms/types.html

El último parámetro recibe un array de configuración y en él, indico que la label será igual a false. De esta manera evitaremos que renderice una label junto al textarea.

Por último, añado el botón de enviar. Como primer parámetro indico el texto que quiero que aparezca en el botón. Después y ya que este campo (como es obvio) no existe en la base de datos, le tengo que indicar obligatoriamente qué tipo de campo quiero que sea, en este caso SubmitType. Para finalizar, en los parámetros de configuración he añadido la clase CSS que quiero que utilice este botón para que se vea bonito.

Enviar el formulario desde el controlador

Ahora que tenemos configurado nuestro formulario, es hora de enviarlo a nuestra plantilla. También necesitaremos crear un nuevo método para guardar los comentarios, así que abrimos el archivo src\Controller\BlogController.php y añadimos los siguientes cambios:

...
use App\Form\CommentType;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Comment;
...

#[Route('/posts/{slug}', name: 'get_post', methods: ['GET'])]
public function getPost(Post $post): Response
{
    $form = $this->createForm(CommentType::class);
    return $this->render('blog/post.html.twig', [
        'post' => $post,
        'form' => $form->createView(),
    ]);
}

#[Route('/posts/{slug}', name: 'save_comment', methods: ['POST'])]
public function saveComment(Request $request, Post $post, EntityManagerInterface $entityManager): Response
{
    $comment = new Comment();
    $comment->setUser($this->getUser());
    $comment->setPost($post);
    $comment->setCreatedAt(new DateTimeImmutable());

    $form = $this->createForm(CommentType::class, $comment);
    $form->handleRequest(($request));

    if ($form->isSubmitted() && $form->isValid()) {
        $entityManager->persist($comment);
        $entityManager->flush();

        return $this->redirectToRoute('get_post', ['slug' => $post->getSlug()]);
    }
    return $this->render('blog/post.html.twig', [
        'post' => $post,
        'form' => $form->createView(),
    ]);
}

Como ves, primero he añadido unos cuantos uses como la clase de nuestro formulario, la clase Request de Symfony, el EntityManagerInterface que utilizaremos para persistir nuestros datos y la entidad Comment.

En el método getPost el cual ya existía anteriormente, he añadido en el enrutamiento que solo permita el método GET y después creamos una instancia de la clase del formulario y lo enviamos por parámetro a la plantilla.

Por último, tenemos el método saveComment el cual se encargará de guardar los comentarios cuando un usuario pulse en enviar y tiene la misma ruta que el método anterior, pero en este caso el método será de tipo POST.

En este método creamos una instancia de la entidad Comment y añadimos los parámetros que no vamos a recibir del formulario como son el usuario, el post y la fecha de creación

Con la línea $form = $this->createForm(CommentType::class, $comment); creamos una instancia de nuestra clase de formulario ya con la información que hemos asignado a la entidad en el paso anterior y con la línea $form->handleRequest(($request)); verificamos si ha sido enviado el formulario o no, además asigna al objeto $comment el campo content enviado por el formulario.

Después realizamos la doble validación de que el formulario ha sido enviado y es válido (if ($form->isSubmitted() && $form->isValid())) y si es así, persistimos los datos y redirigimos al post.

Si hay algún error, volvemos a la página del post y mostrará el error al validar el formulario.

Validación de formularios

Actualmente, en nuestro formulario no tenemos ninguna validación aparte de que no nos envíe un comentario vacío. Para resolver eso, podemos usar el sistema de validación de Symfony. Este nos permitirá desde la entidad añadir validaciones y restricciones.

En nuestro caso quiero limitar con un número mínimo y máximo el tamaño de un comentario, por ejemplo un mínimo de 5 caracteres y un máximo de 500. Para implementar este control, vamos al archivo src\Entity\Comment.php y añadimos los siguientes cambios:

...
use Symfony\Component\Validator\Constraints as Assert;
...

#[ORM\Column(type: Types::TEXT)]
#[Assert\Length(min: 5, max: 500)]
private ?string $content = null;

Al añadir la línea #[Assert\Length(min: 5, max: 500)], para el campo content validará que el tamaño esté en ese límite de caracteres y si no, enviará un mensaje de error.

Si queréis aprender más acerca de las validaciones os dejo en enlace a la documentación 👇

https://symfony.com/doc/current/validation.html

También podéis añadir validaciones adicionales desde el $builder en la clase del formulario, os dejo la info 👇

https://symfony.com/doc/current/forms.html#form-type-options

Renderizado del formulario

Ya lo tenemos todo casi listo, así que es hora de renderizar nuestro formulario en la plantilla y así poder probar que todo funciona correctamente, pero antes vamos a añadir unos nuevos estilos para que todo se vea mejor. Para ello vamos al archivo assets\styles\app.css y añadimos lo siguiente:

.form-blog div textarea {
    @apply py-1 px-2 border-slate-300 border rounded w-full h-28;
}

.form-blog a {
    @apply text-blue-400 underline;
}

.form-blog a:hover {
    @apply text-blue-500;
}

.form-blog div ul li {
    @apply text-red-500 font-semibold;
}

Una vez hecho esto, vamos a la template para pintar nuestro formulario. Para ello abrimos el archivo templates\blog\post.html.twig y añadimos los siguientes cambios:

<div class="m-1 text-left">
  <hr>
  <div class="form-blog">
      {% if app.user %}
          <p class="text-left">
              Escribe un comentario <strong>{{ app.user.username }}:</strong>
          </p>
          {{ form(form, { 'action': path('save_comment', { slug: post.slug }) }) }}
      {% else %}
          <p class="text-left py-2">
          Debes estar autenticado para escribir un comentario <a href="{{ path('app_login') }}">Login</a>
          </p>
      {% endif %}
  </div>
  <hr>
  <h3 class="text-lg font-semibold py-2">Comentarios</h3>
  <hr>
  <div class="my-3 text-left">
      {% for comment in post.comments %}
          <div class="p-3">
              <div>Escrito por: <strong>{{comment.user.username}}</strong></div>
              <div class="text-sm text-stone-500">{{comment.content}}</div>
          </div>
          <hr>
      {% endfor %}
  </div>
</div>

Por si no identificas muy bien la parte sustituida, lo que he hecho es añadir, justo antes de los comentarios, un nuevo div con un bloque if. En {% if app.user %} comprueba si hay un usuario autenticado. Si es así, pintamos el formulario con esta línea {{ form(form, { 'action': path('save_comment', { slug: post.slug }) }) }} en la cual indicamos la ruta donde debe enviar la información. Ya por defecto añade que lo envíe por método post así que no tenemos que añadirlo.

Si el usuario no está autenticado, entra en el bloque else y muestra el aviso de que para comentar hay que acceder con su usuario.

Por lo demás, solo he añadido unos cuantos cambios de diseño para que se vea mejor.

Si ahora accedes a un post, podrás ver una opción u otra dependiendo de si estás autenticado o no. Puedes probar a escribir unos cuantos comentarios y también la validación de tamaño mínimo y máximo para verificar que todo está 👍. También te invito a que le des una vuelta al tema de validaciones y si quieres puedes añadirle las que creas convenientes 😉.

Y con esto cerramos este tutorial. Para el próximo vamos a crear un panel de administración que gracias a las herramientas de Symfony 6 nos lo pondrá muy fácil 💪.

Como siempre, os dejo el enlace al repo https://github.com/albertorc87/blog-symfony-tutorial/tree/formularios-symfony-6

Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.

Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.

Nos leemos 👋.

3747 vistas

🐍 Sígueme en Twitter

Si te gusta el contenido que subo y no quieres perderte nada, sígueme en Twitter y te avisaré cada vez que cree contenido nuevo 💪
Luego ¡Te sigo!

Nos tomamos en serio tu privacidad

Utilizamos cookies propias y de terceros para recopilar y analizar datos sobre la interacción de los usuarios con cosasdedevs.com. Ver política de cookies.