Компонентная модель
Важное понятие в Nette – компонент. В страницы мы вставляем визуальные интерактивные компоненты;
компонентами являются и формы, и все их элементы. Два базовых класса, от
которых наследуются все эти компоненты, входят в пакет
nette/component-model и отвечают за создание иерархии дерева
компонентов.
Component
Nette\ComponentModel\Component –
общий предок всех компонентов. У него есть метод getName(),
возвращающий имя компонента, и метод getParent(), возвращающий его
родителя. Оба можно задать методом setParent(): первый параметр –
родитель, второй – имя компонента.
lookup (?string $type, bool $throw=true): ?Component
Ищет вверх по иерархии объект нужного класса или интерфейса.
Например, $component->lookup(Nette\Application\UI\Presenter::class) возвращает
презентер, если компонент к нему подключён, пусть даже через несколько
уровней. Если подходящий объект не найден, метод выбрасывает
исключение; передайте вторым аргументом false, чтобы вместо этого
вернулся null. Если в качестве $type передать null, метод
ищет самый верхний компонент в дереве, то есть корень без родителя.
lookupPath (?string $type=null, bool $throw=true): ?string
Возвращает так называемый путь – строку, образованную соединением
имён всех компонентов на пути между текущим компонентом и искомым. Так
что, например, $component->lookupPath(Nette\Application\UI\Presenter::class) возвращает
уникальный идентификатор компонента относительно презентера. Когда
$type равен null (или опущен), путь отмеряется до корня
дерева.
Container
Nette\ComponentModel\Container –
родительский компонент, то есть компонент, содержащий потомков и тем
самым образующий древовидную структуру. У него есть методы для
удобного добавления, получения и удаления объектов. Он является
предком, например, формы или классов Control и Presenter. Потомки,
использующие трейт ArrayAccess (такие как Control и Presenter),
позволяют обращаться к потомкам и записью через массив, например
$container['child'].
addComponent (Component $component, ?string $name, ?string $insertBefore=null): static
Добавляет компонент в контейнер как потомка. Если $name равно
null, используется собственное имя компонента. С помощью
необязательного $insertBefore – имени существующего потомка –
новый компонент вставляется прямо перед ним; иначе он добавляется в
конец. Метод возвращает сам контейнер, так что вызовы можно объединять
в цепочку.
removeComponent (Component $component): void
Удаляет компонент-потомок из контейнера.
getComponent (string $name): ?Component
Возвращает компонент. Попытка получить неопределённого потомка
вызывает фабричный метод createComponent($name). Метод createComponent($name)
вызывает в текущем компоненте метод
createComponent<имя компонента>, передавая имя компонента
параметром. Созданный компонент затем добавляется в текущий компонент
как его потомок. Эти методы мы называем фабриками компонентов, и их
можно реализовать в классах, унаследованных от Container.
getComponents(): IComponent[]
Возвращает непосредственных потомков массивом; в ключах находятся
имена этих компонентов. Чтобы получить всё поддерево рекурсивно,
используйте getComponentTree(), при необходимости в сочетании с
array_filter() для фильтрации по типу. (Параметры $deep и
$filterType, известные по прежним версиям, в версии 4.0 удалены.)
getComponentTree(): list<IComponent>
Получает всю иерархию компонентов, включая все вложенные компоненты-потомки, в виде индексированного массива. Обход идёт в глубину.
Наблюдение за предками
Компонентная модель Nette позволяет очень динамично работать с деревом (мы можем удалять, перемещать, добавлять компоненты), поэтому было бы ошибкой полагаться на то, что сразу после создания компонента (в конструкторе) известны родитель, родитель родителя и т. д. Обычно при создании компонента родитель вообще неизвестен.
Как компоненту узнать момент, когда он окажется под презентером или
под любым другим предком заданного типа? Следить за непосредственным
родителем недостаточно, потому что соединение может произойти выше по
дереву, например когда присоединяется родитель родителя. Для этого и
служит метод monitor($type, $attached,
$detached): компонент объявляет, что хочет получать уведомление всякий
раз, когда над ним в дереве появляется предок класса или интерфейса
$type либо исчезает из него. Компонент может наблюдать за
сколькими угодно типами; callback $attached срабатывает, когда
подходящий предок подключается, и получает этого предка аргументом, а
$detached срабатывает при отключении. Наблюдение можно снова
прекратить через unmonitor($type).
Уведомления следуют структуре дерева. При присоединении предок уведомляется раньше своих потомков (сверху вниз), так что родитель может сначала подготовить общее состояние или даже удалить потомка до того, как выполнится собственный callback потомка. При отсоединении порядок обратный: сначала уведомляются потомки. Callback'и к тому же дедуплицируются, так что один и тот же callback никогда не вызывается дважды для одного объекта. Обоснование такого поведения смотрите в записи блога о версии 4.0.
Для лучшего понимания вот пример: класс UploadControl,
представляющий элемент формы для загрузки файлов в Nette Forms, должен
задать форме атрибут enctype со значением multipart/form-data. Однако
в момент создания объекта он может быть ещё не присоединён ни к какой
форме. Так в какой же момент изменить форму? Решение простое: запрос на
наблюдение делается в конструкторе:
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');
});
// ...
}
// ...
}
и как только форма станет доступна, callback будет вызван.
Если вы переходите на более новую версию, посмотрите страницу обновления.