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,119 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events;
/**
* External collector for domain events using WeakMap
* Keeps entities clean as simple value objects with automatic memory management
*/
final class DomainEventCollector
{
/** @var \WeakMap<object, object[]> Events mapped to entities with automatic GC */
private \WeakMap $eventsByEntity;
public function __construct()
{
$this->eventsByEntity = new \WeakMap();
}
/**
* Record a domain event for an entity
*/
public function recordEvent(object $entity, object $event): void
{
if (! isset($this->eventsByEntity[$entity])) {
$this->eventsByEntity[$entity] = [];
}
$this->eventsByEntity[$entity][] = $event;
}
/**
* Get all recorded domain events for an entity
*
* @return object[]
*/
public function getEventsForEntity(object $entity): array
{
return $this->eventsByEntity[$entity] ?? [];
}
/**
* Clear all recorded domain events for an entity
* (Optional - WeakMap handles cleanup automatically)
*/
public function clearEventsForEntity(object $entity): void
{
unset($this->eventsByEntity[$entity]);
}
/**
* Get all recorded domain events across all entities
*
* @return object[]
*/
public function getAllEvents(): array
{
$allEvents = [];
foreach ($this->eventsByEntity as $events) {
$allEvents = array_merge($allEvents, $events);
}
return $allEvents;
}
/**
* Check if there are any domain events for an entity
*/
public function hasEventsForEntity(object $entity): bool
{
return isset($this->eventsByEntity[$entity]) && ! empty($this->eventsByEntity[$entity]);
}
/**
* Get domain events of a specific type for an entity
*
* @template T
* @param class-string<T> $eventClass
* @return T[]
*/
public function getEventsOfTypeForEntity(object $entity, string $eventClass): array
{
$events = $this->getEventsForEntity($entity);
return array_filter(
$events,
fn (object $event) => $event instanceof $eventClass
);
}
/**
* Get count of events for an entity
*/
public function getEventCountForEntity(object $entity): int
{
return count($this->getEventsForEntity($entity));
}
/**
* Get total count of all events across all entities
*/
public function getTotalEventCount(): int
{
$total = 0;
foreach ($this->eventsByEntity as $events) {
$total += count($events);
}
return $total;
}
/**
* Get count of tracked entities
*/
public function getTrackedEntityCount(): int
{
return count($this->eventsByEntity);
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Event that is fired when an entity is created
*/
final readonly class EntityCreatedEvent
{
public Timestamp $timestamp;
public function __construct(
public object $entity,
public string $entityClass,
public mixed $entityId,
public array $data = [],
?Timestamp $timestamp = null
) {
$this->timestamp = $timestamp ?? Timestamp::now();
}
/**
* Check if this event is for a specific entity class
*/
public function isEntityOfType(string $expectedClass): bool
{
return $this->entityClass === $expectedClass || $this->entity instanceof $expectedClass;
}
/**
* Get event metadata
*/
public function getEventData(): array
{
return [
'event_type' => 'entity_created',
'entity_class' => $this->entityClass,
'entity_id' => $this->entityId,
'timestamp' => $this->timestamp->toFloat(),
'data' => $this->data,
];
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Event that is fired when an entity is deleted
*/
final readonly class EntityDeletedEvent
{
public Timestamp $timestamp;
public function __construct(
public object $entity,
public string $entityClass,
public mixed $entityId,
public array $deletedData = [],
?Timestamp $timestamp = null
) {
$this->timestamp = $timestamp ?? Timestamp::now();
}
/**
* Check if this event is for a specific entity class
*/
public function isEntityOfType(string $expectedClass): bool
{
return $this->entityClass === $expectedClass || $this->entity instanceof $expectedClass;
}
/**
* Get event metadata
*/
public function getEventData(): array
{
return [
'event_type' => 'entity_deleted',
'entity_class' => $this->entityClass,
'entity_id' => $this->entityId,
'timestamp' => $this->timestamp->toFloat(),
'deleted_data' => $this->deletedData,
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Event that is fired when an entity is detached from the entity manager
*/
final readonly class EntityDetachedEvent
{
public Timestamp $timestamp;
public function __construct(
public object $entity,
public string $entityClass,
public mixed $entityId,
?Timestamp $timestamp = null
) {
$this->timestamp = $timestamp ?? Timestamp::now();
}
/**
* Check if this event is for a specific entity class
*/
public function isEntityOfType(string $expectedClass): bool
{
return $this->entityClass === $expectedClass || $this->entity instanceof $expectedClass;
}
/**
* Get event metadata
*/
public function getEventData(): array
{
return [
'event_type' => 'entity_detached',
'entity_class' => $this->entityClass,
'entity_id' => $this->entityId,
'timestamp' => $this->timestamp->toFloat(),
];
}
}

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events;
use App\Framework\Core\Events\EventDispatcher;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
/**
* Manages entity lifecycle events and coordinates with the core event system
*/
final class EntityEventManager
{
public function __construct(
private readonly EventDispatcher $eventDispatcher,
private readonly DomainEventCollector $domainEventCollector,
private readonly Clock $clock
) {
}
/**
* Dispatch an entity lifecycle event
*/
public function dispatchLifecycleEvent(object $event): void
{
$this->eventDispatcher->dispatch($event);
}
/**
* Record a domain event for later dispatch
*/
public function recordDomainEvent(object $entity, object $event): void
{
$this->domainEventCollector->recordEvent($entity, $event);
}
/**
* Dispatch all domain events for an entity and clear them
*/
public function dispatchDomainEventsForEntity(object $entity): void
{
$events = $this->domainEventCollector->getEventsForEntity($entity);
foreach ($events as $event) {
$this->eventDispatcher->dispatch($event);
}
$this->domainEventCollector->clearEventsForEntity($entity);
}
/**
* Dispatch all domain events across all entities and clear them
*/
public function dispatchAllDomainEvents(): void
{
$allEvents = $this->domainEventCollector->getAllEvents();
foreach ($allEvents as $event) {
$this->eventDispatcher->dispatch($event);
}
// Events are automatically cleared when entities are garbage collected
// But we can manually clear for immediate cleanup
foreach ($this->domainEventCollector->getAllEvents() as $entity) {
$this->domainEventCollector->clearEventsForEntity($entity);
}
}
/**
* Create and dispatch an EntityCreatedEvent
*/
public function entityCreated(object $entity, string $entityClass, mixed $entityId, array $data = []): void
{
$timestamp = Timestamp::fromClock($this->clock);
$event = new EntityCreatedEvent($entity, $entityClass, $entityId, $data, $timestamp);
$this->dispatchLifecycleEvent($event);
}
/**
* Create and dispatch an EntityUpdatedEvent
*/
public function entityUpdated(
object $entity,
string $entityClass,
mixed $entityId,
array $changes = [],
array $oldValues = [],
array $newValues = []
): void {
$timestamp = Timestamp::fromClock($this->clock);
$event = new EntityUpdatedEvent($entity, $entityClass, $entityId, $changes, $oldValues, $newValues, $timestamp);
$this->dispatchLifecycleEvent($event);
}
/**
* Create and dispatch an EntityDeletedEvent
*/
public function entityDeleted(object $entity, string $entityClass, mixed $entityId, array $deletedData = []): void
{
$timestamp = Timestamp::fromClock($this->clock);
$event = new EntityDeletedEvent($entity, $entityClass, $entityId, $deletedData, $timestamp);
$this->dispatchLifecycleEvent($event);
}
/**
* Create and dispatch an EntityLoadedEvent
*/
public function entityLoaded(
object $entity,
string $entityClass,
mixed $entityId,
array $loadedData = [],
bool $wasLazy = false
): void {
$timestamp = Timestamp::fromClock($this->clock);
$event = new EntityLoadedEvent($entity, $entityClass, $entityId, $loadedData, $wasLazy, $timestamp);
$this->dispatchLifecycleEvent($event);
}
/**
* Create and dispatch an EntityDetachedEvent
*/
public function entityDetached(object $entity, string $entityClass, mixed $entityId): void
{
$timestamp = Timestamp::fromClock($this->clock);
$event = new EntityDetachedEvent($entity, $entityClass, $entityId, $timestamp);
$this->dispatchLifecycleEvent($event);
}
/**
* Get domain event collector for advanced usage
*/
public function getDomainEventCollector(): DomainEventCollector
{
return $this->domainEventCollector;
}
/**
* Get core event dispatcher for advanced usage
*/
public function getEventDispatcher(): EventDispatcher
{
return $this->eventDispatcher;
}
/**
* Get statistics about domain events
*/
public function getDomainEventStats(): array
{
return [
'tracked_entities' => $this->domainEventCollector->getTrackedEntityCount(),
'total_events' => $this->domainEventCollector->getTotalEventCount(),
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Event that is fired when an entity is loaded from database
*/
final readonly class EntityLoadedEvent
{
public Timestamp $timestamp;
public function __construct(
public object $entity,
public string $entityClass,
public mixed $entityId,
public array $loadedData = [],
public bool $wasLazy = false,
?Timestamp $timestamp = null,
) {
$this->timestamp = $timestamp ?? Timestamp::now();
}
/**
* Check if this event is for a specific entity class
*/
public function isEntityOfType(string $expectedClass): bool
{
return $this->entityClass === $expectedClass || $this->entity instanceof $expectedClass;
}
/**
* Get event metadata
*/
public function getEventData(): array
{
return [
'event_type' => 'entity_loaded',
'entity_class' => $this->entityClass,
'entity_id' => $this->entityId,
'timestamp' => $this->timestamp->toFloat(),
'was_lazy' => $this->wasLazy,
'loaded_data' => $this->loadedData,
];
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Event that is fired when an entity is updated
*/
final readonly class EntityUpdatedEvent
{
public Timestamp $timestamp;
public function __construct(
public object $entity,
public string $entityClass,
public mixed $entityId,
public array $changes = [],
public array $oldValues = [],
public array $newValues = [],
?Timestamp $timestamp = null
) {
$this->timestamp = $timestamp ?? Timestamp::now();
}
/**
* Check if this event is for a specific entity class
*/
public function isEntityOfType(string $expectedClass): bool
{
return $this->entityClass === $expectedClass || $this->entity instanceof $expectedClass;
}
/**
* Check if a specific field was changed
*/
public function hasFieldChanged(string $fieldName): bool
{
return in_array($fieldName, $this->changes, true);
}
/**
* Get the old value of a field
*/
public function getOldValue(string $fieldName): mixed
{
return $this->oldValues[$fieldName] ?? null;
}
/**
* Get the new value of a field
*/
public function getNewValue(string $fieldName): mixed
{
return $this->newValues[$fieldName] ?? null;
}
/**
* Get event metadata
*/
public function getEventData(): array
{
return [
'event_type' => 'entity_updated',
'entity_class' => $this->entityClass,
'entity_id' => $this->entityId,
'timestamp' => $this->timestamp->toFloat(),
'changes' => $this->changes,
'changed_fields_count' => count($this->changes),
];
}
}

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events\Examples;
use App\Framework\Database\EntityManager;
/**
* Comprehensive Entity Event System usage examples
*
* This demonstrates how to use the entity lifecycle events
* with the existing Core EventDispatcher system.
*/
final class EntityEventUsageExample
{
public function __construct(
private readonly EntityManager $entityManager
) {
}
/**
* Example: Basic lifecycle event handling
*/
public function basicLifecycleEventHandling(): void
{
// Create a new entity
$user = new ExampleUser('john@example.com', 'John Doe');
// The EntityManager will automatically dispatch EntityCreatedEvent
$this->entityManager->insert($user);
// Update the entity
$user = $user->updateEmail('john.doe@example.com');
// The EntityManager will automatically dispatch EntityUpdatedEvent
$this->entityManager->update($user);
// Delete the entity
// The EntityManager will automatically dispatch EntityDeletedEvent
$this->entityManager->delete($user);
}
/**
* Example: Domain event recording and dispatching
*/
public function domainEventHandling(): void
{
$user = new ExampleUser('jane@example.com', 'Jane Smith');
// Record domain events (these are not automatically dispatched)
$welcomeEvent = new UserWelcomeEmailEvent($user, 'jane@example.com');
$this->entityManager->recordDomainEvent($user, $welcomeEvent);
$analyticsEvent = new UserRegistrationAnalyticsEvent($user, 'web', 'organic');
$this->entityManager->recordDomainEvent($user, $analyticsEvent);
// Create the entity
$this->entityManager->insert($user);
// Manually dispatch domain events for this entity
$this->entityManager->dispatchDomainEventsForEntity($user);
// Or dispatch all domain events across all entities
// $this->entityManager->dispatchAllDomainEvents();
}
/**
* Example: Event statistics and monitoring
*/
public function eventStatistics(): array
{
// Get domain event statistics
$domainStats = $this->entityManager->getDomainEventStats();
return [
'tracked_entities' => $domainStats['tracked_entities'],
'pending_domain_events' => $domainStats['total_events'],
];
}
/**
* Example: Batch operations with events
*/
public function batchOperationsWithEvents(): void
{
$users = [
new ExampleUser('user1@example.com', 'User One'),
new ExampleUser('user2@example.com', 'User Two'),
new ExampleUser('user3@example.com', 'User Three'),
];
// Record domain events for each user
foreach ($users as $user) {
$event = new UserRegistrationAnalyticsEvent($user, 'batch_import', 'admin');
$this->entityManager->recordDomainEvent($user, $event);
}
// Save all users (will trigger EntityCreatedEvent for each)
$this->entityManager->saveAll(...$users);
// Dispatch all domain events at once
$this->entityManager->dispatchAllDomainEvents();
}
/**
* Example: Conditional event handling
*/
public function conditionalEventHandling(): void
{
$user = new ExampleUser('conditional@example.com', 'Conditional User');
// Record conditional domain event
$premiumEvent = new UserPremiumUpgradeEvent($user, 'premium_plan');
$this->entityManager->recordDomainEvent($user, $premiumEvent);
// Events will be dispatched based on conditions in handlers
$this->entityManager->insert($user);
$this->entityManager->dispatchDomainEventsForEntity($user);
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events\Examples;
/**
* Example Entity for demonstration purposes
*/
final class ExampleUser
{
public readonly string $id;
public function __construct(
public readonly string $email,
public readonly string $name,
?string $id = null
) {
$this->id = $id ?? uniqid('user_');
}
public function updateEmail(string $newEmail): self
{
return new self($newEmail, $this->name, $this->id);
}
}

View File

@@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events\Examples;
use App\Framework\Core\Events\OnEvent;
use App\Framework\Database\Events\EntityCreatedEvent;
use App\Framework\Database\Events\EntityDeletedEvent;
use App\Framework\Database\Events\EntityDetachedEvent;
use App\Framework\Database\Events\EntityLoadedEvent;
use App\Framework\Database\Events\EntityUpdatedEvent;
/**
* Example Event Handlers using Core EventDispatcher
*/
final class UserEventHandlers
{
/**
* Handle entity creation for all entities
*/
#[OnEvent(priority: 100)]
public function onEntityCreated(EntityCreatedEvent $event): void
{
error_log("Entity created: {$event->entityClass} with ID {$event->entityId}");
// Log to analytics service
$this->logAnalyticsEvent('entity_created', [
'entity_class' => $event->entityClass,
'entity_id' => $event->entityId,
'timestamp' => $event->timestamp,
]);
}
/**
* Handle user-specific entity creation
*/
#[OnEvent(priority: 90)]
public function onUserCreated(EntityCreatedEvent $event): void
{
if (! $event->isEntityOfType(ExampleUser::class)) {
return;
}
error_log("New user registered: {$event->entity->email}");
// Send welcome email, create user profile, etc.
$this->sendWelcomeEmail($event->entity);
$this->createUserProfile($event->entity);
$this->trackUserRegistration($event->entity);
}
/**
* Handle entity updates with change tracking
*/
#[OnEvent(priority: 100)]
public function onEntityUpdated(EntityUpdatedEvent $event): void
{
error_log("Entity updated: {$event->entityClass} with ID {$event->entityId}");
if ($event->isEntityOfType(ExampleUser::class)) {
$this->handleUserUpdate($event);
}
}
/**
* Handle entity deletion with cleanup
*/
#[OnEvent(priority: 100)]
public function onEntityDeleted(EntityDeletedEvent $event): void
{
error_log("Entity deleted: {$event->entityClass} with ID {$event->entityId}");
if ($event->isEntityOfType(ExampleUser::class)) {
$this->cleanupUserData($event->entity);
$this->notifyUserDeletion($event->entity);
}
}
/**
* Handle entity loading for performance monitoring
*/
#[OnEvent(priority: 50)]
public function onEntityLoaded(EntityLoadedEvent $event): void
{
if ($event->wasLazy) {
error_log("Lazy entity loaded: {$event->entityClass} with ID {$event->entityId}");
}
// Track loading performance
$this->trackEntityLoadTime($event);
}
/**
* Handle entity detachment for cleanup
*/
#[OnEvent(priority: 100)]
public function onEntityDetached(EntityDetachedEvent $event): void
{
error_log("Entity detached: {$event->entityClass} with ID {$event->entityId}");
// Clean up any cached references
$this->cleanupEntityReferences($event->entity);
}
/**
* Handle domain events
*/
#[OnEvent(priority: 200)]
public function onUserWelcomeEmail(UserWelcomeEmailEvent $event): void
{
error_log("Sending welcome email to: {$event->email}");
// Send actual email
// $this->emailService->sendWelcomeEmail($event->user, $event->email);
}
#[OnEvent(priority: 150)]
public function onUserRegistrationAnalytics(UserRegistrationAnalyticsEvent $event): void
{
error_log("Tracking user registration: {$event->source} - {$event->channel}");
// Track in analytics system
// $this->analyticsService->track('user_registered', [
// 'user_id' => $event->user->id,
// 'source' => $event->source,
// 'channel' => $event->channel,
// 'timestamp' => $event->timestamp,
// ]);
}
/**
* Helper methods (implementation would depend on your services)
*/
private function logAnalyticsEvent(string $eventType, array $data): void
{
// Implementation depends on your analytics service
error_log("Analytics: {$eventType} - " . json_encode($data));
}
private function sendWelcomeEmail(ExampleUser $user): void
{
error_log("Sending welcome email to: {$user->email}");
}
private function createUserProfile(ExampleUser $user): void
{
error_log("Creating user profile for: {$user->name}");
}
private function trackUserRegistration(ExampleUser $user): void
{
error_log("Tracking registration for user: {$user->id}");
}
private function handleUserUpdate(EntityUpdatedEvent $event): void
{
error_log("User updated: {$event->entity->email}");
// Handle specific field changes
if ($event->hasFieldChanged('email')) {
$oldEmail = $event->getOldValue('email');
$newEmail = $event->getNewValue('email');
error_log("Email changed from {$oldEmail} to {$newEmail}");
// Send email confirmation, update related records, etc.
}
}
private function cleanupUserData(ExampleUser $user): void
{
error_log("Cleaning up data for deleted user: {$user->id}");
// Delete user files, clear caches, notify related services
}
private function notifyUserDeletion(ExampleUser $user): void
{
error_log("Notifying services about user deletion: {$user->id}");
}
private function trackEntityLoadTime(EntityLoadedEvent $event): void
{
error_log("Entity load tracked: {$event->entityClass} loaded at {$event->timestamp}");
}
private function cleanupEntityReferences(object $entity): void
{
error_log("Cleaning up references for detached entity: " . get_class($entity));
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events\Examples;
/**
* Example Domain Event - User Premium Upgrade
*/
final readonly class UserPremiumUpgradeEvent
{
public function __construct(
public object $user,
public string $planType,
public ?float $timestamp = null
) {
$this->timestamp ??= microtime(true);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events\Examples;
/**
* Example Domain Event - User Registration Analytics
*/
final readonly class UserRegistrationAnalyticsEvent
{
public function __construct(
public object $user,
public string $source,
public string $channel,
public ?float $timestamp = null
) {
$this->timestamp ??= microtime(true);
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Events\Examples;
/**
* Example Domain Event - User Welcome Email
*/
final readonly class UserWelcomeEmailEvent
{
public function __construct(
public object $user,
public string $email,
public ?float $timestamp = null
) {
$this->timestamp ??= microtime(true);
}
}