Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,213 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Results;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use Countable;
/**
* Memory-optimized registry for attribute discoveries using Value Objects
* Pure Value Object implementation without legacy array support
*/
final class AttributeRegistry implements Countable
{
/** @var array<string, DiscoveredAttribute[]> */
private array $mappings = [];
private bool $isOptimized = false;
public function __construct()
{
}
/**
* Convert to array for cache serialization
*/
public function toArray(): array
{
$serializedMappings = [];
foreach ($this->mappings as $attributeClass => $mappings) {
$serializedMappings[$attributeClass] = [];
foreach ($mappings as $mapping) {
$serializedMappings[$attributeClass][] = $mapping->toArray();
}
}
return [
'mappings' => $serializedMappings,
];
}
/**
* Create AttributeRegistry from array data (for cache deserialization)
* Always loads as non-optimized to ensure data integrity
*/
public static function fromArray(array $data): self
{
$registry = new self();
$mappings = [];
foreach (($data['mappings'] ?? []) as $attributeClass => $mappingArrays) {
$mappings[$attributeClass] = [];
foreach ($mappingArrays as $mappingArray) {
try {
$mappings[$attributeClass][] = DiscoveredAttribute::fromArray($mappingArray);
} catch (\Throwable $e) {
// Skip corrupted cache entries - they'll be recreated on next discovery
continue;
}
}
}
$registry->mappings = $mappings;
$registry->isOptimized = false; // Always force re-optimization for cache data integrity
return $registry;
}
/**
* Add a value object mapping
*/
public function add(string $attributeClass, DiscoveredAttribute $attribute): void
{
if (! isset($this->mappings[$attributeClass])) {
$this->mappings[$attributeClass] = [];
}
$this->mappings[$attributeClass][] = $attribute;
$this->isOptimized = false;
}
/**
* Get discovered attributes as Value Objects
* @return DiscoveredAttribute[]
*/
public function get(string $attributeClass): array
{
return $this->mappings[$attributeClass] ?? [];
}
public function has(string $attributeClass): bool
{
return ! empty($this->mappings[$attributeClass]);
}
public function getCount(string $attributeClass): int
{
return count($this->mappings[$attributeClass] ?? []);
}
/**
* Count total mappings across all attribute types (Countable interface)
*/
public function count(): int
{
return array_sum(array_map('count', $this->mappings));
}
public function getAllTypes(): array
{
return array_keys($this->mappings);
}
public function optimize(): void
{
if ($this->isOptimized) {
return;
}
// Deduplicate using Value Object unique IDs
foreach ($this->mappings as $attributeClass => &$mappings) {
$mappings = $this->deduplicateAttributes($mappings);
}
unset($mappings);
$this->mappings = array_filter($this->mappings);
$this->isOptimized = true;
}
public function clear(): void
{
$this->mappings = [];
$this->isOptimized = false;
}
public function clearCache(): void
{
// No caches to clear in this implementation
}
public function getMemoryStats(): array
{
$totalMappings = array_sum(array_map('count', $this->mappings));
$totalMemory = $this->getTotalMemoryFootprint();
return [
'types' => count($this->mappings),
'instances' => $totalMappings,
'estimated_bytes' => $totalMemory->toBytes(),
'memory_footprint' => $totalMemory->toHumanReadable(),
'is_optimized' => $this->isOptimized,
'value_objects' => true,
];
}
/**
* Deduplicate attributes using Value Object unique IDs
* @param DiscoveredAttribute[] $attributes
* @return DiscoveredAttribute[]
*/
private function deduplicateAttributes(array $attributes): array
{
$seen = [];
$deduplicated = [];
foreach ($attributes as $attribute) {
$uniqueId = $attribute->getUniqueId();
if (! isset($seen[$uniqueId])) {
$seen[$uniqueId] = true;
$deduplicated[] = $attribute;
}
}
return $deduplicated;
}
/**
* Find attributes by class name
* @return DiscoveredAttribute[]
*/
public function findByClass(string $className): array
{
$results = [];
foreach ($this->mappings as $mappings) {
foreach ($mappings as $attribute) {
if ($attribute->className->getFullyQualified() === $className) {
$results[] = $attribute;
}
}
}
return $results;
}
/**
* Get total memory footprint
*/
public function getTotalMemoryFootprint(): Byte
{
$total = Byte::zero();
foreach ($this->mappings as $attributes) {
foreach ($attributes as $attribute) {
$total = $total->add($attribute->getMemoryFootprint());
}
}
return $total;
}
}

View File

@@ -0,0 +1,195 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Results;
use Countable;
/**
* Central registry that coordinates the smaller specialized registries
* This replaces the monolithic DiscoveryResults with a composition of lightweight registries
*
* MEMORY OPTIMIZATION: Implements __serialize/__unserialize to prevent cache memory explosion
*/
final readonly class DiscoveryRegistry implements Countable
{
public function __construct(
public AttributeRegistry $attributes = new AttributeRegistry(),
public InterfaceRegistry $interfaces = new InterfaceRegistry(),
public TemplateRegistry $templates = new TemplateRegistry(),
) {
}
// === Memory Management ===
/**
* Optimize all registries for memory efficiency
*/
public function optimize(): void
{
$this->attributes->optimize();
$this->interfaces->optimize();
$this->templates->optimize();
}
/**
* Clear all caches to free memory
*/
public function clearCaches(): void
{
$this->attributes->clearCache();
$this->interfaces->clearCache();
$this->templates->clearCache();
}
/**
* Get comprehensive memory statistics
*/
public function getMemoryStats(): array
{
$attributeStats = $this->attributes->getMemoryStats();
$interfaceStats = $this->interfaces->getMemoryStats();
$templateStats = $this->templates->getMemoryStats();
return [
'total_estimated_bytes' => $attributeStats['estimated_bytes'] + $interfaceStats['estimated_bytes'] + $templateStats['estimated_bytes'],
'attributes' => $attributeStats,
'interfaces' => $interfaceStats,
'templates' => $templateStats,
];
}
public function isEmpty(): bool
{
return count($this) === 0;
}
public function count(): int
{
return count($this->attributes) +
count($this->interfaces) +
count($this->templates);
}
/**
* Create a lightweight version with only essential data
*/
public function createLightweight(): self
{
// Keep only commonly accessed attributes
$essentialAttributes = new AttributeRegistry();
$importantTypes = [
'App\\Framework\\Attributes\\Route',
'App\\Framework\\DI\\Initializer',
'App\\Framework\\Core\\Events\\OnEvent',
'App\\Framework\\Http\\MiddlewarePriorityAttribute',
];
foreach ($importantTypes as $type) {
if ($this->attributes->has($type)) {
foreach ($this->attributes->get($type) as $data) {
$essentialAttributes->add($type, $data);
}
}
}
return new self(
attributes: $essentialAttributes,
interfaces: $this->interfaces,
templates: new TemplateRegistry() // Skip templates in lightweight version
);
}
// === Factory Methods ===
public static function empty(): self
{
return new self();
}
/**
* Convert to array for cache serialization
*/
public function toArray(): array
{
return [
'attributes' => $this->attributes->toArray(),
'interfaces' => $this->interfaces->toArray(),
'templates' => $this->templates->toArray(),
];
}
public function __serialize(): array
{
return $this->toArray();
}
/**
* Custom deserialization using fromArray() method
*/
public function __unserialize(array $data): void
{
$restored = self::fromArray($data);
$this->attributes = $restored->attributes;
$this->interfaces = $restored->interfaces;
$this->templates = $restored->templates;
}
/**
* Create DiscoveryRegistry from array data (for cache deserialization)
* Uses direct constructor instantiation with data injection
*/
public static function fromArray(array $data): self
{
// Verwende die fromArray Factory-Methoden der Registry-Klassen
// Diese laden automatisch als nicht-optimiert für Datenintegrität
return new self(
attributes: isset($data['attributes'])
? AttributeRegistry::fromArray($data['attributes'])
: new AttributeRegistry(),
interfaces: isset($data['interfaces'])
? InterfaceRegistry::fromArray($data['interfaces'])
: new InterfaceRegistry(),
templates: isset($data['templates'])
? TemplateRegistry::fromArray($data['templates'])
: new TemplateRegistry()
);
}
/**
* Merge multiple registries efficiently
*/
public function merge(self $other): self
{
$mergedAttributes = new AttributeRegistry();
// Copy all attributes from both registries
foreach ($this->attributes->getAllTypes() as $type) {
foreach ($this->attributes->get($type) as $mapping) {
$mergedAttributes->add($type, $mapping);
}
}
foreach ($other->attributes->getAllTypes() as $type) {
foreach ($other->attributes->get($type) as $mapping) {
$mergedAttributes->add($type, $mapping);
}
}
$merged = new self(
attributes: $mergedAttributes,
interfaces: $this->interfaces->merge($other->interfaces),
templates: $this->templates->merge($other->templates)
);
$merged->optimize();
return $merged;
}
public function getFileCount(): int
{
return $this->count();
}
}

View File

@@ -1,255 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Results;
/**
* Einheitliche Klasse für alle Discovery-Ergebnisse
*/
final class DiscoveryResults
{
private array $attributeResults = [];
private array $interfaceImplementations = [];
private array $routes = [];
private array $templates = [];
private array $additionalResults = [];
public function __construct(
array $attributeResults = [],
array $interfaceImplementations = [],
array $routes = [],
array $templates = [],
array $additionalResults = []
) {
$this->attributeResults = $attributeResults;
$this->interfaceImplementations = $interfaceImplementations;
$this->routes = $routes;
$this->templates = $templates;
$this->additionalResults = $additionalResults;
}
// === Attribute Results ===
public function setAttributeResults(array $results): void
{
$this->attributeResults = $results;
}
public function addAttributeResult(string $attributeClass, array $data): void
{
$this->attributeResults[$attributeClass][] = $data;
}
public function getAttributeResults(string $attributeClass): array
{
return $this->attributeResults[$attributeClass] ?? [];
}
public function getAllAttributeResults(): array
{
return $this->attributeResults;
}
public function hasAttributeResults(string $attributeClass): bool
{
return !empty($this->attributeResults[$attributeClass]);
}
// === Interface Implementations ===
public function setInterfaceImplementations(array $implementations): void
{
$this->interfaceImplementations = $implementations;
}
public function addInterfaceImplementation(string $interface, string $className): void
{
if (!isset($this->interfaceImplementations[$interface])) {
$this->interfaceImplementations[$interface] = [];
}
if (!in_array($className, $this->interfaceImplementations[$interface])) {
$this->interfaceImplementations[$interface][] = $className;
}
}
public function getInterfaceImplementations(string $interface): array
{
return $this->interfaceImplementations[$interface] ?? [];
}
public function getAllInterfaceImplementations(): array
{
return $this->interfaceImplementations;
}
// === Routes ===
public function setRoutes(array $routes): void
{
$this->routes = $routes;
}
public function getRoutes(): array
{
return $this->routes;
}
// === Templates ===
public function setTemplates(array $templates): void
{
$this->templates = $templates;
}
public function getTemplates(): array
{
return $this->templates;
}
// === Additional Results ===
public function setAdditionalResult(string $key, mixed $value): void
{
$this->additionalResults[$key] = $value;
}
public function getAdditionalResult(string $key, mixed $default = null): mixed
{
return $this->additionalResults[$key] ?? $default;
}
public function has(string $key): bool
{
return isset($this->additionalResults[$key]) ||
isset($this->attributeResults[$key]) ||
isset($this->interfaceImplementations[$key]) ||
isset($this->routes[$key]) ||
isset($this->templates[$key]);
}
// === Compatibility with old ProcessedResults ===
/**
* Kompatibilitätsmethode für bestehenden Code
*/
public function get(string $key): array
{
// Direkte Suche nach dem Key
if (isset($this->attributeResults[$key])) {
return $this->attributeResults[$key];
}
// Versuche auch mit führendem Backslash
$keyWithBackslash = '\\' . ltrim($key, '\\');
if (isset($this->attributeResults[$keyWithBackslash])) {
return $this->attributeResults[$keyWithBackslash];
}
// Versuche ohne führenden Backslash
$keyWithoutBackslash = ltrim($key, '\\');
if (isset($this->attributeResults[$keyWithoutBackslash])) {
return $this->attributeResults[$keyWithoutBackslash];
}
// Für Interfaces (z.B. Initializer::class)
if (isset($this->interfaceImplementations[$key])) {
return array_map(function($className) {
return ['class' => $className];
}, $this->interfaceImplementations[$key]);
}
if (isset($this->interfaceImplementations[$keyWithBackslash])) {
return array_map(function($className) {
return ['class' => $className];
}, $this->interfaceImplementations[$keyWithBackslash]);
}
if (isset($this->interfaceImplementations[$keyWithoutBackslash])) {
return array_map(function($className) {
return ['class' => $className];
}, $this->interfaceImplementations[$keyWithoutBackslash]);
}
// Für spezielle Keys
switch ($key) {
case 'routes':
return $this->routes;
case 'templates':
return $this->templates;
}
return $this->additionalResults[$key] ?? [];
}
// === Serialization ===
public function toArray(): array
{
return [
'attributes' => $this->attributeResults,
'interfaces' => $this->interfaceImplementations,
'routes' => $this->routes,
'templates' => $this->templates,
'additional' => $this->additionalResults,
];
}
public static function fromArray(array $data): self
{
return new self(
$data['attributes'] ?? [],
$data['interfaces'] ?? [],
$data['routes'] ?? [],
$data['templates'] ?? [],
$data['additional'] ?? []
);
}
/* public function __serialize(): array
{
return $this->toArray();
}
public function __unserialize(array $data): void
{
$this->attributeResults = $data['attributes'] ?? [];
$this->interfaceImplementations = $data['interfaces'] ?? [];
$this->routes = $data['routes'] ?? [];
$this->templates = $data['templates'] ?? [];
$this->additionalResults = $data['additional'] ?? [];
}*/
// === Utility Methods ===
public function isEmpty(): bool
{
return empty($this->attributeResults)
&& empty($this->interfaceImplementations)
&& empty($this->routes)
&& empty($this->templates)
&& empty($this->additionalResults);
}
public function merge(DiscoveryResults $other): self
{
return new self(
array_merge_recursive($this->attributeResults, $other->attributeResults),
array_merge_recursive($this->interfaceImplementations, $other->interfaceImplementations),
array_merge($this->routes, $other->routes),
array_merge($this->templates, $other->templates),
array_merge($this->additionalResults, $other->additionalResults)
);
}
/**
* Sortiert Interface-Implementierungen für konsistente Ergebnisse
*/
public function sortInterfaceImplementations(): void
{
foreach ($this->interfaceImplementations as &$implementations) {
sort($implementations);
$implementations = array_unique($implementations);
}
}
}

View File

@@ -0,0 +1,207 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Results;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Discovery\ValueObjects\InterfaceMapping;
use Countable;
/**
* Memory-optimized registry for interface implementations using Value Objects
* Pure Value Object implementation without legacy array support
*/
final class InterfaceRegistry implements Countable
{
/** @var InterfaceMapping[] */
private array $mappings = [];
/** @var array<string, ClassName[]> */
private array $implementationsByInterface = [];
private bool $isOptimized = false;
public function __construct()
{
}
/**
* Convert to array for cache serialization
*/
public function toArray(): array
{
return [
'mappings' => $this->mappings,
];
}
/**
* Create InterfaceRegistry from array data (for cache deserialization)
* Always loads as non-optimized to ensure data integrity
*/
public static function fromArray(array $data): self
{
$registry = new self();
$registry->mappings = $data['mappings'] ?? [];
$registry->implementationsByInterface = []; // Don't restore cache, will be rebuilt
$registry->isOptimized = false; // Always force re-optimization for cache data integrity
return $registry;
}
/**
* Get implementations as ClassName Value Objects
* @return ClassName[]
*/
public function get(string $interface): array
{
if (! isset($this->implementationsByInterface[$interface])) {
$this->implementationsByInterface[$interface] = [];
foreach ($this->mappings as $mapping) {
if ($mapping->interface->getFullyQualified() === $interface) {
$this->implementationsByInterface[$interface][] = $mapping->implementation;
}
}
}
return $this->implementationsByInterface[$interface];
}
/**
* Get all mappings as Value Objects
* @return InterfaceMapping[]
*/
public function getAllMappings(): array
{
return $this->mappings;
}
public function has(string $interface): bool
{
return ! empty($this->get($interface));
}
public function add(InterfaceMapping $mapping): void
{
$this->mappings[] = $mapping;
$this->implementationsByInterface = [];
$this->isOptimized = false;
}
/**
* Count total interface mappings (Countable interface)
*/
public function count(): int
{
return count($this->mappings);
}
public function getAllInterfaces(): array
{
$interfaces = [];
foreach ($this->mappings as $mapping) {
$interfaces[] = $mapping->interface->getFullyQualified();
}
return array_unique($interfaces);
}
public function optimize(): void
{
if ($this->isOptimized) {
return;
}
// Deduplicate mappings using Value Object unique IDs
$seen = [];
$deduplicated = [];
foreach ($this->mappings as $mapping) {
$uniqueId = $mapping->getUniqueId();
if (! isset($seen[$uniqueId])) {
$seen[$uniqueId] = true;
$deduplicated[] = $mapping;
}
}
$this->mappings = $deduplicated;
$this->implementationsByInterface = [];
$this->isOptimized = true;
}
public function clear(): void
{
$this->mappings = [];
$this->implementationsByInterface = [];
$this->isOptimized = false;
}
public function clearCache(): void
{
$this->implementationsByInterface = [];
}
public function getMemoryStats(): array
{
$totalMemory = Byte::zero();
foreach ($this->mappings as $mapping) {
$totalMemory = $totalMemory->add($mapping->getMemoryFootprint());
}
return [
'interfaces' => count($this->getAllInterfaces()),
'implementations' => count($this->mappings),
'estimated_bytes' => $totalMemory->toBytes(),
'memory_footprint' => $totalMemory->toHumanReadable(),
'cached_interfaces' => count($this->implementationsByInterface),
'is_optimized' => $this->isOptimized,
'value_objects' => true,
];
}
public function merge(self $other): self
{
$merged = new self();
foreach ($this->mappings as $mapping) {
$merged->add($mapping);
}
foreach ($other->mappings as $mapping) {
$merged->add($mapping);
}
$merged->optimize();
return $merged;
}
/**
* Find all mappings for a specific interface
* @return InterfaceMapping[]
*/
public function findMappingsForInterface(string $interface): array
{
return array_filter(
$this->mappings,
fn (InterfaceMapping $mapping) => $mapping->interface->getFullyQualified() === $interface
);
}
/**
* Get total memory footprint
*/
public function getTotalMemoryFootprint(): Byte
{
$total = Byte::zero();
foreach ($this->mappings as $mapping) {
$total = $total->add($mapping->getMemoryFootprint());
}
return $total;
}
}

View File

@@ -0,0 +1,200 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Results;
use App\Framework\Discovery\ValueObjects\RouteMapping;
use App\Framework\Http\Method;
use Countable;
/**
* Memory-optimized registry for route discoveries using Value Objects
* Pure Value Object implementation without legacy array support
*/
final class RouteRegistry implements Countable
{
/** @var RouteMapping[] */
private array $routes = [];
/** @var array<string, RouteMapping[]> */
private array $routesByMethod = [];
private bool $isOptimized = false;
public function __construct()
{
}
/**
* Convert to array for cache serialization
*/
public function toArray(): array
{
return [
'routes' => $this->routes,
];
}
/**
* Create RouteRegistry from array data (for cache deserialization)
* Always loads as non-optimized to ensure data integrity
*/
public static function fromArray(array $data): self
{
$registry = new self();
$registry->routes = $data['routes'] ?? [];
$registry->routesByMethod = []; // Don't restore cache, will be rebuilt
$registry->isOptimized = false; // Always force re-optimization for cache data integrity
return $registry;
}
/**
* Get all routes as Value Objects
* @return RouteMapping[]
*/
public function getAll(): array
{
return $this->routes;
}
/**
* Get routes by HTTP method
* @return RouteMapping[]
*/
public function getByMethod(Method $method): array
{
$methodValue = $method->value;
if (! isset($this->routesByMethod[$methodValue])) {
$this->routesByMethod[$methodValue] = array_filter(
$this->routes,
fn (RouteMapping $route) => $route->matchesMethod($method)
);
}
return $this->routesByMethod[$methodValue];
}
public function add(RouteMapping $route): void
{
$this->routes[] = $route;
$this->routesByMethod = [];
$this->isOptimized = false;
}
/**
* Count total routes (Countable interface)
*/
public function count(): int
{
return count($this->routes);
}
public function optimize(): void
{
if ($this->isOptimized) {
return;
}
// Deduplicate routes using Value Object unique IDs
$seen = [];
$deduplicated = [];
foreach ($this->routes as $route) {
$uniqueId = $route->getUniqueId();
if (! isset($seen[$uniqueId])) {
$seen[$uniqueId] = true;
$deduplicated[] = $route;
}
}
$this->routes = $deduplicated;
$this->routesByMethod = [];
$this->isOptimized = true;
}
public function clear(): void
{
$this->routes = [];
$this->routesByMethod = [];
$this->isOptimized = false;
}
public function clearCache(): void
{
$this->routesByMethod = [];
}
public function getMemoryStats(): array
{
$totalMemory = 0;
foreach ($this->routes as $route) {
$totalMemory += $route->getMemoryFootprint()->toBytes();
}
return [
'routes' => count($this->routes),
'estimated_bytes' => $totalMemory,
'methods_cached' => count($this->routesByMethod),
'is_optimized' => $this->isOptimized,
'value_objects' => true,
];
}
public function merge(self $other): self
{
$merged = new self();
foreach ($this->routes as $route) {
$merged->add($route);
}
foreach ($other->routes as $route) {
$merged->add($route);
}
$merged->optimize();
return $merged;
}
/**
* Find routes by path pattern
* @return RouteMapping[]
*/
public function findByPattern(string $pattern): array
{
return array_filter(
$this->routes,
fn (RouteMapping $route) => fnmatch($pattern, $route->path)
);
}
/**
* Find routes by class
* @return RouteMapping[]
*/
public function findByClass(string $className): array
{
return array_filter(
$this->routes,
fn (RouteMapping $route) => $route->class->getFullyQualified() === $className
);
}
/**
* Get total memory footprint
*/
public function getTotalMemoryFootprint(): int
{
$total = 0;
foreach ($this->routes as $route) {
$total += $route->getMemoryFootprint()->toBytes();
}
return $total;
}
}

View File

@@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Results;
use App\Framework\Discovery\ValueObjects\TemplateMapping;
use Countable;
/**
* Memory-optimized registry for template discoveries using Value Objects
* Pure Value Object implementation without legacy array support
*/
final class TemplateRegistry implements Countable
{
/** @var TemplateMapping[] */
private array $templates = [];
/** @var array<string, TemplateMapping> */
private array $templatesByName = [];
private bool $isOptimized = false;
public function __construct()
{
}
/**
* Convert to array for cache serialization
*/
public function toArray(): array
{
$serializedTemplates = [];
foreach ($this->templates as $template) {
$serializedTemplates[] = $template->toArray();
}
return [
'templates' => $serializedTemplates,
];
}
/**
* Create TemplateRegistry from array data (for cache deserialization)
* Always loads as non-optimized to ensure data integrity
*/
public static function fromArray(array $data): self
{
$registry = new self();
$templates = [];
foreach (($data['templates'] ?? []) as $templateArray) {
try {
$templates[] = TemplateMapping::fromArray($templateArray);
} catch (\InvalidArgumentException $e) {
// Skip invalid template mappings from corrupted cache data
continue;
}
}
$registry->templates = $templates;
$registry->templatesByName = []; // Don't restore cache, will be rebuilt
$registry->isOptimized = false; // Always force re-optimization for cache data integrity
return $registry;
}
/**
* Get all templates as Value Objects
* @return TemplateMapping[]
*/
public function getAll(): array
{
return $this->templates;
}
public function get(string $name): ?TemplateMapping
{
if (! isset($this->templatesByName[$name])) {
foreach ($this->templates as $template) {
if ($template->name === $name) {
$this->templatesByName[$name] = $template;
break;
}
}
}
return $this->templatesByName[$name] ?? null;
}
public function has(string $name): bool
{
return $this->get($name) !== null;
}
public function add(TemplateMapping $template): void
{
$this->templates[] = $template;
$this->templatesByName = [];
$this->isOptimized = false;
}
/**
* Count total templates (Countable interface)
*/
public function count(): int
{
return count($this->templates);
}
public function optimize(): void
{
if ($this->isOptimized) {
return;
}
// Deduplicate templates using Value Object unique IDs
$seen = [];
$deduplicated = [];
foreach ($this->templates as $template) {
$uniqueId = $template->getUniqueId();
if (! isset($seen[$uniqueId])) {
$seen[$uniqueId] = true;
$deduplicated[] = $template;
}
}
$this->templates = $deduplicated;
$this->templatesByName = [];
$this->isOptimized = true;
}
public function clearCache(): void
{
$this->templatesByName = [];
}
public function getMemoryStats(): array
{
return [
'templates' => count($this->templates),
'estimated_bytes' => count($this->templates) * 150,
'is_optimized' => $this->isOptimized,
];
}
public function merge(self $other): self
{
$merged = new self();
foreach ($this->templates as $template) {
$merged->add($template);
}
foreach ($other->templates as $template) {
$merged->add($template);
}
$merged->optimize();
return $merged;
}
}