- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
424 lines
12 KiB
PHP
424 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Queue\Wrappers;
|
|
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Queue\Queue;
|
|
use App\Framework\Queue\RetryStrategyHelper;
|
|
use App\Framework\Queue\ValueObjects\JobMetadata;
|
|
use App\Framework\Queue\ValueObjects\JobPayload;
|
|
use App\Framework\Queue\ValueObjects\QueuePriority;
|
|
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);
|
|
}
|
|
}
|
|
}
|