This course is still being released! Check back later for more chapters.
Procesar el formulario enviado
Muy bien, hemos construido, creado, renderizado y estilizado nuestro formulario. Lo he dado todo y ahora nuestro formulario está listo para ser enviado. Ahora, como te dirá cualquier desarrollador experimentado, la verdadera diversión comienza cuando empezamos a tratar los datos enviados. Volvamos a nuestro controlador y hagamos que este formulario sea funcional.
Actualizar el controlador para gestionar los datos del formulario
Abre src/Controller/AdminController.php y, en el método newStarshipPart(), justo debajo del objeto $form, añade $form->handleRequest(). Para ello, tenemos que pasar el objeto de petición actual a este método. Esto ya te resultará familiar. Inyecta Request desdeHttpFoundation como argumento del método $request y pásalo ahandleRequest():
| // ... lines 1 - 7 | |
| use Symfony\Component\HttpFoundation\Request; | |
| // ... lines 9 - 12 | |
| class AdminController extends AbstractController | |
| { | |
| ('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST']) | |
| public function newStarshipPart( | |
| Request $request, | |
| ): Response { | |
| $form = $this->createForm(StarshipPartType::class); | |
| $form->handleRequest($request); | |
| // ... lines 21 - 24 | |
| } | |
| } |
Puede que te estés preguntando de qué va este handleRequest(). Simplemente coge los datos enviados de la petición, aplica esos datos a tu formulario, y ahora tu formulario contiene los valores enviados por el usuario.
Comprobación del envío del formulario
A continuación, queremos saber si el formulario se ha enviado realmente o si sólo hemos cargado la página del formulario. Eso es muy fácil: escribeif ($form->isSubmitted()). Luego, dentro de ese if, podemos recuperar los datos enviados con $form->getData():
| // ... lines 1 - 12 | |
| class AdminController extends AbstractController | |
| { | |
| ('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST']) | |
| public function newStarshipPart( | |
| Request $request, | |
| ): Response { | |
| $form = $this->createForm(StarshipPartType::class); | |
| $form->handleRequest($request); | |
| if ($form->isSubmitted()) { | |
| $part = $form->getData(); | |
| } | |
| // ... lines 24 - 27 | |
| } | |
| } |
Como nuestro tipo de formulario tiene una opción data_class establecida en StarshipPart::class:
| // ... lines 1 - 11 | |
| class StarshipPartType extends AbstractType | |
| { | |
| // ... lines 14 - 26 | |
| public function configureOptions(OptionsResolver $resolver): void | |
| { | |
| $resolver->setDefaults([ | |
| 'data_class' => StarshipPart::class, | |
| ]); | |
| } | |
| } |
los datos que recuperas aquí no son un simple array PHP. En su lugar, es una instancia de entidad StarshipPart con los campos ya rellenados por nosotros. ¿Eres escéptico? Adelante, compruébalo tú mismo. Asignémoslo a una variable $part y a continuación dd($part):
| // ... lines 1 - 12 | |
| class AdminController extends AbstractController | |
| { | |
| ('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST']) | |
| public function newStarshipPart( | |
| Request $request, | |
| ): Response { | |
| // ... lines 19 - 20 | |
| if ($form->isSubmitted()) { | |
| $part = $form->getData(); | |
| dd($part); | |
| } | |
| // ... lines 25 - 28 | |
| } | |
| } |
Probando nuestro formulario
De vuelta en el navegador, rellenaré rápidamente el formulario. Pulsa Crear para enviarlo... y voilá, un nuevo y reluciente objeto StarshipPart con los datos que enviamos. Fíjate en que no tiene ID porque Doctrine aún no lo ha guardado en la base de datos. Añadiré rápidamente un PHPDoc encima de la variable para que el autocompletado de PhpStorm sea más feliz, y borraré la declaración dd():
| // ... lines 1 - 12 | |
| class AdminController extends AbstractController | |
| { | |
| ('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST']) | |
| public function newStarshipPart( | |
| Request $request, | |
| ): Response { | |
| // ... lines 19 - 20 | |
| if ($form->isSubmitted()) { | |
| /** @var StarshipPart $part */ | |
| $part = $form->getData(); | |
| } | |
| // ... lines 25 - 28 | |
| } | |
| } |
Guardar datos con el Gestor de Entidades de Doctrine
Para guardar la nueva parte, necesitamos el EntityManager de Doctrine. Inyéctalo conEntityManagerInterface $entityManager en la firma del método. Luego, de nuevo en el if, añade $entityManager->persist(), pasando el objeto $party a continuación $entityManager->flush():
| // ... lines 1 - 6 | |
| use Doctrine\ORM\EntityManagerInterface; | |
| // ... lines 8 - 13 | |
| class AdminController extends AbstractController | |
| { | |
| ('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST']) | |
| public function newStarshipPart( | |
| // ... line 18 | |
| EntityManagerInterface $entityManager, | |
| ): Response { | |
| // ... lines 21 - 22 | |
| if ($form->isSubmitted()) { | |
| /** @var StarshipPart $part */ | |
| $part = $form->getData(); | |
| $entityManager->persist($part); | |
| $entityManager->flush(); | |
| } | |
| // ... lines 29 - 32 | |
| } | |
| } |
De vuelta al navegador, estableceré el nombre a: "Legacy Hyperdrive". Ponle un precio justo, y no te olvides de una nota importante:
¡Ten cuidado con las altas revoluciones!
Ahora, pulsa de nuevo el botón de enviar. ¿Finalmente ha funcionado? Bueno, al menos no hay errores. Dirígete a la página $part y busca "Hipermotor Legado". Ahí está, tu nueva y reluciente pieza para la nave estelar, lista para la venta.
Celebrando el éxito con mensajes flash
Celebremos este momento como es debido. Voy a añadir un mensaje flash de éxito. Los mensajes flash son mensajes temporales que se almacenan en la sesión y se muestran exactamente una vez. Son perfectos para cosas como
¡Tu pieza se ha creado correctamente!
Si echas un vistazo a templates/base.html.twig, verás que ya tenemos código que hace un bucle sobre los mensajes flash y los muestra con un bonito estilo dependiendo del tipo: éxito, advertencia, error, predeterminado:
| <html> | |
| // ... lines 3 - 13 | |
| <body class="text-white" style="background: radial-gradient(102.21% 102.21% at 50% 28.75%, #00121C 42.62%, #013954 100%);"> | |
| {% set flashBgColors = { | |
| 'success': 'bg-green-400/30', | |
| 'warning': 'bg-yellow-400/30', | |
| 'error': 'bg-red-400/30', | |
| 'default': 'bg-blue-400/30' | |
| } %} | |
| {% for label, messages in app.flashes %} | |
| {% for message in messages %} | |
| <div class="relative isolate flex items-center gap-x-6 overflow-hidden {{ flashBgColors[label]|default(flashBgColors['default']) }} px-6 py-2.5 after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-white/10 sm:px-3.5 sm:before:flex-1"> | |
| <div class="flex flex-wrap items-center gap-x-4 gap-y-2"> | |
| <p class="text-sm/6 text-gray-100"> | |
| {{ message }} | |
| </p> | |
| </div> | |
| <div class="flex flex-1 justify-end"> | |
| <button type="button" class="-m-3 p-3 focus-visible:-outline-offset-4"> | |
| <span class="sr-only">Dismiss</span> | |
| <svg viewBox="0 0 20 20" fill="currentColor" data-slot="icon" aria-hidden="true" class="size-5 text-gray-100"> | |
| <path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| {% endfor %} | |
| {% endfor %} | |
| // ... lines 40 - 67 | |
| </body> | |
| </html> |
De vuelta en nuestro controlador, después de guardar la entidad de la pieza en la base de datos, escribe: $this->addFlash(). Primer argumento: el "tipo" de mensaje - ayuda a controlar el estilo. Escribe aquí success. Segundo argumento: el contenido del mensaje. ¿Qué te parece:sprintf('The part "%s" was successfully created!', $part->getName()):
| // ... lines 1 - 13 | |
| class AdminController extends AbstractController | |
| { | |
| ('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST']) | |
| public function newStarshipPart( | |
| // ... lines 18 - 19 | |
| ): Response { | |
| // ... lines 21 - 22 | |
| if ($form->isSubmitted()) { | |
| // ... lines 24 - 28 | |
| $this->addFlash('success', sprintf('The part "%s" was successfully created.', $part->getName())); | |
| } | |
| // ... lines 31 - 34 | |
| } | |
| } |
Evitar la duplicación y redirigir al usuario
Para evitar que se dupliquen los envíos del formulario cuando el usuario actualice la página -lo que podría dar lugar a partes no deseadas flotando en el espacio-, terminemos el proceso con una redirección. Esta es una buena práctica clásica para los formularios POST. Vamos a redirigir a $this->redirectToRoute().
Podemos redirigir a cualquier sitio, pero yo devolveré a los usuarios a la lista de piezas por comodidad. El nombre de esta ruta es app_part_index.:
| // ... lines 1 - 13 | |
| class AdminController extends AbstractController | |
| { | |
| ('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST']) | |
| public function newStarshipPart( | |
| // ... lines 18 - 19 | |
| ): Response { | |
| // ... lines 21 - 22 | |
| if ($form->isSubmitted()) { | |
| // ... lines 24 - 28 | |
| $this->addFlash('success', sprintf('The part "%s" was successfully created.', $part->getName())); | |
| return $this->redirectToRoute('app_part_index'); | |
| } | |
| // ... lines 33 - 36 | |
| } | |
| } |
Probar el flujo general
Muy bien, creemos de nuevo una nueva pieza. ¿Qué tal un "Reactor Cuántico" como nombre, fijamos un precio y, como notas, diré
No superar el 120% de flujo del núcleo
Vale, vuelve a enviar el formulario, y ya está. Nuestro mensaje flash, anunciando que:
¡La pieza "Reactor cuántico" se ha creado correctamente!
Y si intento actualizar la página, el mensaje desaparece, así que sólo se mostró una vez, y Chrome no me pregunta si quiero volver a enviar el formulario, así que también se redirigió correctamente. ¡Genial!
Añadir un segundo botón de envío
Ahora mismo sólo tenemos un botón de envío: Crear. Pero imagina esto, si te sientes productivo, cafeinado, en racha, y quieres crear varias partes rápidamente, una tras otra, podrías hacerlo con unos pocos clics extra cada vez, haciendo clic en el enlace para volver al formulario. Pero, ¿no sería mucho más rápido tener un segundo botón de envío que, en lugar de crear y volver a la lista, creara y permaneciera en esta página con un formulario vacío abierto, para que pudieras crear inmediatamente otro parte?
Bueno, puede que no sea mucho más rápido, pero aún así podría ahorrarle a alguien unas cuantas horas de su vida a lo largo de muchos años de añadir esas piezas.
¿Tal vez te preguntes si eso es posible? ¡Por supuesto que sí! Y en el próximo capítulo veremos cómo.