Nette Documentation Preview

syntax
Riutilizzare i form in più punti
********************************

.[perex]
Nette offre diverse possibilità per usare lo stesso form in più punti senza duplicare il codice. In questo articolo mostreremo le varie soluzioni, comprese quelle che dovreste evitare.


Factory di form
===============

Un approccio di base per usare lo stesso componente in più punti è creare un metodo o una classe che genera questo componente, e poi richiamarlo dai vari punti dell'applicazione. Un metodo o una classe del genere si chiama *factory*. Non confondetela per favore con il design pattern *metodo factory*, che descrive un modo particolare di usare le factory e con questo tema non ha nulla a che vedere.

Come esempio creiamo una factory che costruisce un form di modifica:

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

class FormFactory
{
	public function createEditForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		return $form;
	}
}
```

Ora potete usare questa factory in vari punti della vostra applicazione, per esempio nei presenter o nei componenti. E lo fate [facendovela passare come dipendenza |dependency-injection:passing-dependencies]. Per prima cosa quindi registriamo la classe nel file di configurazione:

```neon
services:
	- FormFactory
```

E poi la usiamo nel 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 () {
			// elaborazione dei dati inviati
		};
		return $form;
	}
}
```

La factory di form la potete estendere con altri metodi per creare gli altri tipi di form di cui la vostra applicazione ha bisogno. E naturalmente possiamo aggiungere anche un metodo che crea un form di base senza elementi, che gli altri metodi poi useranno:

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

	public function createEditForm(): Form
	{
		$form = $this->createForm();
		$form->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		return $form;
	}
}
```

Il metodo `createForm()` non fa ancora nulla di utile, ma questo cambierà presto.


Dipendenze della factory
========================

Con il tempo può emergere l'esigenza che i form siano multilingue. Il che significa che a tutti i form dobbiamo impostare un [translator |forms:rendering#Traduzione]. Per farlo modifichiamo la classe `FormFactory` in modo che si faccia passare nel costruttore l'oggetto `Translator` e lo imposti al form creato:

```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;
	}

	// ...
}
```

Poiché il metodo `createForm()` viene chiamato anche dagli altri metodi che creano form concreti, basta impostare il translator solo qui. E abbiamo finito. Non serve modificare il codice di nessun presenter o componente, il che è ottimo.


Più classi factory
==================

In alternativa potete creare più classi per ogni form che volete usare nell'applicazione. Questo approccio può aumentare la leggibilità del codice e rendere più semplice la gestione dei form. Lasciamo che la `FormFactory` originale crei solo un form puro con la configurazione di base (per esempio con il supporto alla traduzione) e per il form di modifica creiamo una nuova factory `EditFormFactory`.

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

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


// ✅ uso della composizione
class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		return $form;
	}
}
```

È molto importante che il legame tra le classi `FormFactory` e `EditFormFactory` sia realizzato con la [composizione |nette:introduction-to-object-oriented-programming#Composizione] e non con l'[ereditarietà tra oggetti |nette:introduction-to-object-oriented-programming#Ereditarietà]:

```php
// ⛔ NO! L'EREDITARIETÀ QUI NON C'ENTRA
class EditFormFactory extends FormFactory
{
	public function create(): Form
	{
		$form = parent::create();
		$form->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		return $form;
	}
}
```

Usare qui l'ereditarietà sarebbe del tutto controproducente. Incontrereste dei problemi molto in fretta. Per esempio, nel momento in cui voleste aggiungere dei parametri al metodo `create()`, PHP segnalerebbe un errore perché la sua firma sarebbe diversa da quella dell'antenato. Oppure nel momento in cui passaste alla classe `EditFormFactory` una dipendenza tramite il costruttore. Nascerebbe quello che si chiama [inferno dei costruttori |dependency-injection:passing-dependencies#L'inferno dei costruttori].

In generale è meglio preferire la [composizione all'ereditarietà |dependency-injection:faq#Perché si preferisce la composizione all'ereditarietà?].


Gestione del form
=================

Anche il gestore del form, che viene richiamato dopo l'invio riuscito, può far parte della classe factory. Funzionerà passando i dati inviati al modello perché li elabori. Gli eventuali errori li [restituirà |forms:validation#Elaborare gli errori] al form. Il modello nell'esempio seguente è rappresentato dalla classe `Facade`:

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

	public function create(): Form
	{
		$form = $this->formFactory->create();
		$form->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		$form->onSuccess[] = $this->processForm(...);
		return $form;
	}

	private function processForm(Form $form, array $data): void
	{
		try {
			// elaborazione dei dati inviati
			$this->facade->process($data);

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

Il redirect vero e proprio lo lasciamo però al presenter. Esso aggiunge all'evento `onSuccess` un altro gestore, che esegue il redirect. Grazie a questo si potrà usare il form in presenter diversi e ognuno reindirizzerà altrove.

```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('Il record è stato salvato');
			$this->redirect('Homepage:');
		};
		return $form;
	}
}
```

Questa soluzione sfrutta la proprietà dei form per cui, se sul form o su un suo elemento viene chiamato `addError()`, i gestori `onSuccess` successivi non vengono più richiamati.


Ereditare dalla classe Form
===========================

Un form assemblato non dovrebbe essere un discendente della classe `Form`. In altre parole, non usate questa soluzione:

```php
// ⛔ NO! L'EREDITARIETÀ QUI NON C'ENTRA
class EditForm extends Form
{
	public function __construct(Translator $translator)
	{
		parent::__construct();
		$this->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$this->addSubmit('send', 'Salva');
		$this->setTranslator($translator);
	}
}
```

Invece di assemblare il form nel costruttore, usate una factory.

È importante rendersi conto che la classe `Form` è anzitutto uno strumento per assemblare i form, cioè un *form builder*. E il form assemblato si può considerare il suo prodotto. Ma il prodotto non è un caso particolare del builder, tra loro non c'è la relazione *is a* su cui si fonda l'ereditarietà.


Componente con form
===================

Un approccio del tutto diverso consiste nel creare un [componente |application:components] che contiene un form. Questo dà nuove possibilità, per esempio renderizzare il form in un modo particolare, dato che il componente ha un proprio template. Oppure si possono usare i segnali per la comunicazione AJAX e per caricare informazioni nel form, per esempio per i suggerimenti, e così via.


```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', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		$form->onSuccess[] = $this->processForm(...);

		return $form;
	}

	private function processForm(Form $form, array $data): void
	{
		try {
			// elaborazione dei dati inviati
			$this->facade->process($data);

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

		// richiamo dell'evento
		$this->onSave($this, $data);
	}
}
```

Creiamo poi una factory che produrrà questo componente. Basta [definirne l'interfaccia |application:components#Componenti con dipendenze]:

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

E aggiungerla al file di configurazione:

```neon
services:
	- EditControlFactory
```

E ora possiamo farci passare la factory e usarla nel 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');
			// oppure redirect al risultato della modifica, per esempio:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}
```

Riutilizzare i form in più punti

Nette offre diverse possibilità per usare lo stesso form in più punti senza duplicare il codice. In questo articolo mostreremo le varie soluzioni, comprese quelle che dovreste evitare.

Factory di form

Un approccio di base per usare lo stesso componente in più punti è creare un metodo o una classe che genera questo componente, e poi richiamarlo dai vari punti dell'applicazione. Un metodo o una classe del genere si chiama factory. Non confondetela per favore con il design pattern metodo factory, che descrive un modo particolare di usare le factory e con questo tema non ha nulla a che vedere.

Come esempio creiamo una factory che costruisce un form di modifica:

use Nette\Application\UI\Form;

class FormFactory
{
	public function createEditForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		return $form;
	}
}

Ora potete usare questa factory in vari punti della vostra applicazione, per esempio nei presenter o nei componenti. E lo fate facendovela passare come dipendenza. Per prima cosa quindi registriamo la classe nel file di configurazione:

services:
	- FormFactory

E poi la usiamo nel 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 () {
			// elaborazione dei dati inviati
		};
		return $form;
	}
}

La factory di form la potete estendere con altri metodi per creare gli altri tipi di form di cui la vostra applicazione ha bisogno. E naturalmente possiamo aggiungere anche un metodo che crea un form di base senza elementi, che gli altri metodi poi useranno:

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

	public function createEditForm(): Form
	{
		$form = $this->createForm();
		$form->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		return $form;
	}
}

Il metodo createForm() non fa ancora nulla di utile, ma questo cambierà presto.

Dipendenze della factory

Con il tempo può emergere l'esigenza che i form siano multilingue. Il che significa che a tutti i form dobbiamo impostare un translator. Per farlo modifichiamo la classe FormFactory in modo che si faccia passare nel costruttore l'oggetto Translator e lo imposti al form creato:

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;
	}

	// ...
}

Poiché il metodo createForm() viene chiamato anche dagli altri metodi che creano form concreti, basta impostare il translator solo qui. E abbiamo finito. Non serve modificare il codice di nessun presenter o componente, il che è ottimo.

Più classi factory

In alternativa potete creare più classi per ogni form che volete usare nell'applicazione. Questo approccio può aumentare la leggibilità del codice e rendere più semplice la gestione dei form. Lasciamo che la FormFactory originale crei solo un form puro con la configurazione di base (per esempio con il supporto alla traduzione) e per il form di modifica creiamo una nuova factory EditFormFactory.

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

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


// ✅ uso della composizione
class EditFormFactory
{
	public function __construct(
		private FormFactory $formFactory,
	) {
	}

	public function create(): Form
	{
		$form = $this->formFactory->create();
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		return $form;
	}
}

È molto importante che il legame tra le classi FormFactory e EditFormFactory sia realizzato con la composizione e non con l'ereditarietà tra oggetti:

// ⛔ NO! L'EREDITARIETÀ QUI NON C'ENTRA
class EditFormFactory extends FormFactory
{
	public function create(): Form
	{
		$form = parent::create();
		$form->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		return $form;
	}
}

Usare qui l'ereditarietà sarebbe del tutto controproducente. Incontrereste dei problemi molto in fretta. Per esempio, nel momento in cui voleste aggiungere dei parametri al metodo create(), PHP segnalerebbe un errore perché la sua firma sarebbe diversa da quella dell'antenato. Oppure nel momento in cui passaste alla classe EditFormFactory una dipendenza tramite il costruttore. Nascerebbe quello che si chiama inferno dei costruttori.

In generale è meglio preferire la composizione all'ereditarietà.

Gestione del form

Anche il gestore del form, che viene richiamato dopo l'invio riuscito, può far parte della classe factory. Funzionerà passando i dati inviati al modello perché li elabori. Gli eventuali errori li restituirà al form. Il modello nell'esempio seguente è rappresentato dalla classe Facade:

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

	public function create(): Form
	{
		$form = $this->formFactory->create();
		$form->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		$form->onSuccess[] = $this->processForm(...);
		return $form;
	}

	private function processForm(Form $form, array $data): void
	{
		try {
			// elaborazione dei dati inviati
			$this->facade->process($data);

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

Il redirect vero e proprio lo lasciamo però al presenter. Esso aggiunge all'evento onSuccess un altro gestore, che esegue il redirect. Grazie a questo si potrà usare il form in presenter diversi e ognuno reindirizzerà altrove.

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('Il record è stato salvato');
			$this->redirect('Homepage:');
		};
		return $form;
	}
}

Questa soluzione sfrutta la proprietà dei form per cui, se sul form o su un suo elemento viene chiamato addError(), i gestori onSuccess successivi non vengono più richiamati.

Ereditare dalla classe Form

Un form assemblato non dovrebbe essere un discendente della classe Form. In altre parole, non usate questa soluzione:

// ⛔ NO! L'EREDITARIETÀ QUI NON C'ENTRA
class EditForm extends Form
{
	public function __construct(Translator $translator)
	{
		parent::__construct();
		$this->addText('title', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$this->addSubmit('send', 'Salva');
		$this->setTranslator($translator);
	}
}

Invece di assemblare il form nel costruttore, usate una factory.

È importante rendersi conto che la classe Form è anzitutto uno strumento per assemblare i form, cioè un form builder. E il form assemblato si può considerare il suo prodotto. Ma il prodotto non è un caso particolare del builder, tra loro non c'è la relazione is a su cui si fonda l'ereditarietà.

Componente con form

Un approccio del tutto diverso consiste nel creare un componente che contiene un form. Questo dà nuove possibilità, per esempio renderizzare il form in un modo particolare, dato che il componente ha un proprio template. Oppure si possono usare i segnali per la comunicazione AJAX e per caricare informazioni nel form, per esempio per i suggerimenti, e così via.

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', 'Titolo:');
		// qui si aggiungono gli altri campi del form
		$form->addSubmit('send', 'Salva');
		$form->onSuccess[] = $this->processForm(...);

		return $form;
	}

	private function processForm(Form $form, array $data): void
	{
		try {
			// elaborazione dei dati inviati
			$this->facade->process($data);

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

		// richiamo dell'evento
		$this->onSave($this, $data);
	}
}

Creiamo poi una factory che produrrà questo componente. Basta definirne l'interfaccia:

interface EditControlFactory
{
	function create(): EditControl;
}

E aggiungerla al file di configurazione:

services:
	- EditControlFactory

E ora possiamo farci passare la factory e usarla nel 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');
			// oppure redirect al risultato della modifica, per esempio:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}