Model komponentów

Ważnym pojęciem w Nette jest komponent. Do stron wstawiamy wizualne interaktywne komponenty; formularze i wszystkie ich elementy również są komponentami. Dwie podstawowe klasy, po których dziedziczą wszystkie te komponenty, są częścią pakietu nette/component-model i odpowiadają za tworzenie hierarchii drzewa komponentów.

Component

Nette\ComponentModel\Component to wspólny przodek wszystkich komponentów. Zawiera metodę getName() zwracającą nazwę komponentu i metodę getParent() zwracającą jego rodzica. Oba można ustawić metodą setParent(): pierwszym parametrem jest rodzic, a drugim nazwa komponentu.

lookup (?string $type, bool $throw=true): ?Component

Szuka w górę hierarchii obiektu pożądanej klasy albo interfejsu. Na przykład $component->lookup(Nette\Application\UI\Presenter::class) zwraca presenter, jeśli komponent jest do niego podłączony, nawet przez kilka poziomów. Jeśli pasujący obiekt nie zostanie znaleziony, rzuca wyjątek; przekaż jako drugi argument false, żeby zamiast tego zwrócić null. Jeśli przekażesz jako $type wartość null, metoda szuka najwyższego komponentu w drzewie, czyli korzenia bez rodzica.

lookupPath (?string $type=null, bool $throw=true): ?string

Zwraca tak zwaną ścieżkę, czyli ciąg powstały z połączenia nazw wszystkich komponentów na drodze między bieżącym komponentem a komponentem szukanym. Na przykład $component->lookupPath(Nette\Application\UI\Presenter::class) zwraca unikalny identyfikator komponentu względem presentera. Gdy $type to null (albo jest pominięty), ścieżka mierzona jest do korzenia drzewa.

Container

Nette\ComponentModel\Container to komponent rodzicielski, czyli komponent zawierający dzieci i tworzący tym samym strukturę drzewiastą. Ma metody do łatwego dodawania, pobierania i usuwania obiektów. Jest przodkiem na przykład formularza albo klas Control i Presenter. Potomkowie używający traitu ArrayAccess (jak Control i Presenter) pozwalają też sięgać po dzieci zapisem tablicowym, np. $container['child'].

addComponent (Component $component, ?string $name, ?string $insertBefore=null)static

Dodaje komponent do kontenera jako dziecko. Jeśli $name to null, używana jest własna nazwa komponentu. Za pomocą opcjonalnego $insertBefore, czyli nazwy istniejącego dziecka, nowy komponent wstawiany jest tuż przed nim; w przeciwnym razie dołączany jest na koniec. Metoda zwraca sam kontener, więc wywołania można łączyć w łańcuch.

removeComponent (Component $component)void

Usuwa komponent potomny z kontenera.

getComponent (string $name): ?Component

Zwraca komponent. Próba pobrania niezdefiniowanego dziecka wywołuje metodę fabrykującą createComponent($name). Metoda createComponent($name) wywołuje w bieżącym komponencie metodę createComponent<nazwa komponentu>, przekazując nazwę komponentu jako parametr. Utworzony komponent dodawany jest następnie do bieżącego komponentu jako jego dziecko. Metody te nazywamy fabrykami komponentów i można je implementować w klasach dziedziczących po Container.

getComponents(): IComponent[]

Zwraca bezpośrednich potomków jako tablicę; klucze zawierają nazwy tych komponentów. Żeby pobrać rekurencyjnie całe poddrzewo, użyj getComponentTree(), opcjonalnie w połączeniu z array_filter() do filtrowania po typie. (Parametry $deep i $filterType znane ze starszych wersji zostały usunięte w wersji 4.0.)

getComponentTree(): list<IComponent>

Pobiera całą hierarchię komponentów, wraz ze wszystkimi zagnieżdżonymi komponentami potomnymi, jako tablicę indeksowaną. Przeszukiwanie odbywa się w głąb.

Monitorowanie przodków

Model komponentów Nette pozwala bardzo dynamicznie pracować z drzewem (możemy usuwać, przenosić, dodawać komponenty), więc błędem byłoby poleganie na tym, że po utworzeniu komponentu rodzic, rodzic rodzica itd. są znani natychmiast (w konstruktorze). Zwykle przy tworzeniu komponentu rodzic w ogóle nie jest znany.

Jak komponent może dowiedzieć się o momencie, w którym zostaje podłączony pod presenter albo pod dowolnego innego przodka danego typu? Obserwowanie bezpośredniego rodzica nie wystarczy, bo połączenie może nastąpić wyżej w drzewie, na przykład gdy podłączany jest rodzic rodzica. Do tego służy metoda monitor($type, $attached, $detached): komponent deklaruje, że chce być powiadamiany zawsze, gdy nad nim w drzewie pojawi się przodek klasy albo interfejsu $type albo gdy z niego zniknie. Komponent może monitorować dowolną liczbę typów; callback $attached uruchamia się, gdy pasujący przodek się podłączy, i otrzymuje tego przodka jako argument, a $detached uruchamia się, gdy się odłączy. Monitorowanie można znowu zatrzymać przez unmonitor($type).

Powiadomienia podążają za strukturą drzewa. Przy dołączaniu przodek powiadamiany jest przed swoimi potomkami (z góry na dół), więc rodzic może najpierw przygotować wspólny stan albo nawet usunąć dziecko, zanim uruchomi się jego własny callback. Przy odłączaniu kolejność jest odwrotna: najpierw powiadamiani są potomkowie. Callbacki są też deduplikowane, więc ten sam callback nigdy nie jest wywoływany dwa razy dla tego samego obiektu. Uzasadnienie tego zachowania znajdziesz we wpisie na blogu o wersji 4.0.

Dla lepszego zrozumienia oto przykład: klasa UploadControl, reprezentująca element formularza do wysyłania plików w Nette Forms, musi ustawić formularzowi atrybut enctype na multipart/form-data. W momencie utworzenia obiektu może jednak nie być podłączona do żadnego formularza. W którym więc momencie formularz zmodyfikować? Rozwiązanie jest proste: żądanie monitorowania składa się w konstruktorze:

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');
		});
		// ...
	}

	// ...
}

a gdy tylko formularz stanie się dostępny, callback zostanie wywołany.

Jeśli aktualizujesz do nowszej wersji, zajrzyj na stronę aktualizacji.

wersja: 4.x