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:
213
src/Framework/Discovery/Results/AttributeRegistry.php
Normal file
213
src/Framework/Discovery/Results/AttributeRegistry.php
Normal 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;
|
||||
}
|
||||
}
|
||||
195
src/Framework/Discovery/Results/DiscoveryRegistry.php
Normal file
195
src/Framework/Discovery/Results/DiscoveryRegistry.php
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
207
src/Framework/Discovery/Results/InterfaceRegistry.php
Normal file
207
src/Framework/Discovery/Results/InterfaceRegistry.php
Normal 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;
|
||||
}
|
||||
}
|
||||
200
src/Framework/Discovery/Results/RouteRegistry.php
Normal file
200
src/Framework/Discovery/Results/RouteRegistry.php
Normal 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;
|
||||
}
|
||||
}
|
||||
166
src/Framework/Discovery/Results/TemplateRegistry.php
Normal file
166
src/Framework/Discovery/Results/TemplateRegistry.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user