- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
511 lines
18 KiB
PHP
511 lines
18 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Core;
|
|
|
|
use App\Framework\Cache\Cache;
|
|
use App\Framework\Cache\CacheKey;
|
|
use App\Framework\Core\ValueObjects\ClassName;
|
|
use App\Framework\Discovery\FileVisitor;
|
|
use App\Framework\Discovery\ReflectionAwareVisitor;
|
|
use App\Framework\Filesystem\ValueObjects\FilePath;
|
|
use App\Framework\Reflection\WrappedReflectionClass;
|
|
|
|
/**
|
|
* Visitor zum Verarbeiten von Attributen in Klassen
|
|
*/
|
|
final class AttributeMappingVisitor implements FileVisitor, ReflectionAwareVisitor
|
|
{
|
|
private array $attributeMappers = [];
|
|
|
|
private array $mappedAttributes = [];
|
|
|
|
private array $mapperByClass = [];
|
|
|
|
private array $processingStats = [];
|
|
|
|
/**
|
|
* @param array $attributeMappers Array von AttributeMapper-Instanzen
|
|
*/
|
|
public function __construct(array $attributeMappers = [])
|
|
{
|
|
$this->attributeMappers = $attributeMappers;
|
|
$this->buildMapperIndex();
|
|
}
|
|
|
|
/**
|
|
* Erstellt einen Index der Mapper nach Attributklasse für schnelleren Zugriff
|
|
*/
|
|
private function buildMapperIndex(): void
|
|
{
|
|
$this->mapperByClass = [];
|
|
foreach ($this->attributeMappers as $mapper) {
|
|
$attributeClass = $mapper->getAttributeClass();
|
|
$this->mapperByClass[$attributeClass] = $mapper;
|
|
}
|
|
}
|
|
|
|
public function onScanStart(): void
|
|
{
|
|
$this->mappedAttributes = [];
|
|
$this->processingStats = [
|
|
'total_classes' => 0,
|
|
'classes_with_attributes' => 0,
|
|
'total_attributes' => 0,
|
|
'processed_attributes' => 0,
|
|
'skipped_reflectors' => 0,
|
|
];
|
|
}
|
|
|
|
public function onIncrementalScanStart(): void
|
|
{
|
|
// Bei inkrementellem Scan behalten wir die vorhandenen Mappings bei
|
|
$this->processingStats = [
|
|
'total_classes' => 0,
|
|
'classes_with_attributes' => 0,
|
|
'total_attributes' => 0,
|
|
'processed_attributes' => 0,
|
|
'skipped_reflectors' => 0,
|
|
];
|
|
}
|
|
|
|
public function onIncrementalScanComplete(): void
|
|
{
|
|
// Logging der Verarbeitungsstatistiken für inkrementellen Scan
|
|
// Processing stats removed for production
|
|
}
|
|
|
|
public function visitClass(ClassName $className, FilePath $filePath): void
|
|
{
|
|
// This method is kept for backward compatibility but should not be used
|
|
// when ReflectionProvider is available. The UnifiedDiscoveryService will
|
|
// prefer visitClassWithReflection() when possible.
|
|
}
|
|
|
|
public function visitClassWithReflection(ClassName $className, FilePath $filePath, WrappedReflectionClass $reflection): void
|
|
{
|
|
$this->processingStats['total_classes']++;
|
|
$hasAttributes = false;
|
|
|
|
try {
|
|
// Schnelle Vorprüfung: Hat die Klasse überhaupt relevante Attribute?
|
|
$relevantAttributes = $this->hasRelevantAttributesWrapped($reflection);
|
|
if (! $relevantAttributes) {
|
|
return;
|
|
}
|
|
|
|
$hasAttributes = true;
|
|
$this->processingStats['classes_with_attributes']++;
|
|
|
|
// Verarbeite alle Attribute auf Klassenebene
|
|
$this->processAttributesWrapped($reflection->getAttributes(), $reflection);
|
|
|
|
// Verarbeite alle Methoden
|
|
$methods = $reflection->getMethods();
|
|
foreach ($methods as $method) {
|
|
// Schnelle Vorprüfung für jede Methode
|
|
if ($this->hasRelevantAttributesWrappedMethod($method)) {
|
|
$this->processAttributesWrappedMethod($method->getAttributes(), $method);
|
|
}
|
|
}
|
|
|
|
// Verarbeite alle Eigenschaften
|
|
$properties = $reflection->getProperties();
|
|
foreach ($properties as $property) {
|
|
// Schnelle Vorprüfung für jede Eigenschaft
|
|
if ($this->hasRelevantAttributesWrappedProperty($property)) {
|
|
$this->processAttributesWrappedProperty($property->getAttributes(), $property);
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Fehler beim Verarbeiten der Klasse protokollieren
|
|
// Silent failure - skip this class and continue
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schnelle Vorprüfung, ob eine WrappedReflectionClass relevante Attribute hat
|
|
*/
|
|
private function hasRelevantAttributesWrapped(WrappedReflectionClass $reflection): bool
|
|
{
|
|
$attributes = $reflection->getAttributes();
|
|
$this->processingStats['total_attributes'] += $attributes->count();
|
|
|
|
if ($attributes->isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($attributes as $attribute) {
|
|
$attributeClass = $attribute->getName();
|
|
if (isset($this->mapperByClass[$attributeClass])) {
|
|
$mapper = $this->mapperByClass[$attributeClass];
|
|
|
|
// For wrapped reflection, we need to create native reflection for mapper compatibility
|
|
try {
|
|
$nativeReflection = new \ReflectionClass($reflection->getName());
|
|
if ($mapper->canProcess($nativeReflection)) {
|
|
return true;
|
|
} else {
|
|
$this->processingStats['skipped_reflectors']++;
|
|
}
|
|
} catch (\Throwable) {
|
|
$this->processingStats['skipped_reflectors']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Schnelle Vorprüfung für WrappedReflectionMethod
|
|
*/
|
|
private function hasRelevantAttributesWrappedMethod($method): bool
|
|
{
|
|
$attributes = $method->getAttributes();
|
|
$this->processingStats['total_attributes'] += $attributes->count();
|
|
|
|
if ($attributes->isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($attributes as $attribute) {
|
|
$attributeClass = $attribute->getName();
|
|
if (isset($this->mapperByClass[$attributeClass])) {
|
|
$mapper = $this->mapperByClass[$attributeClass];
|
|
|
|
// For wrapped reflection, we need to create native reflection for mapper compatibility
|
|
try {
|
|
$nativeClass = new \ReflectionClass($method->getDeclaringClass()->getName());
|
|
$nativeMethod = $nativeClass->getMethod($method->getName());
|
|
if ($mapper->canProcess($nativeMethod)) {
|
|
return true;
|
|
} else {
|
|
$this->processingStats['skipped_reflectors']++;
|
|
}
|
|
} catch (\Throwable) {
|
|
$this->processingStats['skipped_reflectors']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Schnelle Vorprüfung für WrappedReflectionProperty
|
|
*/
|
|
private function hasRelevantAttributesWrappedProperty($property): bool
|
|
{
|
|
$attributes = $property->getAttributes();
|
|
$this->processingStats['total_attributes'] += $attributes->count();
|
|
|
|
if ($attributes->isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($attributes as $attribute) {
|
|
$attributeClass = $attribute->getName();
|
|
if (isset($this->mapperByClass[$attributeClass])) {
|
|
$mapper = $this->mapperByClass[$attributeClass];
|
|
|
|
// For wrapped reflection, we need to create native reflection for mapper compatibility
|
|
try {
|
|
$nativeClass = new \ReflectionClass($property->getDeclaringClass()->getName());
|
|
$nativeProperty = $nativeClass->getProperty($property->getName());
|
|
if ($mapper->canProcess($nativeProperty)) {
|
|
return true;
|
|
} else {
|
|
$this->processingStats['skipped_reflectors']++;
|
|
}
|
|
} catch (\Throwable) {
|
|
$this->processingStats['skipped_reflectors']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Verarbeitet Attribute einer WrappedReflectionClass
|
|
*/
|
|
private function processAttributesWrapped($attributes, WrappedReflectionClass $reflection): void
|
|
{
|
|
foreach ($attributes as $attribute) {
|
|
$attributeClass = $attribute->getName();
|
|
|
|
if (isset($this->mapperByClass[$attributeClass])) {
|
|
$mapper = $this->mapperByClass[$attributeClass];
|
|
|
|
try {
|
|
$nativeReflection = new \ReflectionClass($reflection->getName());
|
|
if ($mapper->canProcess($nativeReflection)) {
|
|
$attributeInstance = $attribute->newInstance();
|
|
$mapper->map($attributeInstance, $nativeReflection);
|
|
$this->processingStats['processed_attributes']++;
|
|
|
|
if (! isset($this->mappedAttributes[$attributeClass])) {
|
|
$this->mappedAttributes[$attributeClass] = [];
|
|
}
|
|
|
|
$reflectorKey = [
|
|
'type' => 'class',
|
|
'name' => $reflection->getName(),
|
|
];
|
|
|
|
$this->mappedAttributes[$attributeClass][] = [
|
|
'attribute' => $attributeInstance,
|
|
'reflector' => $reflectorKey,
|
|
'metadata' => $mapper->getAttributeMetadata(),
|
|
];
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Silent failure - skip this attribute and continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verarbeitet Attribute einer WrappedReflectionMethod
|
|
*/
|
|
private function processAttributesWrappedMethod($attributes, $method): void
|
|
{
|
|
foreach ($attributes as $attribute) {
|
|
$attributeClass = $attribute->getName();
|
|
|
|
if (isset($this->mapperByClass[$attributeClass])) {
|
|
$mapper = $this->mapperByClass[$attributeClass];
|
|
|
|
try {
|
|
$nativeClass = new \ReflectionClass($method->getDeclaringClass()->getName());
|
|
$nativeMethod = $nativeClass->getMethod($method->getName());
|
|
if ($mapper->canProcess($nativeMethod)) {
|
|
$attributeInstance = $attribute->newInstance();
|
|
$mapper->map($attributeInstance, $nativeMethod);
|
|
$this->processingStats['processed_attributes']++;
|
|
|
|
if (! isset($this->mappedAttributes[$attributeClass])) {
|
|
$this->mappedAttributes[$attributeClass] = [];
|
|
}
|
|
|
|
$reflectorKey = [
|
|
'type' => 'method',
|
|
'class' => $method->getDeclaringClass()->getName(),
|
|
'name' => $method->getName(),
|
|
];
|
|
|
|
$this->mappedAttributes[$attributeClass][] = [
|
|
'attribute' => $attributeInstance,
|
|
'reflector' => $reflectorKey,
|
|
'metadata' => $mapper->getAttributeMetadata(),
|
|
];
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Silent failure - skip this attribute and continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verarbeitet Attribute einer WrappedReflectionProperty
|
|
*/
|
|
private function processAttributesWrappedProperty($attributes, $property): void
|
|
{
|
|
foreach ($attributes as $attribute) {
|
|
$attributeClass = $attribute->getName();
|
|
|
|
if (isset($this->mapperByClass[$attributeClass])) {
|
|
$mapper = $this->mapperByClass[$attributeClass];
|
|
|
|
try {
|
|
$nativeClass = new \ReflectionClass($property->getDeclaringClass()->getName());
|
|
$nativeProperty = $nativeClass->getProperty($property->getName());
|
|
if ($mapper->canProcess($nativeProperty)) {
|
|
$attributeInstance = $attribute->newInstance();
|
|
$mapper->map($attributeInstance, $nativeProperty);
|
|
$this->processingStats['processed_attributes']++;
|
|
|
|
if (! isset($this->mappedAttributes[$attributeClass])) {
|
|
$this->mappedAttributes[$attributeClass] = [];
|
|
}
|
|
|
|
$reflectorKey = [
|
|
'type' => 'property',
|
|
'class' => $property->getDeclaringClass()->getName(),
|
|
'name' => $property->getName(),
|
|
];
|
|
|
|
$this->mappedAttributes[$attributeClass][] = [
|
|
'attribute' => $attributeInstance,
|
|
'reflector' => $reflectorKey,
|
|
'metadata' => $mapper->getAttributeMetadata(),
|
|
];
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Silent failure - skip this attribute and continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schnelle Vorprüfung, ob ein Reflector relevante Attribute hat
|
|
*/
|
|
private function hasRelevantAttributes(\Reflector $reflector): bool
|
|
{
|
|
if (method_exists($reflector, 'getAttributes')) {
|
|
$attributes = $reflector->getAttributes();
|
|
$this->processingStats['total_attributes'] += count($attributes);
|
|
|
|
if (empty($attributes)) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($attributes as $attribute) {
|
|
$attributeClass = $attribute->getName();
|
|
if (isset($this->mapperByClass[$attributeClass])) {
|
|
$mapper = $this->mapperByClass[$attributeClass];
|
|
if ($mapper->canProcess($reflector)) {
|
|
return true;
|
|
} else {
|
|
$this->processingStats['skipped_reflectors']++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function processAttributes(array $attributes, \Reflector $reflector): void
|
|
{
|
|
foreach ($attributes as $attribute) {
|
|
$attributeClass = $attribute->getName();
|
|
|
|
// Direkter Zugriff auf den Mapper über den Index
|
|
if (isset($this->mapperByClass[$attributeClass])) {
|
|
$mapper = $this->mapperByClass[$attributeClass];
|
|
|
|
// Zusätzliche Prüfung, ob der Mapper diesen Reflector verarbeiten kann
|
|
if ($mapper->canProcess($reflector)) {
|
|
try {
|
|
$attributeInstance = $attribute->newInstance();
|
|
$mapper->map($attributeInstance, $reflector);
|
|
$this->processingStats['processed_attributes']++;
|
|
|
|
// Speichere für Caching
|
|
if (! isset($this->mappedAttributes[$attributeClass])) {
|
|
$this->mappedAttributes[$attributeClass] = [];
|
|
}
|
|
|
|
// Serialisierbare Repräsentation des Reflectors
|
|
$reflectorKey = $this->serializeReflector($reflector);
|
|
|
|
$this->mappedAttributes[$attributeClass][] = [
|
|
'attribute' => $attributeInstance,
|
|
'reflector' => $reflectorKey,
|
|
'metadata' => $mapper->getAttributeMetadata(),
|
|
];
|
|
} catch (\Throwable $e) {
|
|
// Silent failure - skip this attribute and continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Erstellt eine serialisierbare Repräsentation eines Reflectors
|
|
*/
|
|
private function serializeReflector(\Reflector $reflector): array
|
|
{
|
|
if ($reflector instanceof \ReflectionClass) {
|
|
return [
|
|
'type' => 'class',
|
|
'name' => $reflector->getName(),
|
|
];
|
|
} elseif ($reflector instanceof \ReflectionMethod) {
|
|
return [
|
|
'type' => 'method',
|
|
'class' => $reflector->getDeclaringClass()->getName(),
|
|
'name' => $reflector->getName(),
|
|
];
|
|
} elseif ($reflector instanceof \ReflectionProperty) {
|
|
return [
|
|
'type' => 'property',
|
|
'class' => $reflector->getDeclaringClass()->getName(),
|
|
'name' => $reflector->getName(),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'type' => 'unknown',
|
|
'name' => method_exists($reflector, 'getName') ? $reflector->getName() : 'unknown',
|
|
];
|
|
}
|
|
|
|
public function onScanComplete(): void
|
|
{
|
|
// Logging der Verarbeitungsstatistiken
|
|
// Processing completed - stats removed for production
|
|
}
|
|
|
|
public function loadFromCache(Cache $cache): void
|
|
{
|
|
$cacheItem = $cache->get(CacheKey::fromString($this->getCacheKey()));
|
|
if ($cacheItem->isHit) {
|
|
$this->mappedAttributes = $cacheItem->value;
|
|
}
|
|
}
|
|
|
|
public function getCacheKey(): CacheKey
|
|
{
|
|
return CacheKey::fromString('attribute_mappings');
|
|
}
|
|
|
|
public function getCacheableData(): mixed
|
|
{
|
|
return $this->mappedAttributes;
|
|
}
|
|
|
|
/**
|
|
* Fügt einen weiteren AttributeMapper hinzu
|
|
*/
|
|
public function addAttributeMapper(AttributeMapper $mapper): void
|
|
{
|
|
$this->attributeMappers[] = $mapper;
|
|
|
|
// Aktualisiere den Mapper-Index
|
|
$attributeClass = $mapper->getAttributeClass();
|
|
$this->mapperByClass[$attributeClass] = $mapper;
|
|
}
|
|
|
|
/**
|
|
* Gibt alle Attribute eines bestimmten Typs zurück
|
|
*/
|
|
public function getAttributesOfType(string $attributeClass): array
|
|
{
|
|
return $this->mappedAttributes[$attributeClass] ?? [];
|
|
}
|
|
|
|
/**
|
|
* Gibt alle gemappten Attribute zurück
|
|
*/
|
|
public function getAllMappedAttributes(): array
|
|
{
|
|
return $this->mappedAttributes;
|
|
}
|
|
|
|
/**
|
|
* Gibt die Verarbeitungsstatistiken zurück
|
|
*/
|
|
public function getProcessingStats(): array
|
|
{
|
|
return $this->processingStats;
|
|
}
|
|
}
|