*/ private string $name; /** * Associated package contexts. * * @var SplObjectStorage */ private SplObjectStorage $packageContexts; /** * Nodes that this node requires. * * @var SplObjectStorage */ private SplObjectStorage $requires; /** * Nodes that this node extends. * * @var SplObjectStorage */ private SplObjectStorage $extends; /** * Nodes that require this node. * * @var SplObjectStorage */ private SplObjectStorage $requiredBy; /** * Nodes that extend this node. * * @var SplObjectStorage */ private SplObjectStorage $extendedBy; /** * Graph instance. */ private GraphInternal $graph; /** * Whether this node was visited when looking for circular requirements. */ private bool $visitedCircular = false; /** * Whether this node can be required, or only extended. */ private bool $canBeRequired = true; /** * Constructor. * * @param GraphInternal $graph Graph instance */ public function __construct(GraphInternal $graph) { $this->graph = $graph; $this->packageContexts = new SplObjectStorage; $this->requiredBy = new SplObjectStorage; $this->extendedBy = new SplObjectStorage; $this->requires = new SplObjectStorage; $this->extends = new SplObjectStorage; } /** * Initialization function. * * @param string $plugin Plugin name * @param array $config Plugin configuration * @param PackageContext $ctx Context * * @psalm-param class-string $plugin Plugin name * * @return self */ public function init(string $plugin, array $config, PackageContext $ctx): self { $this->name = $plugin; $this->plugin = new Plugin($plugin, $config); $this->packageContexts->attach($ctx); $this->canBeRequired = PluginCache::canBeRequired($plugin); foreach (PluginCache::runAfter($plugin) as $class => $config) { foreach ($this->graph->addPlugin($class, $config, $ctx) as $node) { $this->require($node); } } foreach (PluginCache::runBefore($plugin) as $class => $config) { foreach ($this->graph->addPlugin($class, $config, $ctx) as $node) { $node->require($this); } } foreach (PluginCache::runWithAfter($plugin) as $class => $config) { foreach ($this->graph->addPlugin($class, $config, $ctx) as $node) { $this->extend($node); } } foreach (PluginCache::runWithBefore($plugin) as $class => $config) { foreach ($this->graph->addPlugin($class, $config, $ctx) as $node) { $node->extend($this); } } return $this; } /** * Make node require another node. * * @param self $node Node * * @return void */ private function require(self $node): void { if (!$node->canBeRequired) { $this->extend($node); return; } if ($this->extends->contains($node) || $node->extendedBy->contains($this)) { $this->extends->detach($node); $node->extendedBy->detach($this); } $this->requires->attach($node); $node->requiredBy->attach($this); $this->graph->linkNode($this); } /** * Make node extend another node. * * @param self $node Node * * @return void */ private function extend(self $node): void { if ($this->requires->contains($node) || $node->requiredBy->contains($this)) { return; } $this->extends->attach($node); $node->extendedBy->attach($this); $this->graph->linkNode($this); } /** * Add package context. * * @param PackageContext $ctx Context * * @return self */ public function addPackageContext(PackageContext $ctx): self { if ($this->packageContexts->contains($ctx)) { return $this; } $this->packageContexts->attach($ctx); foreach ($this->requires as $node) { $node->addPackageContext($ctx); } foreach ($this->extends as $node) { $node->addPackageContext($ctx); } return $this; } /** * Merge node with another node. * * @param self $other Other node * * @return Node */ public function merge(self $other): Node { $this->plugin->merge($other->plugin); foreach ($other->requiredBy as $node) { $node->require($this); $node->requires->detach($other); } foreach ($other->extendedBy as $node) { $node->extend($this); $node->extends->detach($other); } return $this; } /** * Flatten tree. * * @return SplQueue> */ public function flatten(): SplQueue { /** @var SplQueue */ $initQueue = new SplQueue; /** @var SplQueue> */ $queue = new SplQueue; $queue->enqueue($initQueue); $this->flattenInternal($queue); return $queue; } /** * Look for circular references. * * @return self */ public function circular(): self { if ($this->visitedCircular) { $plugins = [$this->name]; foreach (\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, DEBUG_BACKTRACE_PROVIDE_OBJECT) as $frame) { $plugins []= $frame['object']->name; if ($frame['object'] === $this) { break; } } throw new CircularException($plugins); } $this->visitedCircular = true; foreach ($this->requiredBy as $node) { $node->circular(); } foreach ($this->extendedBy as $node) { $node->circular(); } $this->visitedCircular = false; return $this; } /** * Internal flattening. * * @param SplQueue> $splQueue Queue * * @return void */ private function flattenInternal(SplQueue $splQueue): void { $queue = $splQueue->top(); $this->plugin->enqueue($queue); /** @var SplQueue */ $extendedBy = new SplQueue; $prevNode = null; foreach ($this->extendedBy as $node) { if (\count($node->requires) + \count($node->extends) === 1) { if ($prevNode instanceof self) { $node->merge($prevNode); } $prevNode = $node; } else { $extendedBy->enqueue($node); } } if ($prevNode) { $extendedBy->enqueue($prevNode); } /** @var SplQueue */ $requiredBy = new SplQueue; $prevNode = null; foreach ($this->requiredBy as $node) { if (\count($node->requires) + \count($node->extends) === 1) { if ($prevNode instanceof self) { $node->merge($prevNode); } $prevNode = $node; } else { $requiredBy->enqueue($node); } } if ($prevNode) { $requiredBy->enqueue($prevNode); } foreach ($extendedBy as $node) { $node->extends->detach($this); if (\count($node->extends) + \count($node->requires) === 0) { $node->flattenInternal($splQueue); } } foreach ($requiredBy as $node) { $node->requires->detach($this); if (\count($node->extends) + \count($node->requires) === 0) { $splQueue->enqueue(new SplQueue); $node->flattenInternal($splQueue); } } } }