Component Model
Un concetto importante in Nette è il componente. Nelle pagine inseriamo componenti visivi interattivi; anche i form e tutti i loro elementi
sono componenti. Le due classi di base da cui tutti questi componenti ereditano fanno parte del pacchetto
nette/component-model e si occupano di creare la gerarchia dell'albero dei componenti.
Component
Nette\ComponentModel\Component
è l'antenato comune di tutti i componenti. Contiene il metodo getName(), che restituisce il nome del componente, e
il metodo getParent(), che ne restituisce il genitore. Entrambi si possono impostare con il metodo
setParent(): il primo parametro è il genitore, il secondo il nome del componente.
lookup (?string $type, bool $throw=true): ?Component
Cerca verso l'alto nella gerarchia un oggetto della classe o dell'interfaccia desiderata. Per esempio
$component->lookup(Nette\Application\UI\Presenter::class) restituisce il presenter se il componente vi è
collegato, anche attraverso più livelli. Se non trova alcun oggetto corrispondente, lancia un'eccezione; passate
false come secondo argomento per far restituire invece null. Se passate null come
$type, il metodo cerca il componente più in alto nell'albero, cioè la radice senza genitore.
lookupPath (?string $type=null, bool $throw=true): ?string
Restituisce il cosiddetto percorso, cioè una stringa formata concatenando i nomi di tutti i componenti sul cammino tra il
componente corrente e quello cercato. Per esempio $component->lookupPath(Nette\Application\UI\Presenter::class)
restituisce l'identificatore univoco del componente rispetto al presenter. Quando $type è null (oppure
viene omesso), il percorso si misura fino alla radice dell'albero.
Container
Nette\ComponentModel\Container
è il componente genitore, cioè il componente che contiene dei figli e forma così la struttura ad albero. Ha metodi per
aggiungere, ottenere e rimuovere facilmente gli oggetti. È l'antenato per esempio del form o delle classi Control e
Presenter. I discendenti che usano il trait ArrayAccess (come Control e
Presenter) permettono anche di accedere ai figli con la notazione ad array, per esempio
$container['child'].
addComponent (Component $component, ?string $name, ?string $insertBefore=null): static
Aggiunge un componente al container come figlio. Se $name è null, viene usato il nome proprio del
componente. Con il parametro facoltativo $insertBefore, cioè il nome di un figlio esistente, il nuovo componente
viene inserito subito prima di esso; altrimenti viene accodato alla fine. Il metodo restituisce il container stesso, così le
chiamate si possono concatenare.
removeComponent (Component $component): void
Rimuove un componente figlio dal container.
getComponent (string $name): ?Component
Restituisce un componente. Il tentativo di ottenere un figlio non definito richiama il metodo factory
createComponent($name). Il metodo createComponent($name) chiama nel componente corrente il metodo
createComponent<nome del componente>, passandogli come parametro il nome del componente. Il componente creato
viene poi aggiunto al componente corrente come suo figlio. Questi metodi li chiamiamo factory di componenti e si possono
realizzare nelle classi che ereditano da Container.
getComponents(): IComponent[]
Restituisce i discendenti diretti come array; le chiavi contengono i nomi di questi componenti. Per ottenere ricorsivamente
l'intero sottoalbero usate getComponentTree(), eventualmente combinato con array_filter() per filtrare
per tipo. (I parametri $deep e $filterType noti dalle versioni più vecchie sono stati rimossi nella
versione 4.0.)
getComponentTree(): list<IComponent>
Ottiene l'intera gerarchia dei componenti, compresi tutti i componenti figli annidati, come array indicizzato. La ricerca è in profondità.
Sorvegliare gli antenati
Il modello a componenti di Nette permette di lavorare con l'albero in modo molto dinamico (possiamo rimuovere, spostare, aggiungere componenti), quindi sarebbe un errore contare sul fatto che dopo la creazione di un componente si conoscano subito (nel costruttore) il genitore, il genitore del genitore ecc. Di solito, quando il componente viene creato, il genitore non è affatto noto.
Come può un componente scoprire il momento in cui viene collegato sotto un presenter, o sotto un altro antenato di un dato
tipo? Sorvegliare il genitore diretto non basta, perché il collegamento può avvenire più in alto nell'albero, per esempio
quando viene collegato il genitore del genitore. È a questo che serve il metodo monitor($type, $attached,
$detached): un componente dichiara di voler essere avvisato ogni volta che sopra di lui nell'albero compare, o da esso
sparisce, un antenato della classe o dell'interfaccia $type. Un componente può sorvegliare un numero qualsiasi di
tipi; il callback $attached scatta quando un antenato corrispondente si collega e riceve quell'antenato come
argomento, mentre $detached scatta quando si scollega. La sorveglianza si può interrompere con
unmonitor($type).
Le notifiche seguono la struttura dell'albero. Al collegamento un antenato viene avvisato prima dei suoi discendenti (dall'alto verso il basso), così un genitore può preparare per primo lo stato condiviso, o perfino rimuovere un figlio prima che il callback del figlio venga eseguito. Al distacco l'ordine è invertito: vengono avvisati per primi i discendenti. I callback vengono inoltre deduplicati, così lo stesso callback non viene mai chiamato due volte per lo stesso oggetto. Il ragionamento dietro questo comportamento lo trovate nell'articolo del blog sulla versione 4.0.
Per capire meglio, ecco un esempio: la classe UploadControl, che rappresenta in Nette Forms il controllo per
caricare i file, deve impostare l'attributo enctype del form a multipart/form-data. Nel momento in cui
l'oggetto viene creato, però, può non essere collegato ad alcun form. In quale momento va quindi modificato il form? La
soluzione è semplice: la richiesta di sorveglianza si fa nel costruttore:
class UploadControl extends Nette\Forms\Controls\BaseControl
{
public function __construct($label)
{
$this->monitor(Nette\Forms\Form::class, function ($form): void {
$form->setHtmlAttribute('enctype', 'multipart/form-data');
});
// ...
}
// ...
}
e appena il form diventa disponibile, il callback viene richiamato.
Se state aggiornando a una versione più recente, guardate la pagina aggiornamento.