Files
michaelschiemer/src/Framework/Notification/Notification.php
Michael Schiemer 3b623e7afb feat(Deployment): Integrate Ansible deployment via PHP deployment pipeline
- Create AnsibleDeployStage using framework's Process module for secure command execution
- Integrate AnsibleDeployStage into DeploymentPipelineCommands for production deployments
- Add force_deploy flag support in Ansible playbook to override stale locks
- Use PHP deployment module as orchestrator (php console.php deploy:production)
- Fix ErrorAggregationInitializer to use Environment class instead of $_ENV superglobal

Architecture:
- BuildStage → AnsibleDeployStage → HealthCheckStage for production
- Process module provides timeout, error handling, and output capture
- Ansible playbook supports rollback via rollback-git-based.yml
- Zero-downtime deployments with health checks
2025-10-26 14:08:07 +01:00

272 lines
8.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Notification;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\ValueObjects\NotificationId;
use App\Framework\Notification\ValueObjects\NotificationPriority;
use App\Framework\Notification\ValueObjects\NotificationStatus;
use App\Framework\Notification\ValueObjects\NotificationTypeInterface;
/**
* Core notification entity
*
* Immutable value object representing a notification
*/
final readonly class Notification
{
/**
* @param NotificationId $id Unique notification identifier
* @param string $recipientId User/Entity receiving the notification
* @param NotificationTypeInterface $type Notification category
* @param string $title Notification title
* @param string $body Notification message body
* @param Timestamp $createdAt Creation timestamp
* @param array<string, mixed> $data Additional structured data
* @param array<NotificationChannel> $channels Delivery channels
* @param NotificationPriority $priority Delivery priority
* @param NotificationStatus $status Current status
* @param Timestamp|null $sentAt Delivery timestamp
* @param Timestamp|null $readAt Read timestamp
* @param string|null $actionUrl Optional action URL
* @param string|null $actionLabel Optional action button label
*/
public function __construct(
public NotificationId $id,
public string $recipientId,
public NotificationTypeInterface $type,
public string $title,
public string $body,
public Timestamp $createdAt,
public array $data = [],
public array $channels = [],
public NotificationPriority $priority = NotificationPriority::NORMAL,
public NotificationStatus $status = NotificationStatus::PENDING,
public ?Timestamp $sentAt = null,
public ?Timestamp $readAt = null,
public ?string $actionUrl = null,
public ?string $actionLabel = null
) {
if (empty($recipientId)) {
throw new \InvalidArgumentException('Recipient ID cannot be empty');
}
if (empty($title)) {
throw new \InvalidArgumentException('Title cannot be empty');
}
if (empty($body)) {
throw new \InvalidArgumentException('Body cannot be empty');
}
if (empty($channels)) {
throw new \InvalidArgumentException('At least one delivery channel is required');
}
}
public static function create(
string $recipientId,
NotificationTypeInterface $type,
string $title,
string $body,
NotificationChannel ...$channels
): self {
return new self(
id: NotificationId::generate(),
recipientId: $recipientId,
type: $type,
title: $title,
body: $body,
createdAt: Timestamp::now(),
data: [],
channels: $channels,
priority: NotificationPriority::NORMAL,
status: NotificationStatus::PENDING
);
}
public function withData(array $data): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: [...$this->data, ...$data],
channels: $this->channels,
priority: $this->priority,
status: $this->status,
sentAt: $this->sentAt,
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function withPriority(NotificationPriority $priority): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $priority,
status: $this->status,
sentAt: $this->sentAt,
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function withAction(string $url, string $label): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: $this->status,
sentAt: $this->sentAt,
readAt: $this->readAt,
actionUrl: $url,
actionLabel: $label
);
}
public function markAsSent(): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: NotificationStatus::SENT,
sentAt: Timestamp::now(),
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function markAsDelivered(): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: NotificationStatus::DELIVERED,
sentAt: $this->sentAt ?? Timestamp::now(),
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function markAsRead(): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: NotificationStatus::READ,
sentAt: $this->sentAt,
readAt: Timestamp::now(),
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function markAsFailed(): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: NotificationStatus::FAILED,
sentAt: $this->sentAt,
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function isRead(): bool
{
return $this->status === NotificationStatus::READ;
}
public function hasAction(): bool
{
return $this->actionUrl !== null;
}
public function supportsChannel(NotificationChannel $channel): bool
{
foreach ($this->channels as $supportedChannel) {
if ($supportedChannel === $channel) {
return true;
}
}
return false;
}
public function toArray(): array
{
return [
'id' => $this->id->toString(),
'recipient_id' => $this->recipientId,
'type' => $this->type->toString(),
'title' => $this->title,
'body' => $this->body,
'data' => $this->data,
'channels' => array_map(fn ($c) => $c->value, $this->channels),
'priority' => $this->priority->value,
'status' => $this->status->value,
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
'sent_at' => $this->sentAt?->format('Y-m-d H:i:s'),
'read_at' => $this->readAt?->format('Y-m-d H:i:s'),
'action_url' => $this->actionUrl,
'action_label' => $this->actionLabel,
];
}
}