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.

versione: 4.x