Component Model

An important concept in Nette is the component. We insert visual interactive components into pages; forms and all their elements are also components. The two basic classes from which all these components inherit are part of the nette/component-model package and are responsible for creating the component tree hierarchy.

Component

Nette\ComponentModel\Component is the common ancestor of all components. It contains the getName() method returning the name of the component and the getParent() method returning its parent. You don't set either of them yourself: the container sets both when you add the component to it. (It uses the setParent() method for that, which is internal, so don't call it from your code.)

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

Searches up the hierarchy for an object of the desired class or interface. For example, $component->lookup(Nette\Application\UI\Presenter::class) returns the presenter if the component is connected to it, even across several levels. If no matching object is found, it throws an exception; pass false as the second argument to return null instead. If you pass null as the $type, the method searches for the topmost component in the tree, i.e., the root with no parent.

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

Returns the so-called path, which is a string formed by concatenating the names of all components on the path between the current component and the component being searched for. So, for example, $component->lookupPath(Nette\Application\UI\Presenter::class) returns the unique identifier of the component relative to the presenter. When $type is null (or omitted), the path is measured up to the root of the tree.

Container

Nette\ComponentModel\Container is the parent component, i.e., the component containing children and thus forming the tree structure. It has methods for easily adding, retrieving, and removing objects. It is the ancestor of, for example, the form or classes Control and Presenter. Descendants that use the ArrayAccess trait (such as Control and Presenter) also allow accessing children via array notation, e.g. $container['child'].

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

Adds a component to the container as a child. If $name is null, the component's own name is used. Using the optional $insertBefore – the name of an existing child – the new component is inserted right before it; otherwise it is appended to the end. The method returns the container itself, so calls can be chained.

The name may contain only letters, digits and underscores, because the hyphen joins names into a path (see getComponent()); any other name throws Nette\InvalidArgumentException. If the container already has a child of that name, or the component already has a parent, the method throws Nette\InvalidStateException.

removeComponent (IComponent $component): void

Removes a child component from the container. If the component is not its child, it throws Nette\InvalidArgumentException.

The component stays in the container until its $detached callbacks (see monitoring ancestors) have run, so they still see it in its place.

getComponent (string $name, bool $throw=true): ?IComponent

Returns a component. Attempting to retrieve an undefined child invokes the factory method createComponent($name). The createComponent($name) method calls the method createComponent<component name> in the current component, passing the component name as a parameter. The created component is then added to the current component as its child. We call these methods component factories, and they can be implemented in classes inherited from Container.

The name can also be a path to a deeper descendant, with the names joined by a hyphen: $form->getComponent('address-street') returns the street component inside the address container. If there is no such component, the method throws Nette\InvalidArgumentException and suggests a similar name if it finds one, for example Component with name 'adress' does not exist, did you mean 'address'?. Pass false as the second argument to get null instead.

getComponents(): IComponent[]

Returns direct descendants as an array; the keys contain the names of these components. To retrieve the entire subtree recursively, use getComponentTree(), optionally combined with array_filter() for type filtering. (The $deep and $filterType parameters known from older versions were removed in version 4.0.)

getComponentTree(): list<IComponent>

Gets the entire hierarchy of components, including all nested child components, as an indexed array. The search is depth-first.

Monitoring Ancestors

The Nette component model allows for very dynamic work with the tree (we can remove, move, add components), so it would be a mistake to rely on the fact that after creating a component, the parent, parent's parent, etc., are known immediately (in the constructor). Usually, the parent is not known at all when the component is created.

How can a component find out the moment it is attached below a presenter – or below any other ancestor of a given type? Watching the direct parent isn't enough, because the connection may happen higher up the tree, for example when the parent's parent gets attached. That is what the monitor($type, $attached, $detached) method is for: a component declares that it wants to be notified whenever an ancestor of class or interface $type appears above it in the tree, or disappears from it. A component can monitor any number of types; the $attached callback fires when a matching ancestor connects and receives that ancestor as its argument, while $detached fires when it disconnects. Monitoring can be stopped again with unmonitor($type).

If the ancestor is already there when you call monitor(), the $attached callback runs right away. So it doesn't matter whether the component starts monitoring before or after it is attached.

Notifications follow the tree structure. When attaching, an ancestor is notified before its descendants (top-down), so a parent can prepare shared state first, or even remove a child before the child's own callback runs. When detaching, the order is reversed – descendants are notified first. A callback registered for several types is called only once for the same ancestor, and registering the same callback a second time has no effect. For the reasoning behind this behavior, see the blog post about version 4.0.

For better understanding, here's an example: The UploadControl class, representing the form control for uploading files in Nette Forms, must set the form's enctype attribute to multipart/form-data. However, at the time the object is created, it might not be attached to any form. So, at what point should the form be modified? The solution is simple – a request for monitoring is made in the constructor:

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

	// ...
}

and as soon as the form becomes available, the callback is invoked.

Refusing to Be Attached

A callback can also stop the change. When $attached throws an exception, the component is not attached at all: the change is rolled back, the callbacks that have already run get their $detached counterpart, and the exception goes on to the code that was adding the component. This is how Nette Forms refuse to place a form inside another form. Here a wizard refuses to live inside a form:

class Wizard extends Nette\ComponentModel\Container
{
	public function __construct()
	{
		$this->monitor(Nette\Forms\Form::class, function (): void {
			throw new Nette\InvalidStateException('A wizard cannot be placed inside a form.');
		});
	}
}

$form->addComponent(new Wizard, 'wizard'); // throws the exception, $form stays as it was

The same works the other way round: a $detached callback that throws keeps the component where it is.

Changing the Tree in a Callback

Callbacks may change the tree themselves: remove their own component, a sibling, or an ancestor. The notifications take it into account, so a component that an earlier callback has removed from the tree is no longer notified.

A $detached callback runs before the component is actually detached, so lookup() inside it still finds the ancestor being left.

Cloning and Serialization

A component can be copied with clone. The copy gets copies of all its descendants, but no parent: it starts outside the tree, so its monitors receive $detached from the ancestors of the original. Inside the copy, lookup() sees the copy's own tree.

Components cannot be serialized; serialize() throws Nette\NotImplementedException.

If you are upgrading to a newer version, see the upgrading page.

version: 4.x 3.x