feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready

This commit is contained in:
2025-10-31 01:39:24 +01:00
parent 55c04e4fd0
commit e26eb2aa12
601 changed files with 44184 additions and 32477 deletions

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Framework\StateManagement\Database;
use App\Framework\Attributes\Entity;
use App\Framework\Attributes\Id;
use App\Framework\Attributes\Column;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Component State Database Entity
*
* Represents current state of a LiveComponent stored in database.
* EntityManager-compatible entity with attribute-based mapping.
*/
#[Entity(table: 'component_state')]
final readonly class ComponentStateEntity
{
public function __construct(
#[Id]
#[Column(name: 'component_id')]
public string $componentId,
#[Column(name: 'state_data', type: 'text')]
public string $stateData, // Encrypted state
#[Column(name: 'state_class')]
public string $stateClass,
#[Column(name: 'component_name')]
public string $componentName,
#[Column(name: 'user_id')]
public ?string $userId,
#[Column(name: 'session_id')]
public ?string $sessionId,
#[Column(name: 'version', type: 'integer')]
public int $version,
#[Column(name: 'checksum')]
public string $checksum, // SHA256 of state_data
#[Column(name: 'created_at', type: 'datetime')]
public Timestamp $createdAt,
#[Column(name: 'updated_at', type: 'datetime')]
public Timestamp $updatedAt,
#[Column(name: 'expires_at', type: 'datetime')]
public ?Timestamp $expiresAt,
) {}
/**
* Check if state is expired
*/
public function isExpired(): bool
{
if ($this->expiresAt === null) {
return false;
}
return Timestamp::now()->isAfter($this->expiresAt);
}
/**
* Create from database row
*/
public static function fromRow(array $row): self
{
return new self(
componentId: $row['component_id'],
stateData: $row['state_data'],
stateClass: $row['state_class'],
componentName: $row['component_name'],
userId: $row['user_id'] ?? null,
sessionId: $row['session_id'] ?? null,
version: (int) $row['version'],
checksum: $row['checksum'],
createdAt: Timestamp::fromString($row['created_at']),
updatedAt: Timestamp::fromString($row['updated_at']),
expiresAt: isset($row['expires_at']) ? Timestamp::fromString($row['expires_at']) : null,
);
}
/**
* Convert to database row
*/
public function toRow(): array
{
return [
'component_id' => $this->componentId,
'state_data' => $this->stateData,
'state_class' => $this->stateClass,
'component_name' => $this->componentName,
'user_id' => $this->userId,
'session_id' => $this->sessionId,
'version' => $this->version,
'checksum' => $this->checksum,
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
'updated_at' => $this->updatedAt->format('Y-m-d H:i:s'),
'expires_at' => $this->expiresAt?->format('Y-m-d H:i:s'),
];
}
/**
* Create new version with updated state
*/
public function withUpdatedState(
string $stateData,
string $checksum,
?Timestamp $expiresAt = null
): self {
return new self(
componentId: $this->componentId,
stateData: $stateData,
stateClass: $this->stateClass,
componentName: $this->componentName,
userId: $this->userId,
sessionId: $this->sessionId,
version: $this->version + 1,
checksum: $checksum,
createdAt: $this->createdAt,
updatedAt: Timestamp::now(),
expiresAt: $expiresAt,
);
}
}

View File

@@ -0,0 +1,269 @@
<?php
declare(strict_types=1);
namespace App\Framework\StateManagement\Database;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Database\EntityManager;
use App\Framework\Logging\Logger;
use App\Framework\StateManagement\StateHistoryManager;
use App\Framework\LiveComponents\Attributes\TrackStateHistory;
/**
* Database-backed State History Manager
*
* Stores state change history in component_state_history table via EntityManager.
* Integrates with #[TrackStateHistory] attribute for opt-in tracking.
*/
final class DatabaseStateHistoryManager implements StateHistoryManager
{
private int $entriesAdded = 0;
public function __construct(
private readonly EntityManager $entityManager,
private readonly ?Logger $logger = null,
) {}
public function addHistoryEntry(
string $componentId,
string $stateData,
string $stateClass,
int $version,
string $changeType,
array $context = [],
?array $changedProperties = null,
?string $previousChecksum = null,
string $currentChecksum = ''
): void {
// Create history entry entity
$entry = new StateHistoryEntry(
id: 0, // Auto-increment will handle this
componentId: $componentId,
stateData: $stateData,
stateClass: $stateClass,
version: $version,
changeType: StateChangeType::from($changeType),
changedProperties: $changedProperties,
userId: $context['user_id'] ?? null,
sessionId: $context['session_id'] ?? null,
ipAddress: $context['ip_address'] ?? null,
userAgent: $context['user_agent'] ?? null,
previousChecksum: $previousChecksum,
currentChecksum: $currentChecksum,
createdAt: Timestamp::now(),
);
// Persist via EntityManager
$this->entityManager->unitOfWork->persist($entry);
$this->entityManager->unitOfWork->commit();
$this->entriesAdded++;
$this->log('debug', "History entry added for component: {$componentId}, version: {$version}");
}
public function getHistory(string $componentId, int $limit = 100, int $offset = 0): array
{
// EntityManager findBy with criteria, orderBy, limit, offset
$entries = $this->entityManager->findBy(
StateHistoryEntry::class,
['component_id' => $componentId],
['created_at' => 'DESC'],
$limit,
$offset
);
$this->log('debug', "Retrieved {count} history entries for component: {$componentId}", [
'count' => count($entries),
'limit' => $limit,
'offset' => $offset
]);
return $entries;
}
public function getHistoryByVersion(string $componentId, int $version): ?StateHistoryEntry
{
$entries = $this->entityManager->findBy(
StateHistoryEntry::class,
[
'component_id' => $componentId,
'version' => $version
],
limit: 1
);
return !empty($entries) ? $entries[0] : null;
}
public function getHistorySince(string $componentId, Timestamp $since, int $limit = 100): array
{
// Note: EntityManager findBy might need extension for timestamp comparison
// For now, get all entries and filter in PHP
$allEntries = $this->entityManager->findBy(
StateHistoryEntry::class,
['component_id' => $componentId],
['created_at' => 'ASC']
);
$filtered = array_filter($allEntries, function (StateHistoryEntry $entry) use ($since) {
return $entry->createdAt->isAfter($since) || $entry->createdAt->equals($since);
});
return array_slice($filtered, 0, $limit);
}
public function getHistoryByUser(string $userId, int $limit = 100): array
{
return $this->entityManager->findBy(
StateHistoryEntry::class,
['user_id' => $userId],
['created_at' => 'DESC'],
$limit
);
}
public function cleanup(string $componentId, int $keepLast): int
{
// Get all entries for component ordered by created_at DESC
$entries = $this->entityManager->findBy(
StateHistoryEntry::class,
['component_id' => $componentId],
['created_at' => 'DESC']
);
// Keep first $keepLast entries, delete the rest
$toDelete = array_slice($entries, $keepLast);
$deletedCount = 0;
foreach ($toDelete as $entry) {
$this->entityManager->unitOfWork->remove($entry);
$deletedCount++;
}
if ($deletedCount > 0) {
$this->entityManager->unitOfWork->commit();
$this->log('info', "Cleaned up {$deletedCount} history entries for component: {$componentId}");
}
return $deletedCount;
}
public function cleanupOlderThan(Timestamp $olderThan): int
{
// Get all entries (EntityManager limitation - need to filter in PHP)
$allEntries = $this->entityManager->findBy(
StateHistoryEntry::class,
[]
);
$toDelete = array_filter($allEntries, function (StateHistoryEntry $entry) use ($olderThan) {
return $entry->createdAt->isBefore($olderThan);
});
$deletedCount = 0;
foreach ($toDelete as $entry) {
$this->entityManager->unitOfWork->remove($entry);
$deletedCount++;
}
if ($deletedCount > 0) {
$this->entityManager->unitOfWork->commit();
$this->log('info', "Cleaned up {$deletedCount} history entries older than {$olderThan->format('Y-m-d H:i:s')}");
}
return $deletedCount;
}
public function deleteHistory(string $componentId): int
{
$entries = $this->entityManager->findBy(
StateHistoryEntry::class,
['component_id' => $componentId]
);
$deletedCount = 0;
foreach ($entries as $entry) {
$this->entityManager->unitOfWork->remove($entry);
$deletedCount++;
}
if ($deletedCount > 0) {
$this->entityManager->unitOfWork->commit();
$this->log('info', "Deleted all {$deletedCount} history entries for component: {$componentId}");
}
return $deletedCount;
}
public function isHistoryEnabled(string $componentClass): bool
{
// Check if class has #[TrackStateHistory] attribute
if (!class_exists($componentClass)) {
return false;
}
$reflection = new \ReflectionClass($componentClass);
$attributes = $reflection->getAttributes(TrackStateHistory::class);
return !empty($attributes);
}
public function getStatistics(): array
{
// Get all history entries
$allEntries = $this->entityManager->findBy(
StateHistoryEntry::class,
[]
);
if (empty($allEntries)) {
return [
'total_entries' => 0,
'total_components' => 0,
'oldest_entry' => null,
'newest_entry' => null,
];
}
// Calculate statistics
$componentIds = array_unique(array_map(
fn(StateHistoryEntry $entry) => $entry->componentId,
$allEntries
));
$timestamps = array_map(
fn(StateHistoryEntry $entry) => $entry->createdAt,
$allEntries
);
usort($timestamps, fn(Timestamp $a, Timestamp $b) => $a->compareTo($b));
return [
'total_entries' => count($allEntries),
'total_components' => count($componentIds),
'oldest_entry' => $timestamps[0] ?? null,
'newest_entry' => $timestamps[count($timestamps) - 1] ?? null,
];
}
/**
* Log message if logger available
*/
private function log(string $level, string $message, array $context = []): void
{
if ($this->logger === null) {
return;
}
$context['entries_added'] = $this->entriesAdded;
match ($level) {
'debug' => $this->logger->debug($message, $context),
'info' => $this->logger->info($message, $context),
'warning' => $this->logger->warning($message, $context),
'error' => $this->logger->error($message, $context),
default => $this->logger->info($message, $context),
};
}
}

View File

@@ -0,0 +1,298 @@
<?php
declare(strict_types=1);
namespace App\Framework\StateManagement\Database;
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Database\EntityManager;
use App\Framework\Logging\Logger;
use App\Framework\StateManagement\SerializableState;
use App\Framework\StateManagement\StateManager;
use App\Framework\StateManagement\StateManagerStatistics;
/**
* Database-backed State Manager with Cache Layer
*
* Two-tier architecture:
* - L1 Cache: Hot state (~1h TTL)
* - L2 Database: Persistent storage via EntityManager
*
* Write-Through Strategy:
* - Writes go to both cache and database
* - Reads from cache, fallback to database
* - Cache invalidation on updates
*/
final class DatabaseStateManager implements StateManager
{
private int $cacheHits = 0;
private int $cacheMisses = 0;
private int $dbReads = 0;
private int $dbWrites = 0;
public function __construct(
private readonly EntityManager $entityManager,
private readonly Cache $cache,
private readonly string $stateClass,
private readonly ?Logger $logger = null,
private readonly Duration $cacheTtl = new Duration(3600), // 1 hour
) {}
public function getState(string $key): mixed
{
// Try cache first (L1)
$cacheKey = $this->getCacheKey($key);
$cached = $this->cache->get($cacheKey);
if ($cached->isHit) {
$this->cacheHits++;
$this->log('debug', "Cache hit for key: {$key}");
// Deserialize from cache
return $this->deserializeState($cached->value);
}
$this->cacheMisses++;
$this->log('debug', "Cache miss for key: {$key}, falling back to database");
// Fallback to database (L2) via EntityManager
$entity = $this->entityManager->find(ComponentStateEntity::class, $key);
if ($entity === null) {
return null;
}
$this->dbReads++;
// Populate cache for next access
$state = $this->deserializeState($entity->stateData);
$this->cache->set($cacheKey, $entity->stateData, $this->cacheTtl);
return $state;
}
public function setState(string $key, mixed $state, ?Duration $ttl = null): void
{
if (!$state instanceof SerializableState) {
throw new \InvalidArgumentException("State must implement SerializableState");
}
// Serialize state
$stateData = $this->serializeState($state);
$checksum = $this->calculateChecksum($stateData);
$expiresAt = $ttl ? Timestamp::now()->add($ttl) : null;
// Load existing entity or create new
$existing = $this->entityManager->find(ComponentStateEntity::class, $key);
if ($existing === null) {
// Create new entity
$entity = new ComponentStateEntity(
componentId: $key,
stateData: $stateData,
stateClass: $this->stateClass,
componentName: $this->extractComponentName($key),
userId: null, // Could be set by context
sessionId: null,
version: 1,
checksum: $checksum,
createdAt: Timestamp::now(),
updatedAt: Timestamp::now(),
expiresAt: $expiresAt,
);
// Persist via EntityManager
$this->entityManager->unitOfWork->persist($entity);
$this->entityManager->unitOfWork->commit();
} else {
// Update existing entity
$updated = $existing->withUpdatedState($stateData, $checksum, $expiresAt);
// Persist updated entity
$this->entityManager->unitOfWork->persist($updated);
$this->entityManager->unitOfWork->commit();
}
// Write-through to cache
$cacheKey = $this->getCacheKey($key);
$this->cache->set($cacheKey, $stateData, $ttl ?? $this->cacheTtl);
$this->dbWrites++;
$this->log('debug', "State saved for key: {$key}");
}
public function hasState(string $key): bool
{
// Check cache first
$cacheKey = $this->getCacheKey($key);
if ($this->cache->get($cacheKey)->isHit) {
return true;
}
// Check database via EntityManager
return $this->entityManager->find(ComponentStateEntity::class, $key) !== null;
}
public function removeState(string $key): void
{
// Delete from database via EntityManager
$entity = $this->entityManager->find(ComponentStateEntity::class, $key);
if ($entity !== null) {
$this->entityManager->unitOfWork->remove($entity);
$this->entityManager->unitOfWork->commit();
}
// Invalidate cache
$cacheKey = $this->getCacheKey($key);
$this->cache->forget($cacheKey);
$this->log('debug', "State removed for key: {$key}");
}
public function updateState(string $key, callable $updater, ?Duration $ttl = null): mixed
{
// Get current state
$currentState = $this->getState($key);
// Apply updater
$newState = $updater($currentState);
// Save updated state
$this->setState($key, $newState, $ttl);
return $newState;
}
public function getAllStates(): array
{
// Use EntityManager to fetch all states
$entities = $this->entityManager->findBy(
ComponentStateEntity::class,
['state_class' => $this->stateClass]
);
$states = [];
foreach ($entities as $entity) {
$states[$entity->componentId] = $this->deserializeState($entity->stateData);
}
return $states;
}
public function clearAll(): void
{
// Delete all states via EntityManager
$entities = $this->entityManager->findBy(
ComponentStateEntity::class,
['state_class' => $this->stateClass]
);
foreach ($entities as $entity) {
$this->entityManager->unitOfWork->remove($entity);
}
$this->entityManager->unitOfWork->commit();
// Note: Cache clearing is limited by Cache interface
$this->log('warning', "clearAll() executed - cache may contain stale entries");
}
public function getStatistics(): StateManagerStatistics
{
// Count total keys via EntityManager
$entities = $this->entityManager->findBy(
ComponentStateEntity::class,
['state_class' => $this->stateClass]
);
$totalKeys = count($entities);
$totalRequests = $this->cacheHits + $this->cacheMisses;
return new StateManagerStatistics(
totalKeys: $totalKeys,
hitCount: $this->cacheHits,
missCount: $this->cacheMisses,
setCount: $this->dbWrites,
removeCount: 0,
updateCount: 0,
averageSetTime: Duration::zero(),
averageGetTime: Duration::zero(),
expiredKeys: 0,
memoryUsage: Byte::fromBytes(0),
);
}
/**
* Serialize state to string
*/
private function serializeState(SerializableState $state): string
{
// Note: Encryption should be handled by StateTransformer pipeline
return json_encode($state->toArray());
}
/**
* Deserialize state from string
*/
private function deserializeState(string $data): SerializableState
{
// Note: Decryption should be handled by StateTransformer pipeline
$array = json_decode($data, true);
return call_user_func([$this->stateClass, 'fromArray'], $array);
}
/**
* Calculate checksum for integrity verification
*/
private function calculateChecksum(string $data): string
{
return hash('sha256', $data);
}
/**
* Extract component name from component ID
*/
private function extractComponentName(string $componentId): string
{
// ComponentId format: "{componentName}_{uniqueId}"
$parts = explode('_', $componentId, 2);
return $parts[0] ?? 'unknown';
}
/**
* Get cache key for component ID
*/
private function getCacheKey(string $key): CacheKey
{
return CacheKey::from("component_state:{$key}");
}
/**
* Log message if logger available
*/
private function log(string $level, string $message, array $context = []): void
{
if ($this->logger === null) {
return;
}
$context['state_class'] = $this->stateClass;
$context['cache_hits'] = $this->cacheHits;
$context['cache_misses'] = $this->cacheMisses;
$context['db_reads'] = $this->dbReads;
$context['db_writes'] = $this->dbWrites;
match ($level) {
'debug' => $this->logger->debug($message, $context),
'info' => $this->logger->info($message, $context),
'warning' => $this->logger->warning($message, $context),
'error' => $this->logger->error($message, $context),
default => $this->logger->info($message, $context),
};
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Framework\StateManagement\Database;
/**
* State Change Type Enum
*
* Represents the type of state change in history.
*/
enum StateChangeType: string
{
case CREATED = 'created';
case UPDATED = 'updated';
case DELETED = 'deleted';
}

View File

@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace App\Framework\StateManagement\Database;
use App\Framework\Attributes\Entity;
use App\Framework\Attributes\Id;
use App\Framework\Attributes\Column;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* State History Entry Entity
*
* Represents a historical snapshot of component state change.
* EntityManager-compatible entity for audit trails and debugging.
*/
#[Entity(table: 'component_state_history')]
final readonly class StateHistoryEntry
{
public function __construct(
#[Id]
#[Column(name: 'id', type: 'integer')]
public int $id,
#[Column(name: 'component_id')]
public string $componentId,
#[Column(name: 'state_data', type: 'text')]
public string $stateData, // Encrypted state snapshot
#[Column(name: 'state_class')]
public string $stateClass,
#[Column(name: 'version', type: 'integer')]
public int $version,
#[Column(name: 'change_type')]
public StateChangeType $changeType,
#[Column(name: 'changed_properties', type: 'json')]
public ?array $changedProperties, // Which properties changed
#[Column(name: 'user_id')]
public ?string $userId,
#[Column(name: 'session_id')]
public ?string $sessionId,
#[Column(name: 'ip_address')]
public ?string $ipAddress,
#[Column(name: 'user_agent', type: 'text')]
public ?string $userAgent,
#[Column(name: 'previous_checksum')]
public ?string $previousChecksum,
#[Column(name: 'current_checksum')]
public string $currentChecksum,
#[Column(name: 'created_at', type: 'datetime')]
public Timestamp $createdAt,
) {}
/**
* Create from database row
*/
public static function fromRow(array $row): self
{
return new self(
id: (int) $row['id'],
componentId: $row['component_id'],
stateData: $row['state_data'],
stateClass: $row['state_class'],
version: (int) $row['version'],
changeType: StateChangeType::from($row['change_type']),
changedProperties: isset($row['changed_properties'])
? json_decode($row['changed_properties'], true)
: null,
userId: $row['user_id'] ?? null,
sessionId: $row['session_id'] ?? null,
ipAddress: $row['ip_address'] ?? null,
userAgent: $row['user_agent'] ?? null,
previousChecksum: $row['previous_checksum'] ?? null,
currentChecksum: $row['current_checksum'],
createdAt: Timestamp::fromString($row['created_at']),
);
}
/**
* Convert to database row
*/
public function toRow(): array
{
return [
'component_id' => $this->componentId,
'state_data' => $this->stateData,
'state_class' => $this->stateClass,
'version' => $this->version,
'change_type' => $this->changeType->value,
'changed_properties' => $this->changedProperties ? json_encode($this->changedProperties) : null,
'user_id' => $this->userId,
'session_id' => $this->sessionId,
'ip_address' => $this->ipAddress,
'user_agent' => $this->userAgent,
'previous_checksum' => $this->previousChecksum,
'current_checksum' => $this->currentChecksum,
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
];
}
/**
* Check if this is a creation event
*/
public function isCreation(): bool
{
return $this->changeType === StateChangeType::CREATED;
}
/**
* Check if this is an update event
*/
public function isUpdate(): bool
{
return $this->changeType === StateChangeType::UPDATED;
}
/**
* Check if this is a deletion event
*/
public function isDeletion(): bool
{
return $this->changeType === StateChangeType::DELETED;
}
}

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace App\Framework\StateManagement;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\StateManagement\Database\StateHistoryEntry;
/**
* State History Manager Interface
*
* Manages historical snapshots of component state changes.
* Used for audit trails, debugging, and state recovery.
*/
interface StateHistoryManager
{
/**
* Add a history entry for a state change
*
* @param string $componentId Component identifier
* @param string $stateData Serialized state data
* @param string $stateClass State class name
* @param int $version State version number
* @param string $changeType Type of change (created/updated/deleted)
* @param array $context Additional context (user_id, session_id, ip_address, user_agent, etc.)
* @param array|null $changedProperties List of changed property names
* @param string|null $previousChecksum Checksum before change
* @param string $currentChecksum Checksum after change
*/
public function addHistoryEntry(
string $componentId,
string $stateData,
string $stateClass,
int $version,
string $changeType,
array $context = [],
?array $changedProperties = null,
?string $previousChecksum = null,
string $currentChecksum = ''
): void;
/**
* Get history for a component
*
* @param string $componentId Component identifier
* @param int $limit Maximum number of entries to return
* @param int $offset Offset for pagination
* @return array<StateHistoryEntry> History entries ordered by created_at DESC
*/
public function getHistory(string $componentId, int $limit = 100, int $offset = 0): array;
/**
* Get specific version of state from history
*
* @param string $componentId Component identifier
* @param int $version Version number
* @return StateHistoryEntry|null History entry or null if not found
*/
public function getHistoryByVersion(string $componentId, int $version): ?StateHistoryEntry;
/**
* Get history entries since a specific timestamp
*
* @param string $componentId Component identifier
* @param Timestamp $since Timestamp to start from
* @param int $limit Maximum number of entries
* @return array<StateHistoryEntry> History entries ordered by created_at ASC
*/
public function getHistorySince(string $componentId, Timestamp $since, int $limit = 100): array;
/**
* Get history entries for a specific user
*
* @param string $userId User identifier
* @param int $limit Maximum number of entries
* @return array<StateHistoryEntry> History entries ordered by created_at DESC
*/
public function getHistoryByUser(string $userId, int $limit = 100): array;
/**
* Cleanup old history entries for a component
*
* Keep only the last N entries, delete older ones.
*
* @param string $componentId Component identifier
* @param int $keepLast Number of entries to keep
* @return int Number of entries deleted
*/
public function cleanup(string $componentId, int $keepLast): int;
/**
* Cleanup all history entries older than a specific timestamp
*
* @param Timestamp $olderThan Delete entries older than this timestamp
* @return int Number of entries deleted
*/
public function cleanupOlderThan(Timestamp $olderThan): int;
/**
* Delete all history for a component
*
* @param string $componentId Component identifier
* @return int Number of entries deleted
*/
public function deleteHistory(string $componentId): int;
/**
* Check if history tracking is enabled for a component
*
* @param string $componentClass Component class name
* @return bool True if component has #[TrackStateHistory] attribute
*/
public function isHistoryEnabled(string $componentClass): bool;
/**
* Get statistics about history storage
*
* @return array{total_entries: int, total_components: int, oldest_entry: ?Timestamp, newest_entry: ?Timestamp}
*/
public function getStatistics(): array;
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Framework\StateManagement;
use App\Framework\Attributes\Initializer;
use App\Framework\Cache\Cache;
use App\Framework\Database\EntityManager;
use App\Framework\DI\Container;
use App\Framework\Logging\Logger;
use App\Framework\StateManagement\Database\DatabaseStateHistoryManager;
use App\Framework\StateManagement\Database\DatabaseStateManager;
/**
* State Management System Initializer
*
* Registers state management services in DI container:
* - DatabaseStateManager for persistent state storage
* - DatabaseStateHistoryManager for audit trails
* - LiveComponentStateManager wrapper (application layer)
*/
final readonly class StateManagementInitializer
{
public function __construct(
private EntityManager $entityManager,
private Cache $cache,
private ?Logger $logger = null,
) {}
#[Initializer]
public function initialize(Container $container): void
{
// Register StateHistoryManager
$historyManager = new DatabaseStateHistoryManager(
entityManager: $this->entityManager,
logger: $this->logger
);
$container->singleton(StateHistoryManager::class, $historyManager);
// Register StateManager with database backend
// Note: The stateClass will be set by LiveComponentStateManager
// This is a generic StateManager for framework-wide use
$stateManager = new DatabaseStateManager(
entityManager: $this->entityManager,
cache: $this->cache,
stateClass: '', // Will be overridden per component
logger: $this->logger
);
$container->singleton(StateManager::class, $stateManager);
}
/**
* Create StateManager for specific state class
*
* Factory method for creating state managers with specific state class configuration.
* Useful for components that need typed state managers.
*
* @param string $stateClass Fully qualified state class name
* @return StateManager State manager configured for specific state class
*/
public function createForStateClass(string $stateClass): StateManager
{
return new DatabaseStateManager(
entityManager: $this->entityManager,
cache: $this->cache,
stateClass: $stateClass,
logger: $this->logger
);
}
}