Nette Documentation Preview

syntax
Reutilización de formularios en múltiples lugares
*************************************************

.[perex]
En Nette, tienes varias opciones para usar el mismo formulario en múltiples lugares sin duplicar código. En este artículo, mostraremos diferentes soluciones, incluidas aquellas que deberías evitar.


Fábrica de formularios
======================

Uno de los enfoques básicos para usar el mismo componente en múltiples lugares es crear un método o clase que genere este componente y luego llamar a este método en diferentes lugares de la aplicación. Tal método o clase se llama *fábrica*. Por favor, no lo confundas con el patrón de diseño *factory method*, que describe una forma específica de usar fábricas y no está relacionado con este tema.

Como ejemplo, crearemos una fábrica que construirá un formulario de edición:

```php
use Nette\Application\UI\Form;

class FormFactory
{
	public function createEditForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		return $form;
	}
}
```

Ahora puedes usar esta fábrica en diferentes lugares de tu aplicación, por ejemplo, en Presenters o componentes. Y lo haces [solicitándola como dependencia|dependency-injection:passing-dependencies]. Primero, registramos la clase en el archivo de configuración:

```neon
services:
	- FormFactory
```

Y luego la usamos en un Presenter:


```php
class MyPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private FormFactory $formFactory,
	) {
	}

	protected function createComponentEditForm(): Form
	{
		$form = $this->formFactory->createEditForm();
		$form->onSuccess[] = function () {
			// procesamiento de los datos enviados
		};
		return $form;
	}
}
```

Puedes extender la fábrica de formularios con métodos adicionales para crear otros tipos de formularios según las necesidades de tu aplicación. Y, por supuesto, también podemos añadir un método que cree un formulario base sin elementos, que los otros métodos utilizarán:

```php
class FormFactory
{
	public function createForm(): Form
	{
		$form = new Form;
		return $form;
	}

	public function createEditForm(): Form
	{
		$form = $this->createForm();
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		return $form;
	}
}
```

El método `createForm()` aún no hace nada útil, pero eso cambiará rápidamente.


Dependencias de la fábrica
==========================

Con el tiempo, resultará que necesitamos que los formularios sean multilingües. Esto significa que debemos establecer el llamado [traductor |forms:rendering#Traducción] para todos los formularios. Para ello, modificaremos la clase `FormFactory` para que acepte un objeto `Translator` como dependencia en el constructor y lo pasaremos al formulario:

```php
use Nette\Localization\Translator;

class FormFactory
{
	public function __construct(
		private Translator $translator,
	) {
	}

	public function createForm(): Form
	{
		$form = new Form;
		$form->setTranslator($this->translator);
		return $form;
	}

	// ...
}
```

Dado que el método `createForm()` también es llamado por otros métodos que crean formularios específicos, basta con establecer el traductor solo en él. Y hemos terminado. No es necesario cambiar el código de ningún Presenter o componente, lo cual es genial.


Múltiples clases de fábrica
===========================

Alternativamente, puedes crear múltiples clases para cada formulario que quieras usar en tu aplicación. Este enfoque puede aumentar la legibilidad del código y facilitar la gestión de los formularios. Dejaremos que la `FormFactory` original cree solo un formulario limpio con la configuración básica (por ejemplo, con soporte para traducciones) y crearemos una nueva fábrica `EditFormFactory` para el formulario de edición.

```php
class FormFactory
{
	public function __construct(
		private Translator $translator,
	) {
	}

	public function create(): Form
	{
		$form = new Form;
		$form->setTranslator($this->translator);
		return $form;
	}
}


// ✅ uso de composición
class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		return $form;
	}
}
```

Es muy importante que la relación entre las clases `FormFactory` y `EditFormFactory` se realice mediante [composición |nette:introduction-to-object-oriented-programming#Composición], y no mediante [herencia de objetos |nette:introduction-to-object-oriented-programming#Herencia]:

```php
// ⛔ ¡ASÍ NO! LA HERENCIA NO PERTENECE AQUÍ
class EditFormFactory extends FormFactory
{
	public function create(): Form
	{
		$form = parent::create();
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		return $form;
	}
}
```

Usar la herencia en este caso sería completamente contraproducente. Te encontrarías con problemas muy rápidamente. Por ejemplo, en el momento en que quisieras añadir parámetros al método `create()`; PHP informaría de un error indicando que su firma difiere de la del padre. O al pasar dependencias a la clase `EditFormFactory` a través del constructor. Se produciría una situación que llamamos [constructor hell |dependency-injection:passing-dependencies#Constructor hell].

En general, es mejor preferir la [composición sobre la herencia |dependency-injection:faq#Por qué se prefiere la composición sobre la herencia].


Manejo del formulario
=====================

El manejador del formulario, que se llama después de un envío exitoso, también puede ser parte de la clase de fábrica. Funcionará pasando los datos enviados al modelo para su procesamiento. Los posibles errores se [pasarán de vuelta |forms:validation#Errores durante el procesamiento] al formulario. El modelo en el siguiente ejemplo está representado por la clase `Facade`:

```php
class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
		private Facade $facade,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		$form->onSuccess[] = [$this, 'processForm'];
		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// procesamiento de los datos enviados
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
		}
	}
}
```

Sin embargo, dejaremos la redirección real al Presenter. Este añadirá otro manejador al evento `onSuccess` que realizará la redirección. Gracias a esto, será posible usar el formulario en diferentes Presenters y redirigir a un lugar diferente en cada uno.

```php
class MyPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private EditFormFactory $formFactory,
	) {
	}

	protected function createComponentEditForm(): Form
	{
		$form = $this->formFactory->create();
		$form->onSuccess[] = function () {
			$this->flashMessage('El registro ha sido guardado');
			$this->redirect('Homepage:');
		};
		return $form;
	}
}
```

Esta solución aprovecha la propiedad de los formularios de que cuando se llama a `addError()` en el formulario o en uno de sus elementos, el siguiente manejador `onSuccess` ya no se llama.


Herencia de la clase Form
=========================

Un formulario construido no debe ser un descendiente del formulario. En otras palabras, no uses esta solución:

```php
// ⛔ ¡ASÍ NO! LA HERENCIA NO PERTENECE AQUÍ
class EditForm extends Form
{
	public function __construct(Translator $translator)
	{
		parent::__construct();
		$this->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$this->addSubmit('send', 'Enviar');
		$this->setTranslator($translator);
	}
}
```

En lugar de construir el formulario en el constructor, usa una fábrica.

Es necesario darse cuenta de que la clase `Form` es, ante todo, una herramienta para construir un formulario, es decir, un *form builder*. Y el formulario construido puede entenderse como su producto. Pero el producto no es un caso específico del constructor, no hay una relación *es un* entre ellos que forme la base de la herencia.


Componente con formulario
=========================

Un enfoque completamente diferente es la creación de un [componente|application:components] que incluya un formulario. Esto ofrece nuevas posibilidades, por ejemplo, renderizar el formulario de una manera específica, ya que el componente también incluye una plantilla. O se pueden usar señales para la comunicación AJAX y la carga de información en el formulario, por ejemplo, para sugerencias, etc.


```php
use Nette\Application\UI\Form;

class EditControl extends Nette\Application\UI\Control
{
	public array $onSave = [];

	public function __construct(
		private Facade $facade,
	) {
	}

	protected function createComponentForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		$form->onSuccess[] = [$this, 'processForm'];

		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// procesamiento de los datos enviados
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
			return;
		}

		// disparar el evento
		$this->onSave($this, $data);
	}
}
```

También crearemos una fábrica que producirá este componente. Basta con [escribir su interfaz |application:components#Componentes con dependencias]:

```php
interface EditControlFactory
{
	function create(): EditControl;
}
```

Y añadirla al archivo de configuración:

```neon
services:
	- EditControlFactory
```

Y ahora podemos solicitar la fábrica y usarla en el Presenter:

```php
class MyPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private EditControlFactory $controlFactory,
	) {
	}

	protected function createComponentEditForm(): EditControl
	{
		$control = $this->controlFactory->create();

		$control->onSave[] = function (EditControl $control, $data) {
			$this->redirect('this');
			// o redirigimos al resultado de la edición, p.ej.:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}
```

{{sitename: Best Practices}}

Reutilización de formularios en múltiples lugares

En Nette, tienes varias opciones para usar el mismo formulario en múltiples lugares sin duplicar código. En este artículo, mostraremos diferentes soluciones, incluidas aquellas que deberías evitar.

Fábrica de formularios

Uno de los enfoques básicos para usar el mismo componente en múltiples lugares es crear un método o clase que genere este componente y luego llamar a este método en diferentes lugares de la aplicación. Tal método o clase se llama fábrica. Por favor, no lo confundas con el patrón de diseño factory method, que describe una forma específica de usar fábricas y no está relacionado con este tema.

Como ejemplo, crearemos una fábrica que construirá un formulario de edición:

use Nette\Application\UI\Form;

class FormFactory
{
	public function createEditForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		return $form;
	}
}

Ahora puedes usar esta fábrica en diferentes lugares de tu aplicación, por ejemplo, en Presenters o componentes. Y lo haces solicitándola como dependencia. Primero, registramos la clase en el archivo de configuración:

services:
	- FormFactory

Y luego la usamos en un Presenter:

class MyPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private FormFactory $formFactory,
	) {
	}

	protected function createComponentEditForm(): Form
	{
		$form = $this->formFactory->createEditForm();
		$form->onSuccess[] = function () {
			// procesamiento de los datos enviados
		};
		return $form;
	}
}

Puedes extender la fábrica de formularios con métodos adicionales para crear otros tipos de formularios según las necesidades de tu aplicación. Y, por supuesto, también podemos añadir un método que cree un formulario base sin elementos, que los otros métodos utilizarán:

class FormFactory
{
	public function createForm(): Form
	{
		$form = new Form;
		return $form;
	}

	public function createEditForm(): Form
	{
		$form = $this->createForm();
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		return $form;
	}
}

El método createForm() aún no hace nada útil, pero eso cambiará rápidamente.

Dependencias de la fábrica

Con el tiempo, resultará que necesitamos que los formularios sean multilingües. Esto significa que debemos establecer el llamado traductor para todos los formularios. Para ello, modificaremos la clase FormFactory para que acepte un objeto Translator como dependencia en el constructor y lo pasaremos al formulario:

use Nette\Localization\Translator;

class FormFactory
{
	public function __construct(
		private Translator $translator,
	) {
	}

	public function createForm(): Form
	{
		$form = new Form;
		$form->setTranslator($this->translator);
		return $form;
	}

	// ...
}

Dado que el método createForm() también es llamado por otros métodos que crean formularios específicos, basta con establecer el traductor solo en él. Y hemos terminado. No es necesario cambiar el código de ningún Presenter o componente, lo cual es genial.

Múltiples clases de fábrica

Alternativamente, puedes crear múltiples clases para cada formulario que quieras usar en tu aplicación. Este enfoque puede aumentar la legibilidad del código y facilitar la gestión de los formularios. Dejaremos que la FormFactory original cree solo un formulario limpio con la configuración básica (por ejemplo, con soporte para traducciones) y crearemos una nueva fábrica EditFormFactory para el formulario de edición.

class FormFactory
{
	public function __construct(
		private Translator $translator,
	) {
	}

	public function create(): Form
	{
		$form = new Form;
		$form->setTranslator($this->translator);
		return $form;
	}
}


// ✅ uso de composición
class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		return $form;
	}
}

Es muy importante que la relación entre las clases FormFactory y EditFormFactory se realice mediante composición, y no mediante herencia de objetos:

// ⛔ ¡ASÍ NO! LA HERENCIA NO PERTENECE AQUÍ
class EditFormFactory extends FormFactory
{
	public function create(): Form
	{
		$form = parent::create();
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		return $form;
	}
}

Usar la herencia en este caso sería completamente contraproducente. Te encontrarías con problemas muy rápidamente. Por ejemplo, en el momento en que quisieras añadir parámetros al método create(); PHP informaría de un error indicando que su firma difiere de la del padre. O al pasar dependencias a la clase EditFormFactory a través del constructor. Se produciría una situación que llamamos constructor hell.

En general, es mejor preferir la composición sobre la herencia.

Manejo del formulario

El manejador del formulario, que se llama después de un envío exitoso, también puede ser parte de la clase de fábrica. Funcionará pasando los datos enviados al modelo para su procesamiento. Los posibles errores se pasarán de vuelta al formulario. El modelo en el siguiente ejemplo está representado por la clase Facade:

class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
		private Facade $facade,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		$form->onSuccess[] = [$this, 'processForm'];
		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// procesamiento de los datos enviados
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
		}
	}
}

Sin embargo, dejaremos la redirección real al Presenter. Este añadirá otro manejador al evento onSuccess que realizará la redirección. Gracias a esto, será posible usar el formulario en diferentes Presenters y redirigir a un lugar diferente en cada uno.

class MyPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private EditFormFactory $formFactory,
	) {
	}

	protected function createComponentEditForm(): Form
	{
		$form = $this->formFactory->create();
		$form->onSuccess[] = function () {
			$this->flashMessage('El registro ha sido guardado');
			$this->redirect('Homepage:');
		};
		return $form;
	}
}

Esta solución aprovecha la propiedad de los formularios de que cuando se llama a addError() en el formulario o en uno de sus elementos, el siguiente manejador onSuccess ya no se llama.

Herencia de la clase Form

Un formulario construido no debe ser un descendiente del formulario. En otras palabras, no uses esta solución:

// ⛔ ¡ASÍ NO! LA HERENCIA NO PERTENECE AQUÍ
class EditForm extends Form
{
	public function __construct(Translator $translator)
	{
		parent::__construct();
		$this->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$this->addSubmit('send', 'Enviar');
		$this->setTranslator($translator);
	}
}

En lugar de construir el formulario en el constructor, usa una fábrica.

Es necesario darse cuenta de que la clase Form es, ante todo, una herramienta para construir un formulario, es decir, un form builder. Y el formulario construido puede entenderse como su producto. Pero el producto no es un caso específico del constructor, no hay una relación es un entre ellos que forme la base de la herencia.

Componente con formulario

Un enfoque completamente diferente es la creación de un componente que incluya un formulario. Esto ofrece nuevas posibilidades, por ejemplo, renderizar el formulario de una manera específica, ya que el componente también incluye una plantilla. O se pueden usar señales para la comunicación AJAX y la carga de información en el formulario, por ejemplo, para sugerencias, etc.

use Nette\Application\UI\Form;

class EditControl extends Nette\Application\UI\Control
{
	public array $onSave = [];

	public function __construct(
		private Facade $facade,
	) {
	}

	protected function createComponentForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Título:');
		// aquí se añaden otros campos del formulario
		$form->addSubmit('send', 'Enviar');
		$form->onSuccess[] = [$this, 'processForm'];

		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// procesamiento de los datos enviados
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
			return;
		}

		// disparar el evento
		$this->onSave($this, $data);
	}
}

También crearemos una fábrica que producirá este componente. Basta con escribir su interfaz:

interface EditControlFactory
{
	function create(): EditControl;
}

Y añadirla al archivo de configuración:

services:
	- EditControlFactory

Y ahora podemos solicitar la fábrica y usarla en el Presenter:

class MyPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private EditControlFactory $controlFactory,
	) {
	}

	protected function createComponentEditForm(): EditControl
	{
		$control = $this->controlFactory->create();

		$control->onSave[] = function (EditControl $control, $data) {
			$this->redirect('this');
			// o redirigimos al resultado de la edición, p.ej.:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}