docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\Wrappers;
use App\Framework\Queue\Queue;
use App\Framework\Queue\ValueObjects\JobPayload;
use App\Framework\Queue\ValueObjects\QueuePriority;
use App\Framework\Queue\ValueObjects\JobMetadata;
use App\Framework\Queue\RetryStrategyHelper;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Retry\RetryStrategy;
/**
* Command Queue Wrapper
*
* Specialized queue wrapper for command processing with type-safe operations
*/
final readonly class CommandQueue
{
public function __construct(
private Queue $queue
) {}
/**
* Push any object as a command
*
* @param object $command Any object to be processed as a command
* @param QueuePriority|null $priority Command priority (defaults to normal)
* @param Duration|null $delay Delay before processing (defaults to immediate)
* @param Duration|null $timeout Maximum processing time
* @param RetryStrategy|null $retryStrategy Custom retry strategy
*/
public function pushCommand(
object $command,
?QueuePriority $priority = null,
?Duration $delay = null,
?Duration $timeout = null,
?RetryStrategy $retryStrategy = null
): void {
$payload = JobPayload::create(
job: $command,
priority: $priority ?? QueuePriority::normal(),
delay: $delay ?? Duration::zero(),
timeout: $timeout ?? Duration::fromSeconds(30),
retryStrategy: $retryStrategy ?? RetryStrategyHelper::forCommands(),
metadata: JobMetadata::forCommand($command)
);
$this->queue->push($payload);
}
/**
* Push a high priority command (jumps ahead in queue)
*/
public function pushHighPriorityCommand(object $command): void
{
$this->pushCommand(
command: $command,
priority: QueuePriority::high(),
timeout: Duration::fromSeconds(15) // Shorter timeout for high priority
);
}
/**
* Push a critical command (highest priority, immediate processing)
*/
public function pushCriticalCommand(object $command): void
{
$this->pushCommand(
command: $command,
priority: QueuePriority::critical(),
timeout: Duration::fromSeconds(10),
retryStrategy: RetryStrategyHelper::none() // No retries for critical
);
}
/**
* Push a background command (low priority, longer timeout)
*/
public function pushBackgroundCommand(object $command): void
{
$this->pushCommand(
command: $command,
priority: QueuePriority::low(),
timeout: Duration::fromMinutes(5),
retryStrategy: RetryStrategyHelper::forGeneral()
);
}
/**
* Push a delayed command (scheduled for future processing)
*/
public function pushDelayedCommand(object $command, Duration $delay): void
{
$this->pushCommand(
command: $command,
delay: $delay,
priority: QueuePriority::normal()
);
}
/**
* Pop the next command from the queue
*
* @return object|null The next command object or null if queue is empty
*/
public function popCommand(): ?object
{
$payload = $this->queue->pop();
return $payload?->job;
}
/**
* Peek at the next command without removing it
*
* @return object|null The next command object or null if queue is empty
*/
public function peekCommand(): ?object
{
$payload = $this->queue->peek();
return $payload?->job;
}
/**
* Pop the next command with its metadata
*
* @return JobPayload|null The complete job payload or null if queue is empty
*/
public function popCommandWithMetadata(): ?JobPayload
{
return $this->queue->pop();
}
/**
* Get number of pending commands
*/
public function size(): int
{
return $this->queue->size();
}
/**
* Check if queue is empty
*/
public function isEmpty(): bool
{
return $this->size() === 0;
}
/**
* Clear all commands from queue
*
* @return int Number of commands removed
*/
public function clear(): int
{
return $this->queue->clear();
}
/**
* Get command queue statistics
*/
public function getStats(): array
{
$stats = $this->queue->getStats();
return [
'type' => 'command',
'size' => $this->size(),
'is_empty' => $this->isEmpty(),
...$stats
];
}
/**
* Batch push multiple commands
*
* @param array<object> $commands Array of command objects
* @param QueuePriority|null $priority Priority for all commands
*/
public function pushBatch(array $commands, ?QueuePriority $priority = null): void
{
foreach ($commands as $command) {
$this->pushCommand($command, $priority);
}
}
/**
* Pop multiple commands in batch
*
* @param int $count Maximum number of commands to pop
* @return array<object> Array of command objects
*/
public function popBatch(int $count): array
{
$commands = [];
for ($i = 0; $i < $count; $i++) {
$command = $this->popCommand();
if ($command === null) {
break;
}
$commands[] = $command;
}
return $commands;
}
}

View File

@@ -0,0 +1,419 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\Wrappers;
use App\Framework\Queue\Queue;
use App\Framework\Queue\ValueObjects\JobPayload;
use App\Framework\Queue\ValueObjects\QueuePriority;
use App\Framework\Queue\ValueObjects\JobMetadata;
use App\Framework\Queue\RetryStrategyHelper;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Retry\RetryStrategy;
/**
* Email Queue Wrapper
*
* Specialized queue wrapper for email processing with type-safe operations
* Optimized for email delivery, notifications, marketing emails, and system emails
*/
final readonly class EmailQueue
{
public function __construct(
private Queue $queue
) {}
/**
* Push any object as an email job
*
* @param object $email Any email object to be processed
* @param QueuePriority|null $priority Email priority (defaults to normal)
* @param Duration|null $delay Delay before processing (defaults to immediate)
* @param Duration|null $timeout Maximum processing time
* @param RetryStrategy|null $retryStrategy Custom retry strategy
*/
public function pushEmail(
object $email,
?QueuePriority $priority = null,
?Duration $delay = null,
?Duration $timeout = null,
?RetryStrategy $retryStrategy = null
): void {
$payload = JobPayload::create(
job: $email,
priority: $priority ?? QueuePriority::normal(),
delay: $delay ?? Duration::zero(),
timeout: $timeout ?? Duration::fromMinutes(5),
retryStrategy: $retryStrategy ?? RetryStrategyHelper::forEmails(),
metadata: JobMetadata::forEmail($email)
);
$this->queue->push($payload);
}
/**
* Push a transactional email (high priority, immediate delivery)
*/
public function pushTransactionalEmail(object $email): void
{
$this->pushEmail(
email: $email,
priority: QueuePriority::high(),
timeout: Duration::fromMinutes(2),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Push a notification email (normal priority, moderate retry)
*/
public function pushNotificationEmail(object $email): void
{
$this->pushEmail(
email: $email,
priority: QueuePriority::normal(),
timeout: Duration::fromMinutes(5),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Push a marketing email (low priority, aggressive retry)
*/
public function pushMarketingEmail(object $email): void
{
$this->pushEmail(
email: $email,
priority: QueuePriority::low(),
timeout: Duration::fromMinutes(10),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Push a system email (high priority for admin notifications)
*/
public function pushSystemEmail(object $email): void
{
$this->pushEmail(
email: $email,
priority: QueuePriority::high(),
timeout: Duration::fromMinutes(3),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Push a bulk email (background processing with batching)
*/
public function pushBulkEmail(object $email): void
{
$this->pushEmail(
email: $email,
priority: QueuePriority::low(),
timeout: Duration::fromMinutes(15),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Push a scheduled email (delayed delivery)
*/
public function pushScheduledEmail(object $email, Duration $delay): void
{
$this->pushEmail(
email: $email,
delay: $delay,
priority: QueuePriority::normal(),
timeout: Duration::fromMinutes(5),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Push a critical email (immediate processing, no delays)
*/
public function pushCriticalEmail(object $email): void
{
$this->pushEmail(
email: $email,
priority: QueuePriority::critical(),
timeout: Duration::fromMinutes(1),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Push a reminder email (moderate priority, future delivery)
*/
public function pushReminderEmail(object $email, Duration $delay): void
{
$this->pushEmail(
email: $email,
delay: $delay,
priority: QueuePriority::normal(),
timeout: Duration::fromMinutes(3),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Pop the next email from the queue
*
* @return object|null The next email object or null if queue is empty
*/
public function popEmail(): ?object
{
$payload = $this->queue->pop();
return $payload?->job;
}
/**
* Peek at the next email without removing it
*
* @return object|null The next email object or null if queue is empty
*/
public function peekEmail(): ?object
{
$payload = $this->queue->peek();
return $payload?->job;
}
/**
* Pop the next email with its metadata
*
* @return JobPayload|null The complete job payload or null if queue is empty
*/
public function popEmailWithMetadata(): ?JobPayload
{
return $this->queue->pop();
}
/**
* Get number of pending emails
*/
public function size(): int
{
return $this->queue->size();
}
/**
* Check if queue is empty
*/
public function isEmpty(): bool
{
return $this->size() === 0;
}
/**
* Clear all emails from queue
*
* @return int Number of emails removed
*/
public function clear(): int
{
return $this->queue->clear();
}
/**
* Get email queue statistics
*/
public function getStats(): array
{
$stats = $this->queue->getStats();
return [
'type' => 'email',
'size' => $this->size(),
'is_empty' => $this->isEmpty(),
...$stats
];
}
/**
* Batch push multiple emails
*
* @param array<object> $emails Array of email objects
* @param QueuePriority|null $priority Priority for all emails
*/
public function pushBatch(array $emails, ?QueuePriority $priority = null): void
{
foreach ($emails as $email) {
$this->pushEmail($email, $priority);
}
}
/**
* Push emails with different priorities based on email type
*
* @param array<object> $emails Array of email objects
*/
public function pushSmartBatch(array $emails): void
{
foreach ($emails as $email) {
$this->pushEmailByType($email);
}
}
/**
* Pop multiple emails in batch
*
* @param int $count Maximum number of emails to pop
* @return array<object> Array of email objects
*/
public function popBatch(int $count): array
{
$emails = [];
for ($i = 0; $i < $count; $i++) {
$email = $this->popEmail();
if ($email === null) {
break;
}
$emails[] = $email;
}
return $emails;
}
/**
* Filter emails by type/class
*
* @param string $emailClass The email class to filter by
* @return array<object> Emails of the specified type
*/
public function getEmailsByType(string $emailClass): array
{
$emails = [];
$tempEmails = [];
// Pop all emails temporarily
while (!$this->isEmpty()) {
$payload = $this->popEmailWithMetadata();
if ($payload === null) {
break;
}
if ($payload->job instanceof $emailClass) {
$emails[] = $payload->job;
} else {
$tempEmails[] = $payload;
}
}
// Push back non-matching emails
foreach ($tempEmails as $payload) {
$this->queue->push($payload);
}
return $emails;
}
/**
* Get emails count by priority
*
* @return array<string, int> Priority name => count mapping
*/
public function getEmailCountByPriority(): array
{
$stats = $this->getStats();
return $stats['priority_breakdown'] ?? [];
}
/**
* Get emails scheduled for future delivery
*
* @return array<object> Emails with future delivery times
*/
public function getScheduledEmails(): array
{
$scheduledEmails = [];
$tempEmails = [];
// Pop all emails temporarily
while (!$this->isEmpty()) {
$payload = $this->popEmailWithMetadata();
if ($payload === null) {
break;
}
if ($payload->isDelayed()) {
$scheduledEmails[] = $payload->job;
}
$tempEmails[] = $payload;
}
// Push back all emails
foreach ($tempEmails as $payload) {
$this->queue->push($payload);
}
return $scheduledEmails;
}
/**
* Get count of emails by delivery status
*
* @return array<string, int> Status counts
*/
public function getDeliveryStatusCounts(): array
{
return [
'pending' => $this->size(),
'scheduled' => count($this->getScheduledEmails()),
'immediate' => $this->size() - count($this->getScheduledEmails())
];
}
/**
* Bulk push emails with rate limiting awareness
*
* @param array<object> $emails Array of email objects
* @param Duration $interval Interval between email processing
*/
public function pushBulkWithRateLimit(array $emails, Duration $interval): void
{
$delay = Duration::zero();
foreach ($emails as $email) {
$this->pushEmail(
email: $email,
delay: $delay,
priority: QueuePriority::low(),
timeout: Duration::fromMinutes(10)
);
$delay = $delay->add($interval);
}
}
/**
* Push email with automatic priority based on email type
*/
private function pushEmailByType(object $email): void
{
$className = get_class($email);
// Automatic priority assignment based on email name patterns
if (str_contains($className, 'Transactional') || str_contains($className, 'Receipt') || str_contains($className, 'Invoice')) {
$this->pushTransactionalEmail($email);
} elseif (str_contains($className, 'Marketing') || str_contains($className, 'Newsletter') || str_contains($className, 'Campaign')) {
$this->pushMarketingEmail($email);
} elseif (str_contains($className, 'System') || str_contains($className, 'Admin') || str_contains($className, 'Alert')) {
$this->pushSystemEmail($email);
} elseif (str_contains($className, 'Bulk') || str_contains($className, 'Batch')) {
$this->pushBulkEmail($email);
} elseif (str_contains($className, 'Critical') || str_contains($className, 'Emergency')) {
$this->pushCriticalEmail($email);
} elseif (str_contains($className, 'Reminder') || str_contains($className, 'Follow')) {
$this->pushReminderEmail($email, Duration::fromHours(1)); // Default 1 hour delay
} else {
// Default to notification email
$this->pushNotificationEmail($email);
}
}
}

View File

@@ -0,0 +1,321 @@
<?php
declare(strict_types=1);
namespace App\Framework\Queue\Wrappers;
use App\Framework\Queue\Queue;
use App\Framework\Queue\ValueObjects\JobPayload;
use App\Framework\Queue\ValueObjects\QueuePriority;
use App\Framework\Queue\ValueObjects\JobMetadata;
use App\Framework\Queue\RetryStrategyHelper;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Retry\RetryStrategy;
/**
* Event Queue Wrapper
*
* Specialized queue wrapper for event processing with type-safe operations
* Optimized for domain events, system events, and integration events
*/
final readonly class EventQueue
{
public function __construct(
private Queue $queue
) {}
/**
* Push any object as an event
*
* @param object $event Any event object to be processed
* @param QueuePriority|null $priority Event priority (defaults to normal)
* @param Duration|null $delay Delay before processing (defaults to immediate)
* @param Duration|null $timeout Maximum processing time
* @param RetryStrategy|null $retryStrategy Custom retry strategy
*/
public function pushEvent(
object $event,
?QueuePriority $priority = null,
?Duration $delay = null,
?Duration $timeout = null,
?RetryStrategy $retryStrategy = null
): void {
$payload = JobPayload::create(
job: $event,
priority: $priority ?? QueuePriority::normal(),
delay: $delay ?? Duration::zero(),
timeout: $timeout ?? Duration::fromMinutes(2),
retryStrategy: $retryStrategy ?? RetryStrategyHelper::forEvents(),
metadata: JobMetadata::forEvent($event)
);
$this->queue->push($payload);
}
/**
* Push a domain event (high priority for business logic)
*/
public function pushDomainEvent(object $event): void
{
$this->pushEvent(
event: $event,
priority: QueuePriority::high(),
timeout: Duration::fromMinutes(1),
retryStrategy: RetryStrategyHelper::forEvents()
);
}
/**
* Push a system event (normal priority for system operations)
*/
public function pushSystemEvent(object $event): void
{
$this->pushEvent(
event: $event,
priority: QueuePriority::normal(),
timeout: Duration::fromMinutes(3),
retryStrategy: RetryStrategyHelper::forEvents()
);
}
/**
* Push an integration event (low priority, high retry tolerance)
*/
public function pushIntegrationEvent(object $event): void
{
$this->pushEvent(
event: $event,
priority: QueuePriority::low(),
timeout: Duration::fromMinutes(10),
retryStrategy: RetryStrategyHelper::forWebhooks()
);
}
/**
* Push a notification event (background processing)
*/
public function pushNotificationEvent(object $event): void
{
$this->pushEvent(
event: $event,
priority: QueuePriority::low(),
timeout: Duration::fromMinutes(5),
retryStrategy: RetryStrategyHelper::forEmails()
);
}
/**
* Push a delayed event (scheduled for future processing)
*/
public function pushDelayedEvent(object $event, Duration $delay): void
{
$this->pushEvent(
event: $event,
delay: $delay,
priority: QueuePriority::normal(),
timeout: Duration::fromMinutes(2)
);
}
/**
* Push a critical event (immediate processing, no retries)
*/
public function pushCriticalEvent(object $event): void
{
$this->pushEvent(
event: $event,
priority: QueuePriority::critical(),
timeout: Duration::fromSeconds(30),
retryStrategy: RetryStrategyHelper::none()
);
}
/**
* Pop the next event from the queue
*
* @return object|null The next event object or null if queue is empty
*/
public function popEvent(): ?object
{
$payload = $this->queue->pop();
return $payload?->job;
}
/**
* Peek at the next event without removing it
*
* @return object|null The next event object or null if queue is empty
*/
public function peekEvent(): ?object
{
$payload = $this->queue->peek();
return $payload?->job;
}
/**
* Pop the next event with its metadata
*
* @return JobPayload|null The complete job payload or null if queue is empty
*/
public function popEventWithMetadata(): ?JobPayload
{
return $this->queue->pop();
}
/**
* Get number of pending events
*/
public function size(): int
{
return $this->queue->size();
}
/**
* Check if queue is empty
*/
public function isEmpty(): bool
{
return $this->size() === 0;
}
/**
* Clear all events from queue
*
* @return int Number of events removed
*/
public function clear(): int
{
return $this->queue->clear();
}
/**
* Get event queue statistics
*/
public function getStats(): array
{
$stats = $this->queue->getStats();
return [
'type' => 'event',
'size' => $this->size(),
'is_empty' => $this->isEmpty(),
...$stats
];
}
/**
* Batch push multiple events
*
* @param array<object> $events Array of event objects
* @param QueuePriority|null $priority Priority for all events
*/
public function pushBatch(array $events, ?QueuePriority $priority = null): void
{
foreach ($events as $event) {
$this->pushEvent($event, $priority);
}
}
/**
* Push events with different priorities based on event type
*
* @param array<object> $events Array of event objects
*/
public function pushSmartBatch(array $events): void
{
foreach ($events as $event) {
$this->pushEventByType($event);
}
}
/**
* Pop multiple events in batch
*
* @param int $count Maximum number of events to pop
* @return array<object> Array of event objects
*/
public function popBatch(int $count): array
{
$events = [];
for ($i = 0; $i < $count; $i++) {
$event = $this->popEvent();
if ($event === null) {
break;
}
$events[] = $event;
}
return $events;
}
/**
* Filter events by type/class
*
* @param string $eventClass The event class to filter by
* @return array<object> Events of the specified type
*/
public function getEventsByType(string $eventClass): array
{
$events = [];
$tempEvents = [];
// Pop all events temporarily
while (!$this->isEmpty()) {
$payload = $this->popEventWithMetadata();
if ($payload === null) {
break;
}
if ($payload->job instanceof $eventClass) {
$events[] = $payload->job;
} else {
$tempEvents[] = $payload;
}
}
// Push back non-matching events
foreach ($tempEvents as $payload) {
$this->queue->push($payload);
}
return $events;
}
/**
* Get events count by priority
*
* @return array<string, int> Priority name => count mapping
*/
public function getEventCountByPriority(): array
{
$stats = $this->getStats();
return $stats['priority_breakdown'] ?? [];
}
/**
* Push event with automatic priority based on event type
*/
private function pushEventByType(object $event): void
{
$className = get_class($event);
// Automatic priority assignment based on event name patterns
if (str_contains($className, 'Domain') || str_contains($className, 'Business')) {
$this->pushDomainEvent($event);
} elseif (str_contains($className, 'System') || str_contains($className, 'Application')) {
$this->pushSystemEvent($event);
} elseif (str_contains($className, 'Integration') || str_contains($className, 'External')) {
$this->pushIntegrationEvent($event);
} elseif (str_contains($className, 'Notification') || str_contains($className, 'Email')) {
$this->pushNotificationEvent($event);
} elseif (str_contains($className, 'Critical') || str_contains($className, 'Alert')) {
$this->pushCriticalEvent($event);
} else {
// Default to system event
$this->pushSystemEvent($event);
}
}
}