* @template-implements ObjectSet */ final class OptionallyUnqualifiedNamedObjectSet implements ObjectSet { /** @var list */ private array $elements = []; /** @var array */ private array $elementPositionsByKey = []; /** @phpstan-param E ...$elements */ public function __construct(OptionallyNamedObject ...$elements) { foreach ($elements as $element) { $this->add($element); } } public function isEmpty(): bool { return count($this->elements) === 0; } public function get(UnqualifiedName $elementName): ?OptionallyNamedObject { $key = $this->getKey($elementName); if (isset($this->elementPositionsByKey[$key])) { return $this->elements[$this->elementPositionsByKey[$key]]; } return null; } public function add(object $element): void { $elementName = $element->getObjectName(); if ($elementName !== null) { $key = $this->getKey($elementName); if (isset($this->elementPositionsByKey[$key])) { throw ObjectAlreadyExists::new($elementName); } $this->elementPositionsByKey[$key] = count($this->elements); } $this->elements[] = $element; } public function remove(UnqualifiedName $elementName): void { $key = $this->getKey($elementName); if (! isset($this->elementPositionsByKey[$key])) { throw ObjectDoesNotExist::new($elementName); } $this->removeByKey($key); } public function modify(UnqualifiedName $elementName, callable $modification): void { $key = $this->getKey($elementName); if (! isset($this->elementPositionsByKey[$key])) { throw ObjectDoesNotExist::new($elementName); } $position = $this->elementPositionsByKey[$key]; $this->replace($key, $position, $modification($this->elements[$position])); } public function clear(): void { $this->elements = $this->elementPositionsByKey = []; } /** {@inheritDoc} */ public function toList(): array { return $this->elements; } /** {@inheritDoc} */ public function getIterator(): Traversable { yield from $this->elements; } /** * Replaces the element corresponding to the old key with the provided element. * * @phpstan-param E $element * * @throws ObjectAlreadyExists If an element with the same name as the element name already exists. */ private function replace(string $oldKey, int $position, OptionallyNamedObject $element): void { $elementName = $element->getObjectName(); if ($elementName !== null) { $newKey = $this->getKey($elementName); if ($newKey !== $oldKey) { if (isset($this->elementPositionsByKey[$newKey])) { throw ObjectAlreadyExists::new($elementName); } unset($this->elementPositionsByKey[$oldKey]); $this->elementPositionsByKey[$newKey] = $position; } } else { unset($this->elementPositionsByKey[$oldKey]); } // @phpstan-ignore assign.propertyType $this->elements[$position] = $element; } private function removeByKey(string $key): void { $position = $this->elementPositionsByKey[$key]; array_splice($this->elements, $position, 1); unset($this->elementPositionsByKey[$key]); foreach ($this->elementPositionsByKey as $elementKey => $elementPosition) { if ($elementPosition <= $position) { continue; } $this->elementPositionsByKey[$elementKey]--; } } private function getKey(UnqualifiedName $name): string { return strtolower($name->getIdentifier()->getValue()); } }