Modelo de componentes

Un concepto importante en Nette es el componente. En las páginas insertamos componentes visuales interactivos; los formularios y todos sus elementos también son componentes. Las dos clases básicas de las que heredan todos estos componentes forman parte del paquete nette/component-model y se encargan de crear la jerarquía del árbol de componentes.

Component

Nette\ComponentModel\Component es el antecesor común de todos los componentes. Contiene el método getName(), que devuelve el nombre del componente, y el método getParent(), que devuelve su padre. Ambos se pueden establecer con el método setParent(): el primer parámetro es el padre y el segundo el nombre del componente.

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

Busca hacia arriba en la jerarquía un objeto de la clase o interfaz deseada. Por ejemplo, $component->lookup(Nette\Application\UI\Presenter::class) devuelve el presenter si el componente está conectado a él, aunque sea a través de varios niveles. Si no encuentra ningún objeto correspondiente, lanza una excepción; pase false como segundo argumento para que devuelva null en su lugar. Si pasa null como $type, el método busca el componente más alto del árbol, es decir, la raíz sin padre.

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

Devuelve la llamada ruta, que es una cadena formada al concatenar los nombres de todos los componentes del camino entre el componente actual y el componente buscado. Así, por ejemplo, $component->lookupPath(Nette\Application\UI\Presenter::class) devuelve el identificador único del componente relativo al presenter. Cuando $type es null (o se omite), la ruta se mide hasta la raíz del árbol.

Container

Nette\ComponentModel\Container es el componente padre, es decir, el componente que contiene hijos y que forma así la estructura de árbol. Tiene métodos para añadir, obtener y eliminar objetos con facilidad. Es el antecesor, por ejemplo, del formulario o de las clases Control y Presenter. Los descendientes que usan el trait ArrayAccess (como Control y Presenter) permiten además acceder a los hijos con la notación de array, p. ej. $container['child'].

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

Añade un componente al contenedor como hijo. Si $name es null, se usa el nombre propio del componente. Con el $insertBefore opcional, el nombre de un hijo existente, el componente nuevo se inserta justo antes de él; en caso contrario se añade al final. El método devuelve el propio contenedor, así que las llamadas se pueden encadenar.

removeComponent (Component $component)void

Elimina un componente hijo del contenedor.

getComponent (string $name): ?Component

Devuelve un componente. Intentar obtener un hijo no definido invoca el método factory createComponent($name). El método createComponent($name) llama al método createComponent<nombre del componente> del componente actual y le pasa como parámetro el nombre del componente. El componente creado se añade después al componente actual como hijo suyo. A estos métodos los llamamos factories de componentes y se pueden implementar en las clases que heredan de Container.

getComponents(): IComponent[]

Devuelve los descendientes directos como array; las claves contienen los nombres de esos componentes. Para obtener todo el subárbol de forma recursiva, use getComponentTree(), combinado opcionalmente con array_filter() para filtrar por tipo. (Los parámetros $deep y $filterType conocidos de las versiones anteriores se eliminaron en la versión 4.0.)

getComponentTree(): list<IComponent>

Obtiene toda la jerarquía de componentes, incluidos todos los componentes hijos anidados, como array indexado. La búsqueda es en profundidad.

Monitorizar los antecesores

El modelo de componentes de Nette permite trabajar con el árbol de forma muy dinámica (podemos eliminar, mover y añadir componentes), así que sería un error confiar en que, tras crear un componente, el padre, el padre del padre, etc. se conocen de inmediato (en el constructor). Normalmente, cuando se crea el componente el padre no se conoce en absoluto.

¿Cómo puede averiguar un componente el momento en que se adjunta bajo un presenter, o bajo cualquier otro antecesor de un tipo dado? Vigilar al padre directo no basta, porque la conexión puede ocurrir más arriba en el árbol, por ejemplo cuando se adjunta el padre del padre. Para eso sirve el método monitor($type, $attached, $detached): un componente declara que quiere que se le avise siempre que por encima de él aparezca en el árbol un antecesor de la clase o interfaz $type, o que desaparezca de él. Un componente puede monitorizar cualquier número de tipos; el callback $attached se dispara cuando se conecta un antecesor correspondiente y lo recibe como argumento, mientras que $detached se dispara cuando se desconecta. La monitorización se puede detener de nuevo con unmonitor($type).

Las notificaciones siguen la estructura del árbol. Al adjuntar, se avisa antes al antecesor que a sus descendientes (de arriba abajo), así que un padre puede preparar primero el estado compartido, o incluso eliminar un hijo antes de que se ejecute el callback de ese hijo. Al desconectar, el orden se invierte: se avisa primero a los descendientes. Los callbacks también se deduplican, así que el mismo callback nunca se llama dos veces para el mismo objeto. El razonamiento tras este comportamiento está en la entrada del blog sobre la versión 4.0.

Para entenderlo mejor, aquí tiene un ejemplo: la clase UploadControl, que representa el elemento de formulario para subir archivos en Nette Forms, tiene que establecer el atributo enctype del formulario a multipart/form-data. Pero, en el momento en que se crea el objeto, puede que no esté adjunto a ningún formulario. Entonces, ¿en qué momento hay que modificar el formulario? La solución es sencilla: en el constructor se hace una petición de monitorización:

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

	// ...
}

y, en cuanto el formulario está disponible, se invoca el callback.

Si está actualizando a una versión más reciente, vea la página de actualización.

versión: 4.x