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:
@@ -1,24 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Database;
|
||||
|
||||
use App\Framework\Attributes\Singleton;
|
||||
use App\Framework\Database\Cache\EntityCacheManager;
|
||||
use App\Framework\Database\Criteria\Criteria;
|
||||
use App\Framework\Database\Criteria\CriteriaQuery;
|
||||
use App\Framework\Database\Events\EntityEventManager;
|
||||
use App\Framework\Database\Metadata\EntityMetadata;
|
||||
use App\Framework\Database\Metadata\MetadataRegistry;
|
||||
use App\Framework\Database\QueryBuilder\QueryBuilderFactory;
|
||||
use App\Framework\Database\QueryBuilder\SelectQueryBuilder;
|
||||
use App\Framework\Database\UnitOfWork\UnitOfWork;
|
||||
|
||||
#[Singleton]
|
||||
final readonly class EntityManager
|
||||
final readonly class EntityManager implements EntityLoaderInterface
|
||||
{
|
||||
private Hydrator $hydrator;
|
||||
public function __construct(
|
||||
private DatabaseManager $databaseManager,
|
||||
private MetadataRegistry $metadataRegistry,
|
||||
private TypeConverter $typeConverter,
|
||||
private IdentityMap $identityMap,
|
||||
private LazyLoader $lazyLoader
|
||||
private LazyLoader $lazyLoader,
|
||||
private HydratorInterface $hydrator,
|
||||
private BatchRelationLoader $batchRelationLoader,
|
||||
public UnitOfWork $unitOfWork,
|
||||
private QueryBuilderFactory $queryBuilderFactory,
|
||||
private EntityEventManager $entityEventManager,
|
||||
private ?EntityCacheManager $cacheManager = null
|
||||
) {
|
||||
|
||||
$this->hydrator = new Hydrator($this->typeConverter, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,6 +38,14 @@ final readonly class EntityManager
|
||||
*/
|
||||
public function find(string $entityClass, mixed $id): ?object
|
||||
{
|
||||
// Use cache manager if available
|
||||
if ($this->cacheManager !== null) {
|
||||
return $this->cacheManager->findEntity($entityClass, $id, function () use ($entityClass, $id) {
|
||||
return $this->findWithLazyLoading($entityClass, $id);
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback to direct loading
|
||||
// Prüfe Identity Map zuerst
|
||||
if ($this->identityMap->has($entityClass, $id)) {
|
||||
return $this->identityMap->get($entityClass, $id);
|
||||
@@ -57,7 +77,7 @@ final readonly class EntityManager
|
||||
$result = $this->databaseManager->getConnection()->query($query, [$id]);
|
||||
$data = $result->fetch();
|
||||
|
||||
if (!$data) {
|
||||
if (! $data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -66,6 +86,9 @@ final readonly class EntityManager
|
||||
// In Identity Map speichern
|
||||
$this->identityMap->set($entityClass, $id, $entity);
|
||||
|
||||
// Entity Loaded Event dispatchen
|
||||
$this->entityEventManager->entityLoaded($entity, $entityClass, $id, $data, false);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
@@ -80,12 +103,17 @@ final readonly class EntityManager
|
||||
$query = "SELECT {$metadata->idColumn} FROM {$metadata->tableName} WHERE {$metadata->idColumn} = ?";
|
||||
$result = $this->databaseManager->getConnection()->query($query, [$id]);
|
||||
|
||||
if (!$result->fetch()) {
|
||||
if (! $result->fetch()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Erstelle Lazy Ghost
|
||||
return $this->lazyLoader->createLazyGhost($metadata, $id);
|
||||
$entity = $this->lazyLoader->createLazyGhost($metadata, $id);
|
||||
|
||||
// Entity Loaded Event dispatchen (als Lazy)
|
||||
$this->entityEventManager->entityLoaded($entity, $entityClass, $id, [], true);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +127,7 @@ final readonly class EntityManager
|
||||
}
|
||||
|
||||
$metadata = $this->metadataRegistry->getMetadata($entityClass);
|
||||
|
||||
return $this->lazyLoader->createLazyGhost($metadata, $id);
|
||||
}
|
||||
|
||||
@@ -173,13 +202,28 @@ final readonly class EntityManager
|
||||
* Findet Entities nach Kriterien
|
||||
*/
|
||||
public function findBy(string $entityClass, array $criteria, ?array $orderBy = null, ?int $limit = null): array
|
||||
{
|
||||
// Use cache manager if available
|
||||
if ($this->cacheManager !== null) {
|
||||
return $this->cacheManager->findCollection($entityClass, $criteria, $orderBy, $limit, null, function () use ($entityClass, $criteria, $orderBy, $limit) {
|
||||
return $this->findByWithoutCache($entityClass, $criteria, $orderBy, $limit);
|
||||
});
|
||||
}
|
||||
|
||||
return $this->findByWithoutCache($entityClass, $criteria, $orderBy, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method for finding entities without cache
|
||||
*/
|
||||
private function findByWithoutCache(string $entityClass, array $criteria, ?array $orderBy = null, ?int $limit = null): array
|
||||
{
|
||||
$metadata = $this->metadataRegistry->getMetadata($entityClass);
|
||||
|
||||
$query = "SELECT {$metadata->idColumn} FROM {$metadata->tableName}";
|
||||
$params = [];
|
||||
|
||||
if (!empty($criteria)) {
|
||||
if (! empty($criteria)) {
|
||||
$conditions = [];
|
||||
foreach ($criteria as $field => $value) {
|
||||
$columnName = $metadata->getColumnName($field);
|
||||
@@ -219,9 +263,110 @@ final readonly class EntityManager
|
||||
public function findOneBy(string $entityClass, array $criteria): ?object
|
||||
{
|
||||
$results = $this->findBy($entityClass, $criteria, limit: 1);
|
||||
|
||||
return $results[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Findet Entities mit vorab geladenen Relationen (N+1 Solution)
|
||||
*
|
||||
* @param string $entityClass
|
||||
* @param array $criteria
|
||||
* @param array $relations Relations to preload ['comments', 'author', etc.]
|
||||
* @param array|null $orderBy
|
||||
* @param int|null $limit
|
||||
* @return array<object>
|
||||
*/
|
||||
public function findWithRelations(
|
||||
string $entityClass,
|
||||
array $criteria = [],
|
||||
array $relations = [],
|
||||
?array $orderBy = null,
|
||||
?int $limit = null
|
||||
): array {
|
||||
// Step 1: Load base entities (using existing findBy logic but eager)
|
||||
$entities = $this->findByEager($entityClass, $criteria, $orderBy, $limit);
|
||||
|
||||
if (empty($entities) || empty($relations)) {
|
||||
return $entities;
|
||||
}
|
||||
|
||||
// Step 2: Preload each specified relation in batch
|
||||
foreach ($relations as $relationName) {
|
||||
$this->batchRelationLoader->preloadRelation($entities, $relationName);
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager version of findBy - loads full entities immediately
|
||||
* Used internally by findWithRelations
|
||||
*/
|
||||
private function findByEager(string $entityClass, array $criteria, ?array $orderBy = null, ?int $limit = null): array
|
||||
{
|
||||
$metadata = $this->metadataRegistry->getMetadata($entityClass);
|
||||
|
||||
$query = "SELECT * FROM {$metadata->tableName}";
|
||||
$params = [];
|
||||
|
||||
if (! empty($criteria)) {
|
||||
$conditions = [];
|
||||
foreach ($criteria as $field => $value) {
|
||||
if (is_array($value)) {
|
||||
// Handle IN queries for batch loading
|
||||
$placeholders = str_repeat('?,', count($value) - 1) . '?';
|
||||
$columnName = $metadata->getColumnName($field);
|
||||
$conditions[] = "{$columnName} IN ({$placeholders})";
|
||||
$params = array_merge($params, array_values($value));
|
||||
} else {
|
||||
$columnName = $metadata->getColumnName($field);
|
||||
$conditions[] = "{$columnName} = ?";
|
||||
$params[] = $value;
|
||||
}
|
||||
}
|
||||
$query .= " WHERE " . implode(' AND ', $conditions);
|
||||
}
|
||||
|
||||
if ($orderBy) {
|
||||
$orderClauses = [];
|
||||
foreach ($orderBy as $field => $direction) {
|
||||
$columnName = $metadata->getColumnName($field);
|
||||
$orderClauses[] = "{$columnName} " . strtoupper($direction);
|
||||
}
|
||||
$query .= " ORDER BY " . implode(', ', $orderClauses);
|
||||
}
|
||||
|
||||
if ($limit) {
|
||||
$query .= " LIMIT {$limit}";
|
||||
}
|
||||
|
||||
$result = $this->databaseManager->getConnection()->query($query, $params);
|
||||
|
||||
$entities = [];
|
||||
foreach ($result->fetchAll() as $data) {
|
||||
$idValue = $data[$metadata->idColumn];
|
||||
|
||||
// Check identity map first
|
||||
if ($this->identityMap->has($entityClass, $idValue)) {
|
||||
$entity = $this->identityMap->get($entityClass, $idValue);
|
||||
|
||||
// If it's a lazy ghost, initialize it for eager loading
|
||||
if ($this->isLazyGhost($entity)) {
|
||||
$this->initializeLazyObject($entity);
|
||||
}
|
||||
|
||||
$entities[] = $entity;
|
||||
} else {
|
||||
$entity = $this->hydrator->hydrate($metadata, $data);
|
||||
$this->identityMap->set($entityClass, $idValue, $entity);
|
||||
$entities[] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility Methods
|
||||
*/
|
||||
@@ -240,7 +385,12 @@ final readonly class EntityManager
|
||||
try {
|
||||
$property = $metadata->reflection->getProperty($paramName);
|
||||
$id = $property->getValue($entity);
|
||||
|
||||
// Entity Detached Event dispatchen (vor Identity Map Remove)
|
||||
$this->entityEventManager->entityDetached($entity, $entity::class, $id);
|
||||
|
||||
$this->identityMap->remove($entity::class, $id);
|
||||
|
||||
break;
|
||||
} catch (\ReflectionException) {
|
||||
// Property nicht gefunden
|
||||
@@ -283,6 +433,72 @@ final readonly class EntityManager
|
||||
return IdGenerator::generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get profiling statistics if profiling is enabled
|
||||
*/
|
||||
public function getProfilingStatistics(): ?array
|
||||
{
|
||||
$connection = $this->databaseManager->getConnection();
|
||||
|
||||
if ($connection instanceof \App\Framework\Database\Profiling\ProfilingConnection) {
|
||||
return $connection->getProfilingStatistics();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get profiling summary if profiling is enabled
|
||||
*/
|
||||
public function getProfilingSummary(): ?\App\Framework\Database\Profiling\ProfileSummary
|
||||
{
|
||||
$connection = $this->databaseManager->getConnection();
|
||||
|
||||
if ($connection instanceof \App\Framework\Database\Profiling\ProfilingConnection) {
|
||||
return $connection->getProfilingSummary();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear profiling data if profiling is enabled
|
||||
*/
|
||||
public function clearProfilingData(): void
|
||||
{
|
||||
$connection = $this->databaseManager->getConnection();
|
||||
|
||||
if ($connection instanceof \App\Framework\Database\Profiling\ProfilingConnection) {
|
||||
$connection->clearProfilingData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable profiling at runtime
|
||||
*/
|
||||
public function setProfilingEnabled(bool $enabled): void
|
||||
{
|
||||
$connection = $this->databaseManager->getConnection();
|
||||
|
||||
if ($connection instanceof \App\Framework\Database\Profiling\ProfilingConnection) {
|
||||
$connection->setProfilingEnabled($enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if profiling is enabled
|
||||
*/
|
||||
public function isProfilingEnabled(): bool
|
||||
{
|
||||
$connection = $this->databaseManager->getConnection();
|
||||
|
||||
if ($connection instanceof \App\Framework\Database\Profiling\ProfilingConnection) {
|
||||
return $connection->isProfilingEnabled();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert eine Entity (INSERT oder UPDATE)
|
||||
*/
|
||||
@@ -295,10 +511,17 @@ final readonly class EntityManager
|
||||
|
||||
// Prüfe ob Entity bereits existiert
|
||||
if ($this->exists($entity::class, $id)) {
|
||||
return $this->update($entity);
|
||||
$result = $this->update($entity);
|
||||
} else {
|
||||
return $this->insert($entity);
|
||||
$result = $this->insert($entity);
|
||||
}
|
||||
|
||||
// Cache the entity after successful save
|
||||
if ($this->cacheManager !== null) {
|
||||
$this->cacheManager->cacheEntity($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,7 +560,7 @@ final readonly class EntityManager
|
||||
// Property-Wert auslesen
|
||||
$property = $metadata->reflection->getProperty($propertyName);
|
||||
|
||||
if(!$property->isInitialized($entity)) {
|
||||
if (! $property->isInitialized($entity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -360,6 +583,9 @@ final readonly class EntityManager
|
||||
$id = $idProperty->getValue($entity);
|
||||
$this->identityMap->set($entity::class, $id, $entity);
|
||||
|
||||
// Entity Created Event dispatchen
|
||||
$this->entityEventManager->entityCreated($entity, $entity::class, $id, $params);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
@@ -370,6 +596,16 @@ final readonly class EntityManager
|
||||
{
|
||||
$metadata = $this->metadataRegistry->getMetadata($entity::class);
|
||||
|
||||
// ID für Change Tracking und WHERE Clause
|
||||
$idProperty = $metadata->reflection->getProperty($metadata->idProperty);
|
||||
$id = $idProperty->getValue($entity);
|
||||
|
||||
// Change Tracking: Original Entity aus IdentityMap laden
|
||||
$originalEntity = $this->identityMap->get($entity::class, $id);
|
||||
$changes = [];
|
||||
$oldValues = [];
|
||||
$newValues = [];
|
||||
|
||||
// SET-Clause und Params aufbauen
|
||||
$setClause = [];
|
||||
$params = [];
|
||||
@@ -377,7 +613,7 @@ final readonly class EntityManager
|
||||
foreach ($metadata->properties as $propertyName => $propertyMetadata) {
|
||||
|
||||
// Relations beim Update ignorieren
|
||||
if($propertyMetadata->isRelation) {
|
||||
if ($propertyMetadata->isRelation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -386,16 +622,35 @@ final readonly class EntityManager
|
||||
continue;
|
||||
}
|
||||
|
||||
$setClause[] = "{$propertyMetadata->columnName} = ?";
|
||||
|
||||
// Property-Wert auslesen
|
||||
$property = $metadata->reflection->getProperty($propertyName);
|
||||
$params[] = $property->getValue($entity);
|
||||
$newValue = $property->getValue($entity);
|
||||
|
||||
// Change Tracking: Vergleiche mit Original-Werten
|
||||
$oldValue = null;
|
||||
if ($originalEntity !== null) {
|
||||
$originalProperty = $metadata->reflection->getProperty($propertyName);
|
||||
$oldValue = $originalProperty->getValue($originalEntity);
|
||||
}
|
||||
|
||||
// Nur bei Änderungen in SET clause aufnehmen und Changes tracken
|
||||
if ($originalEntity === null || $oldValue !== $newValue) {
|
||||
$setClause[] = "{$propertyMetadata->columnName} = ?";
|
||||
$params[] = $newValue;
|
||||
|
||||
// Change Tracking Daten sammeln
|
||||
$changes[] = $propertyName;
|
||||
$oldValues[$propertyName] = $oldValue;
|
||||
$newValues[$propertyName] = $newValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn keine Änderungen vorliegen, kein UPDATE ausführen
|
||||
if (empty($setClause)) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
// ID für WHERE Clause hinzufügen
|
||||
$idProperty = $metadata->reflection->getProperty($metadata->idProperty);
|
||||
$id = $idProperty->getValue($entity);
|
||||
$params[] = $id;
|
||||
|
||||
// UPDATE Query bauen
|
||||
@@ -408,6 +663,9 @@ final readonly class EntityManager
|
||||
// Identity Map aktualisieren
|
||||
$this->identityMap->set($entity::class, $id, $entity);
|
||||
|
||||
// Entity Updated Event mit Change Tracking dispatchen
|
||||
$this->entityEventManager->entityUpdated($entity, $entity::class, $id, $changes, $oldValues, $newValues);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
@@ -426,6 +684,14 @@ final readonly class EntityManager
|
||||
$query = "DELETE FROM {$metadata->tableName} WHERE {$metadata->idColumn} = ?";
|
||||
$this->databaseManager->getConnection()->query($query, [$id]);
|
||||
|
||||
// Evict from cache
|
||||
if ($this->cacheManager !== null) {
|
||||
$this->cacheManager->evictEntity($entity);
|
||||
}
|
||||
|
||||
// Entity Deleted Event dispatchen (vor Identity Map Remove)
|
||||
$this->entityEventManager->entityDeleted($entity, $entity::class, $id, []);
|
||||
|
||||
// Aus Identity Map entfernen
|
||||
$this->identityMap->remove($entity::class, $id);
|
||||
}
|
||||
@@ -439,6 +705,7 @@ final readonly class EntityManager
|
||||
foreach ($entities as $entity) {
|
||||
$result[] = $this->save($entity);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -460,10 +727,163 @@ final readonly class EntityManager
|
||||
try {
|
||||
$result = $callback($this);
|
||||
$connection->commit();
|
||||
|
||||
return $result;
|
||||
} catch (\Throwable $e) {
|
||||
$connection->rollback();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute criteria query and return entities
|
||||
*/
|
||||
public function findByCriteria(Criteria $criteria): array
|
||||
{
|
||||
$metadata = $this->metadataRegistry->getMetadata($criteria->entityClass);
|
||||
$criteriaQuery = new CriteriaQuery($criteria, $metadata->tableName);
|
||||
|
||||
$sql = $criteriaQuery->toSql();
|
||||
$parameters = $criteriaQuery->getParameters();
|
||||
|
||||
$result = $this->databaseManager->getConnection()->query($sql, $parameters);
|
||||
|
||||
$entities = [];
|
||||
foreach ($result->fetchAll() as $data) {
|
||||
// Check if it's a projection query (non-entity result)
|
||||
$projection = $criteria->getProjection();
|
||||
if ($projection && count($projection->getAliases()) > 0) {
|
||||
// Return raw data for projection queries
|
||||
$entities[] = $data;
|
||||
} else {
|
||||
// Normal entity hydration
|
||||
$idValue = $data[$metadata->idColumn];
|
||||
|
||||
if ($this->identityMap->has($criteria->entityClass, $idValue)) {
|
||||
$entities[] = $this->identityMap->get($criteria->entityClass, $idValue);
|
||||
} else {
|
||||
$entity = $this->hydrator->hydrate($metadata, $data);
|
||||
$this->identityMap->set($criteria->entityClass, $idValue, $entity);
|
||||
$entities[] = $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute criteria query and return first result
|
||||
*/
|
||||
public function findOneByCriteria(Criteria $criteria): mixed
|
||||
{
|
||||
$criteria->setMaxResults(1);
|
||||
$results = $this->findByCriteria($criteria);
|
||||
|
||||
return $results[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count entities matching criteria
|
||||
*/
|
||||
public function countByCriteria(Criteria $criteria): int
|
||||
{
|
||||
$metadata = $this->metadataRegistry->getMetadata($criteria->entityClass);
|
||||
|
||||
// Create count criteria
|
||||
$countCriteria = clone $criteria;
|
||||
$countCriteria->setProjection(\App\Framework\Database\Criteria\Projections::count());
|
||||
$countCriteria->setMaxResults(null);
|
||||
$countCriteria->setFirstResult(null);
|
||||
|
||||
$criteriaQuery = new CriteriaQuery($countCriteria, $metadata->tableName);
|
||||
|
||||
$sql = $criteriaQuery->toSql();
|
||||
$parameters = $criteriaQuery->getParameters();
|
||||
|
||||
$result = $this->databaseManager->getConnection()->queryScalar($sql, $parameters);
|
||||
|
||||
return (int) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new query builder
|
||||
*/
|
||||
public function createQueryBuilder(): SelectQueryBuilder
|
||||
{
|
||||
return $this->queryBuilderFactory->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query builder for a specific entity
|
||||
*/
|
||||
public function createQueryBuilderFor(string $entityClass): SelectQueryBuilder
|
||||
{
|
||||
return $this->queryBuilderFactory->selectFromWithEntity($entityClass, $this->identityMap, $this->hydrator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query builder for a table
|
||||
*/
|
||||
public function createQueryBuilderForTable(string $tableName): SelectQueryBuilder
|
||||
{
|
||||
return $this->queryBuilderFactory->selectFromTable($tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identity map (for QueryBuilder integration)
|
||||
*/
|
||||
public function getIdentityMap(): IdentityMap
|
||||
{
|
||||
return $this->identityMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hydrator (for QueryBuilder integration)
|
||||
*/
|
||||
public function getHydrator(): HydratorInterface
|
||||
{
|
||||
return $this->hydrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity event manager
|
||||
*/
|
||||
public function getEntityEventManager(): EntityEventManager
|
||||
{
|
||||
return $this->entityEventManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a domain event for an entity
|
||||
*/
|
||||
public function recordDomainEvent(object $entity, object $event): void
|
||||
{
|
||||
$this->entityEventManager->recordDomainEvent($entity, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch all domain events for an entity
|
||||
*/
|
||||
public function dispatchDomainEventsForEntity(object $entity): void
|
||||
{
|
||||
$this->entityEventManager->dispatchDomainEventsForEntity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch all domain events across all entities
|
||||
*/
|
||||
public function dispatchAllDomainEvents(): void
|
||||
{
|
||||
$this->entityEventManager->dispatchAllDomainEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get domain event statistics
|
||||
*/
|
||||
public function getDomainEventStats(): array
|
||||
{
|
||||
return $this->entityEventManager->getDomainEventStats();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user