}> means [type => [obj, depth, path, [attached, detached]]] */ private $monitors = []; /** * Finds the closest ancestor specified by class or interface name. * @param bool $throw throw exception if component doesn't exist? */ public final function lookup(?string $type, bool $throw = \true) : ?IComponent { if (!isset($this->monitors[$type])) { // not monitored or not processed yet $obj = $this->parent; $path = self::NameSeparator . $this->name; $depth = 1; while ($obj !== null) { $parent = $obj->getParent(); if ($type ? $obj instanceof $type : $parent === null) { break; } $path = self::NameSeparator . $obj->getName() . $path; $depth++; $obj = $parent; // IComponent::getParent() if ($obj === $this) { $obj = null; // prevent cycling } } if ($obj) { $this->monitors[$type] = [$obj, $depth, \substr($path, 1), []]; } else { $this->monitors[$type] = [null, null, null, []]; // not found } } if ($throw && $this->monitors[$type][0] === null) { $message = $this->name !== null ? "Component '{$this->name}' is not attached to '{$type}'." : "Component of type '" . static::class . "' is not attached to '{$type}'."; throw new \Packetery\Nette\InvalidStateException($message); } return $this->monitors[$type][0]; } /** * Finds the closest ancestor specified by class or interface name and returns backtrace path. * A path is the concatenation of component names separated by self::NAME_SEPARATOR. */ public final function lookupPath(?string $type = null, bool $throw = \true) : ?string { $this->lookup($type, $throw); return $this->monitors[$type][2]; } /** * Starts monitoring of ancestors. */ public final function monitor(string $type, ?callable $attached = null, ?callable $detached = null) : void { if (\func_num_args() === 1) { $attached = [$this, 'attached']; $detached = [$this, 'detached']; } if (($obj = $this->lookup($type, \false)) && $attached && !\in_array([$attached, $detached], $this->monitors[$type][3], \true)) { $attached($obj); } $this->monitors[$type][3][] = [$attached, $detached]; // mark as monitored } /** * Stops monitoring of ancestors. */ public final function unmonitor(string $type) : void { unset($this->monitors[$type]); } /** * This method will be called when the component (or component's parent) * becomes attached to a monitored object. Do not call this method yourself. * @deprecated use monitor($type, $attached) */ protected function attached(IComponent $obj) : void { } /** * This method will be called before the component (or component's parent) * becomes detached from a monitored object. Do not call this method yourself. * @deprecated use monitor($type, null, $detached) */ protected function detached(IComponent $obj) : void { } /********************* interface IComponent ****************d*g**/ public final function getName() : ?string { return $this->name; } /** * Returns the parent container if any. */ public final function getParent() : ?IContainer { return $this->parent; } /** * Sets or removes the parent of this component. This method is managed by containers and should * not be called by applications * @return static * @throws \Packetery\Nette\InvalidStateException * @internal */ public function setParent(?IContainer $parent, ?string $name = null) { if ($parent === null && $this->parent === null && $name !== null) { $this->name = $name; // just rename return $this; } elseif ($parent === $this->parent && $name === null) { return $this; // nothing to do } // A component cannot be given a parent if it already has a parent. if ($this->parent !== null && $parent !== null) { throw new \Packetery\Nette\InvalidStateException("Component '{$this->name}' already has a parent."); } // remove from parent? if ($parent === null) { $this->refreshMonitors(0); $this->parent = null; } else { // add to parent $this->validateParent($parent); $this->parent = $parent; if ($name !== null) { $this->name = $name; } $tmp = []; $this->refreshMonitors(0, $tmp); } return $this; } /** * Is called by a component when it is about to be set new parent. Descendant can * override this method to disallow a parent change by throwing an \Packetery\Nette\InvalidStateException * @throws \Packetery\Nette\InvalidStateException */ protected function validateParent(IContainer $parent) : void { } /** * Refreshes monitors. * @param array|null $missing (array = attaching, null = detaching) * @param array $listeners */ private function refreshMonitors(int $depth, ?array &$missing = null, array &$listeners = []) : void { if ($this instanceof IContainer) { foreach ($this->getComponents() as $component) { if ($component instanceof self) { $component->refreshMonitors($depth + 1, $missing, $listeners); } } } if ($missing === null) { // detaching foreach ($this->monitors as $type => $rec) { if (isset($rec[1]) && $rec[1] > $depth) { if ($rec[3]) { // monitored $this->monitors[$type] = [null, null, null, $rec[3]]; foreach ($rec[3] as $pair) { $listeners[] = [$pair[1], $rec[0]]; } } else { // not monitored, just randomly cached unset($this->monitors[$type]); } } } } else { // attaching foreach ($this->monitors as $type => $rec) { if (isset($rec[0])) { // is in cache yet continue; } elseif (!$rec[3]) { // not monitored, just randomly cached unset($this->monitors[$type]); } elseif (isset($missing[$type])) { // known from previous lookup $this->monitors[$type] = [null, null, null, $rec[3]]; } else { unset($this->monitors[$type]); // forces re-lookup if ($obj = $this->lookup($type, \false)) { foreach ($rec[3] as $pair) { $listeners[] = [$pair[0], $obj]; } } else { $missing[$type] = \true; } $this->monitors[$type][3] = $rec[3]; // mark as monitored } } } if ($depth === 0) { // call listeners $prev = []; foreach ($listeners as $item) { if ($item[0] && !\in_array($item, $prev, \true)) { $item[0]($item[1]); $prev[] = $item; } } } } /********************* cloneable, serializable ****************d*g**/ /** * Object cloning. */ public function __clone() { if ($this->parent === null) { return; } elseif ($this->parent instanceof Container) { $this->parent = $this->parent->_isCloning(); if ($this->parent === null) { // not cloning $this->refreshMonitors(0); } } else { $this->parent = null; $this->refreshMonitors(0); } } /** * Prevents serialization. */ public final function __sleep() { throw new \Packetery\Nette\NotImplementedException('Object serialization is not supported by class ' . static::class); } /** * Prevents unserialization. */ public final function __wakeup() { throw new \Packetery\Nette\NotImplementedException('Object unserialization is not supported by class ' . static::class); } }