path => owner id ('core' o 'pkg:comp') */ private array $owners = []; /** @var array> path => owners en conflicto */ private array $collisions = []; public static function fromLock(Lock $lock): self { $ownership = new self(); $core = $lock->core(); foreach (($core['files'] ?? []) as $path => $hash) { $ownership->claim('core', $path); } foreach ($lock->components() as $id => $entry) { foreach (($entry['files'] ?? []) as $path => $hash) { $ownership->claim($id, $path); } } return $ownership; } private function claim(string $owner, string $path): void { if (!isset($this->owners[$path])) { $this->owners[$path] = $owner; return; } if ($this->owners[$path] !== $owner) { $this->collisions[$path][] = $this->owners[$path]; $this->collisions[$path][] = $owner; } } public function ownerOf(string $path): ?string { return $this->owners[$path] ?? null; } /** @return list */ public function pathsOf(string $owner): array { return array_keys(array_filter($this->owners, fn ($o) => $o === $owner)); } /** * @return array> */ public function collisions(): array { $result = []; foreach ($this->collisions as $path => $owners) { $result[$path] = array_values(array_unique($owners)); } return $result; } public function assertNoCollisions(): void { $collisions = $this->collisions(); if ($collisions === []) { return; } $parts = []; foreach ($collisions as $path => $owners) { $parts[] = $path . ' (' . implode(', ', $owners) . ')'; } throw new ToolboxException('Colisión de propiedad: ' . implode('; ', $parts)); } }