Riutilizzo dei moduli in più punti
In Nette avete diverse opzioni per utilizzare lo stesso modulo in più punti senza duplicare il codice. In questo articolo mostreremo diverse soluzioni, comprese quelle che dovreste evitare.
Factory per moduli
Uno degli approcci fondamentali per utilizzare lo stesso componente in più punti è creare un metodo o una classe che genera questo componente e successivamente chiamare questo metodo in diversi punti dell'applicazione. Tale metodo o classe viene chiamato factory. Si prega di non confondere con il pattern di progettazione factory method, che descrive un modo specifico di utilizzare le factory e non è correlato a questo argomento.
Come esempio, creiamo una factory che costruirà un modulo di modifica:
use Nette\Application\UI\Form;
class FormFactory
{
public function createEditForm(): Form
{
$form = new Form;
$form->addText('title', 'Titolo:');
// qui vengono aggiunti altri campi del modulo
$form->addSubmit('send', 'Invia');
return $form;
}
}
Ora potete utilizzare questa factory in diversi punti della vostra applicazione, ad esempio nei presenter o nei componenti. E questo richiedendola come dipendenza. Prima di tutto, quindi, registriamo la classe nel file di configurazione:
services:
- FormFactory
E poi la utilizziamo 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;
}
}
Potete estendere la factory dei moduli con altri metodi per creare altri tipi di moduli secondo le esigenze della vostra applicazione. E naturalmente possiamo aggiungere anche un metodo che crei un modulo base senza elementi, e che gli altri metodi utilizzeranno:
class FormFactory
{
public function createForm(): Form
{
$form = new Form;
return $form;
}
public function createEditForm(): Form
{
$form = $this->createForm();
$form->addText('title', 'Titolo:');
// qui vengono aggiunti altri campi del modulo
$form->addSubmit('send', 'Invia');
return $form;
}
}
Il metodo createForm()
per ora non fa nulla di utile, ma questo cambierà rapidamente.
Dipendenze della factory
Col tempo si scoprirà che abbiamo bisogno che i moduli siano multilingue. Ciò significa che a tutti i moduli dobbiamo
impostare il cosiddetto translator. A tal fine,
modifichiamo la classe FormFactory
in modo che accetti l'oggetto Translator
come dipendenza nel
costruttore e lo passiamo al modulo:
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 moduli specifici, è sufficiente
impostare il translator solo lì. E abbiamo finito. Non è necessario modificare il codice di nessun presenter o componente, il
che è fantastico.
Più classi factory
In alternativa, potete creare più classi per ogni modulo che volete utilizzare nella vostra applicazione. Questo approccio
può aumentare la leggibilità del codice e facilitare la gestione dei moduli. Lasciamo che la FormFactory
originale
crei solo un modulo pulito con la configurazione di base (ad esempio con il supporto alle traduzioni) e per il modulo 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 vengono aggiunti altri campi del modulo
$form->addSubmit('send', 'Invia');
return $form;
}
}
È molto importante che la relazione tra le classi FormFactory
e EditFormFactory
sia realizzata
tramite composizione, e non
tramite ereditarietà degli
oggetti:
// ⛔ NON COSÌ! L'EREDITARIETÀ NON APPARTIENE QUI
class EditFormFactory extends FormFactory
{
public function create(): Form
{
$form = parent::create();
$form->addText('title', 'Titolo:');
// qui vengono aggiunti altri campi del modulo
$form->addSubmit('send', 'Invia');
return $form;
}
}
L'uso dell'ereditarietà sarebbe in questo caso del tutto controproducente. Incontrereste problemi molto rapidamente. Ad
esempio, nel momento in cui voleste aggiungere parametri al metodo create()
; PHP segnalerebbe un errore indicando che
la sua firma differisce da quella del genitore. Oppure passando una dipendenza alla classe EditFormFactory
tramite il
costruttore. Si verificherebbe una situazione che chiamiamo constructor hell.
In generale, è meglio preferire la composizione all'ereditarietà.
Gestione del modulo
La gestione del modulo, che viene chiamata dopo l'invio riuscito, può anche far parte della classe factory. Funzionerà
passando i dati inviati al modello per l'elaborazione. Eventuali errori verranno restituiti al modulo. 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 vengono aggiunti altri campi del modulo
$form->addSubmit('send', 'Invia');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// elaborazione dei dati inviati
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
}
}
}
Tuttavia, lasceremo il reindirizzamento effettivo al presenter. Aggiungerà un altro gestore all'evento onSuccess
,
che eseguirà il reindirizzamento. Grazie a ciò, sarà possibile utilizzare il modulo in diversi presenter e reindirizzare a un
luogo diverso in ciascuno di essi.
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 moduli secondo cui, quando viene chiamato addError()
sul modulo o su
un suo elemento, il successivo gestore onSuccess
non viene più chiamato.
Ereditarietà dalla classe Form
Il modulo costruito non deve essere un discendente del modulo. In altre parole, non utilizzate questa soluzione:
// ⛔ NON COSÌ! L'EREDITARIETÀ NON APPARTIENE QUI
class EditForm extends Form
{
public function __construct(Translator $translator)
{
parent::__construct();
$this->addText('title', 'Titolo:');
// qui vengono aggiunti altri campi del modulo
$this->addSubmit('send', 'Invia');
$this->setTranslator($translator);
}
}
Invece di costruire il modulo nel costruttore, utilizzate una factory.
È necessario rendersi conto che la classe Form
è principalmente uno strumento per costruire un modulo, ovvero un
form builder. E il modulo costruito può essere considerato come il suo prodotto. Ma il prodotto non è un caso specifico
del builder, non c'è tra loro una relazione is a che costituisce la base dell'ereditarietà.
Componente con modulo
Un approccio completamente diverso è la creazione di un componente, che include un modulo. Questo offre nuove possibilità, ad esempio renderizzare il modulo in modo specifico, poiché il componente include anche un template. Oppure è possibile utilizzare i segnali per la comunicazione AJAX e il caricamento dinamico di informazioni nel modulo, ad esempio per i suggerimenti, ecc.
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 vengono aggiunti altri campi del modulo
$form->addSubmit('send', 'Invia');
$form->onSuccess[] = [$this, 'processForm'];
return $form;
}
public function processForm(Form $form, array $data): void
{
try {
// elaborazione dei dati inviati
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
return;
}
// attivazione dell'evento
$this->onSave($this, $data);
}
}
Creeremo anche una factory che produrrà questo componente. È sufficiente scrivere la sua interfaccia:
interface EditControlFactory
{
function create(): EditControl;
}
E aggiungerla al file di configurazione:
services:
- EditControlFactory
E ora possiamo richiedere la factory e utilizzarla 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');
// o reindirizziamo al risultato della modifica, ad esempio:
// $this->redirect('detail', ['id' => $data->id]);
};
return $control;
}
}