This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
04.

Varios botones de envío

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Muy bien, tenemos nuestro botón "Crear" funcionando a las mil maravillas, pero ¿y si queremos un par de flujos de trabajo diferentes? "Crear y cerrar" para guardar la pieza y volver a la lista, y "Crear y añadir otra" para guardar la pieza pero permanecer en la página del formulario para introducir datos rápidamente.

No te preocupes, Symfony es totalmente capaz de manejar esto. Vamos a sumergirnos en el segundo método para añadir botones de envío a un formulario: utilizando SubmitType.

Añadir un segundo botón de envío dentro del tipo de formulario

Lo primero es lo primero: cambiemos el nombre de nuestro botón actual en la plantillanew.html.twig a "Crear y cerrar":

// ... lines 1 - 4
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 7 - 8
{{ form_start(form) }}
// ... lines 10 - 11
<button type="submit" class="text-white bg-green-700 hover:bg-green-800 rounded-lg px-5 py-2.5 me-2 mb-2 cursor-pointer">Create and close</button>
{{ form_end(form) }}
</div>
{% endblock %}

Ahora, abre tu clase de tipo de formulario en src/Form/StarshipPartType.php. Es hora de añadir un segundo botón debajo de los campos. Añádelo utilizando->add('createAndAddNew', SubmitType::class):

36 lines | src/Form/StarshipPartType.php
// ... lines 1 - 8
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
// ... lines 10 - 12
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 18 - 24
->add('createAndAddNew', SubmitType::class)
;
}
// ... lines 28 - 34
}

Este práctico SubmitType::class le dice a Symfony que lo renderice como un<button type="submit">.

Si vas al navegador y actualizas, verás nuestros dos botones. El nuevo no parece un botón, porque hemos restablecido los estilos. Pero técnicamente, en el código, es <button type="submit">. Más adelante arreglaremos los estilos.

Acceso a campos no mapeados en Symfony

Por ahora, en el controlador, ya sabemos que $form->getData()nos pasa una entidad mapeada, que en nuestro caso es StarshipPart:

39 lines | src/Controller/AdminController.php
// ... lines 1 - 13
class AdminController extends AbstractController
{
#[Route('/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()) {
/** @var StarshipPart $part */
$part = $form->getData();
// ... lines 26 - 31
}
// ... lines 33 - 36
}
}

Sin embargo, esta vez necesitamos acceder a un campo no mapeado: el botón de envío que acabamos de añadir. Este campo no tiene una propiedad coincidente en la entidad, por eso lo llamamos "no mapeado".

No pasa nada, podemos acceder a los datos brutos del formulario. Justo debajo de addFlash(), crea una variable $createAndAddNewBtn que sea igual a$form->get('createAndAddNew'). Esto debería coincidir con el nombre del botón de tu tipo de formulario. Hagamos primero una prueba rápida. Debajo,dd($createAndAddNewBtn):

42 lines | src/Controller/AdminController.php
// ... lines 1 - 13
class AdminController extends AbstractController
{
#[Route('/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()));
$createAndAddNewBtn = $form->get('createAndAddNew');
dd($createAndAddNewBtn);
// ... lines 33 - 34
}
// ... lines 36 - 39
}
}

De vuelta al navegador, rellenar las notas del formulario es opcional, así que bastará con el nombre y el precio. A continuación, pulsa nuestro botón "Crear y añadir nuevo"... y... ahí está nuestro volcado. Es un Botón de Enviar con algunos campos intrigantes. Míralo más de cerca y descubrirás el mágico clicked = true. Lo creas o no, esta pequeña joya es la forma en que podemos saber qué botón se ha pulsado realmente, y podemos aprovecharla para potenciar diferentes lógicas de negocio.

Aprovechar los clics de los botones para ejecutar la lógica empresarial

Volviendo al código, deshazte del dd() y dale una ayudita a PhpStorm. Para solucionar el autocompletado, encima del botón, añade un docblock con/** @var SubmitButton $createAndAddNewBtn */. Ahora, dentro de la lógica de envío del formulario, escribe $createAndAddNewBtn->isClicked(). Eso es exactamente lo que necesitamos. Envuélvelo en una declaración if, y si se ha hecho clic en el botón, devolvamos $this->redirectToRoute('app_admin_starship_part_new'):

46 lines | src/Controller/AdminController.php
// ... lines 1 - 8
use Symfony\Component\Form\SubmitButton;
// ... lines 10 - 14
class AdminController extends AbstractController
{
#[Route('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST'])]
public function newStarshipPart(
// ... lines 19 - 20
): Response {
// ... lines 22 - 23
if ($form->isSubmitted()) {
// ... lines 25 - 31
/** @var SubmitButton $createAndAddNewBtn */
$createAndAddNewBtn = $form->get('createAndAddNew');
if ($createAndAddNewBtn->isClicked()) {
return $this->redirectToRoute('app_admin_starship_part_new');
}
// ... lines 37 - 38
}
// ... lines 40 - 43
}
}

Ahora, de vuelta en el navegador, actualiza y vuelve a enviar. Hmm, vemos el mensaje flash de éxito dos veces... Eso es porque se añadió durante la petición dd(), y de nuevo, cuando volvimos a enviarla. Pero funcionó: estamos de nuevo en el formulario en blanco, listos para añadir otra parte. ¡Genial!

Las complejidades del tipo de formulario de Symfony: Adivinar el tipo de campo

Volvamos un momento al tipo de formulario. Estamos utilizando $builder->add()para añadir campos a nuestro formulario. A veces especificamos el segundo argumento, pero otras, no. Este segundo es el tipo de campo, y es nulo por defecto. Entonces, ¿por qué no necesitamos especificarlo siempre?

Bueno, Symfony tiene esta ingeniosa función llamada adivinar el tipo de campo. Cuando el tipo es null, Symfony inspeccionará la clase de datos subyacente - en nuestro caso, la entidad StarshipPart. Buscará una propiedad que coincida con el nombre del campo. Basándose en el tipo de la propiedad encontrada (adivinada a partir de las sugerencias de tipo y los metadatos), Symfony seleccionará automáticamente el tipo de campo de formulario más apropiado.

Por ejemplo, como price es un entero, Symfony elegirá IntegerType. Como name es una cadena, Symfony optará por TextType. Si tuviéramos, digamos, un booleano isActive, entonces Symfony seleccionaría CheckboxType. Muy guay, ¿eh?

La mayoría de las veces, Symfony adivina exactamente lo que quieres. Pero cuando no puede adivinar correctamente, o cuando quieres algo diferente, siempre puedes anular el tipo adivinado por defecto pasando el tipo explícitamente.

Si cambiamos price por IntegerType::class, cuando actualicemos el formulario, nada cambiará, porque ese era el tipo adivinado.

Explorando los tipos de campos de formulario incorporados de Symfony

Pero, ¿qué tipos de campo de formulario incorporados tiene Symfony y dónde podemos encontrarlos? Gran pregunta, ¡y me alegro de que lo preguntes! Los documentos de Symfony siempre estarán ahí para ayudarte. Pero Symfony también viene con una herramienta súper fácil de usar para descubrir todo sobre los tipos de campo incorporados.

En tu terminal, ejecuta:

symfony console debug:form

Esto vuelca todos los tipos de formulario disponibles, incluyendo tus propias clases de tipos de formulario, que podemos ver aquí.

Si quieres inspeccionar un tipo específico, sólo tienes que especificarlo como argumento del comando. Por ejemplo, Ejecuta::

symfony console debug:form TextType

Y verás todas las opciones que admite TextType, las opciones que son obligatorias y qué opciones proceden de los tipos padre a los que extiende. Prueba con otro:

symfony console debug:form EntityType

Lo utilizamos en nuestro StarshipPartType para el campo ship. Con éste, verás que la opción class es obligatoria. Si miramos nuestro tipo de formulario... el MakerBundle ya lo ha rellenado por nosotros:

36 lines | src/Form/StarshipPartType.php
// ... lines 1 - 12
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 18 - 20
->add('starship', EntityType::class, [
'class' => Starship::class,
// ... line 23
])
// ... line 25
;
}
// ... lines 28 - 34
}

¡Qué inteligente! Las opciones se establecen como tercer argumento de $builder->add().

Y por cierto, Symfony no sólo adivina los tipos de campo, también adivina las opciones del tipo de campo. Por ejemplo, si una propiedad de entidad Doctrine es nullable: true como para este campo notes:

86 lines | src/Entity/StarshipPart.php
// ... lines 1 - 10
class StarshipPart
{
// ... lines 13 - 25
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $notes = null;
// ... lines 28 - 84
}

Entonces Symfony la convertirá automáticamente en opcional. Y al revés para los demás campos, el atributo HTML required se añadirá si el campo no es anulable, como para name y price:

86 lines | src/Entity/StarshipPart.php
// ... lines 1 - 10
class StarshipPart
{
// ... lines 13 - 19
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\Column]
private ?int $price = null;
// ... lines 25 - 84
}

Puedes ver esto con el inspector HTML... Si inspeccionamos el campo notas, vemos que no tiene el atributo required. Si inspeccionamos el campo nombre, ¡sí lo tiene! Lo mismo ocurre con el precio.

Por eso, cuando envías un formulario vacío, vemos errores de validación HTML5 para los campos obligatorios.

Y, por supuesto, puedes anular fácilmente este comportamiento en esa tercera matriz de argumentos$builder->add(). Para verlo, añade required => falsea las opciones de nuestro campo price. De vuelta en el navegador, actualiza e inspecciona el campo de precio: ¡el atributo required ha desaparecido!

Revierte ese cambio: ¡realmente es necesario!

Profundizaremos en los tipos de campos de formulario más adelante en este curso. Por ahora, cambiemos de marcha hacia algo divertido y elegante: vestir nuestro formulario para el baile aplicándole un tema de formulario Symfony incorporado. ¡Eso a continuación!