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.