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
This commit is contained in:
2025-10-26 14:08:07 +01:00
parent a90263d3be
commit 3b623e7afb
170 changed files with 19888 additions and 575 deletions

View File

@@ -0,0 +1,219 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use App\Framework\Notification\Notification;
use App\Framework\Notification\NotificationDispatcher;
use App\Framework\Notification\Dispatcher\DispatchStrategy;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\ValueObjects\NotificationPriority;
use App\Framework\Notification\ValueObjects\SystemNotificationType;
/**
* Multi-Channel Notification Dispatch Example
*
* Demonstrates the four dispatch strategies:
* - ALL: Send to all channels regardless of failures
* - FIRST_SUCCESS: Stop after first successful delivery
* - FALLBACK: Try next only if previous failed
* - ALL_OR_NONE: All must succeed or entire dispatch fails
*/
echo "=== Multi-Channel Notification Dispatch Examples ===\n\n";
// Setup (in real app, get from DI container)
$dispatcher = $container->get(NotificationDispatcher::class);
// Example 1: ALL Strategy - Send to all channels
echo "1. ALL Strategy - Send to all channels\n";
echo str_repeat("-", 50) . "\n";
$notification = Notification::create(
recipientId: 'user_123',
type: new SystemNotificationType('system.update'),
title: 'System Update Available',
body: 'A new system update is available. Please review and install.',
NotificationChannel::TELEGRAM,
NotificationChannel::EMAIL,
NotificationChannel::SMS
)->withPriority(NotificationPriority::NORMAL);
$result = $dispatcher->sendNow($notification, DispatchStrategy::ALL);
echo "Status: " . ($result->isSuccess() ? "✅ SUCCESS" : "❌ FAILURE") . "\n";
echo "Successful channels: " . count($result->getSuccessful()) . "\n";
echo "Failed channels: " . count($result->getFailed()) . "\n";
foreach ($result->getSuccessful() as $channelResult) {
echo "{$channelResult->channel->value}: " . json_encode($channelResult->metadata) . "\n";
}
foreach ($result->getFailed() as $channelResult) {
echo "{$channelResult->channel->value}: {$channelResult->errorMessage}\n";
}
echo "\n";
// Example 2: FIRST_SUCCESS Strategy - Stop after first success
echo "2. FIRST_SUCCESS Strategy - Quick delivery\n";
echo str_repeat("-", 50) . "\n";
$notification = Notification::create(
recipientId: 'user_456',
type: new SystemNotificationType('order.shipped'),
title: 'Your Order Has Shipped',
body: 'Your order #12345 has been shipped and is on its way!',
NotificationChannel::TELEGRAM, // Try Telegram first
NotificationChannel::EMAIL, // Then Email if Telegram fails
NotificationChannel::SMS // Then SMS if Email fails
)->withPriority(NotificationPriority::HIGH);
$result = $dispatcher->sendNow($notification, DispatchStrategy::FIRST_SUCCESS);
echo "Status: " . ($result->isSuccess() ? "✅ SUCCESS" : "❌ FAILURE") . "\n";
echo "Delivery stopped after first success\n";
echo "Channels attempted: " . (count($result->getSuccessful()) + count($result->getFailed())) . "\n";
foreach ($result->getSuccessful() as $channelResult) {
echo "{$channelResult->channel->value}: Delivered successfully\n";
}
foreach ($result->getFailed() as $channelResult) {
echo "{$channelResult->channel->value}: {$channelResult->errorMessage}\n";
}
echo "\n";
// Example 3: FALLBACK Strategy - Telegram -> Email -> SMS chain
echo "3. FALLBACK Strategy - Graceful degradation\n";
echo str_repeat("-", 50) . "\n";
$notification = Notification::create(
recipientId: 'user_789',
type: new SystemNotificationType('security.alert'),
title: 'Security Alert',
body: 'Unusual login activity detected on your account.',
NotificationChannel::TELEGRAM, // Primary: Telegram (instant)
NotificationChannel::EMAIL, // Fallback 1: Email (reliable)
NotificationChannel::SMS // Fallback 2: SMS (last resort)
)->withPriority(NotificationPriority::URGENT);
$result = $dispatcher->sendNow($notification, DispatchStrategy::FALLBACK);
echo "Status: " . ($result->isSuccess() ? "✅ SUCCESS" : "❌ FAILURE") . "\n";
echo "Fallback chain executed\n";
foreach ($result->getSuccessful() as $channelResult) {
echo "{$channelResult->channel->value}: Delivered (fallback stopped here)\n";
}
foreach ($result->getFailed() as $channelResult) {
echo "{$channelResult->channel->value}: Failed, tried next channel\n";
}
echo "\n";
// Example 4: ALL_OR_NONE Strategy - Critical notifications
echo "4. ALL_OR_NONE Strategy - All must succeed\n";
echo str_repeat("-", 50) . "\n";
$notification = Notification::create(
recipientId: 'user_101',
type: new SystemNotificationType('account.deleted'),
title: 'Account Deletion Confirmation',
body: 'Your account has been permanently deleted as requested.',
NotificationChannel::EMAIL,
NotificationChannel::SMS
)->withPriority(NotificationPriority::URGENT);
$result = $dispatcher->sendNow($notification, DispatchStrategy::ALL_OR_NONE);
echo "Status: " . ($result->isSuccess() ? "✅ ALL SUCCEEDED" : "❌ STOPPED ON FIRST FAILURE") . "\n";
echo "Successful channels: " . count($result->getSuccessful()) . "\n";
echo "Failed channels: " . count($result->getFailed()) . "\n";
if ($result->isFailure()) {
echo "⚠️ Critical notification failed - some channels did not receive the message\n";
}
foreach ($result->getSuccessful() as $channelResult) {
echo "{$channelResult->channel->value}: Delivered\n";
}
foreach ($result->getFailed() as $channelResult) {
echo "{$channelResult->channel->value}: {$channelResult->errorMessage}\n";
}
echo "\n";
// Example 5: Async Multi-Channel with Strategy
echo "5. Async Multi-Channel Dispatch\n";
echo str_repeat("-", 50) . "\n";
$notification = Notification::create(
recipientId: 'user_202',
type: new SystemNotificationType('newsletter.weekly'),
title: 'Your Weekly Newsletter',
body: 'Check out this week\'s highlights and updates.',
NotificationChannel::EMAIL,
NotificationChannel::TELEGRAM
)->withPriority(NotificationPriority::LOW);
// Async dispatch - queued with priority mapping
$dispatcher->send($notification, async: true, strategy: DispatchStrategy::ALL);
echo "✅ Notification queued for async dispatch\n";
echo "Strategy: ALL (will attempt all channels in background)\n";
echo "Priority: LOW (mapped to queue priority)\n";
echo "\n";
// Example 6: Strategy Selection Based on Priority
echo "6. Dynamic Strategy Selection\n";
echo str_repeat("-", 50) . "\n";
function selectStrategy(NotificationPriority $priority): DispatchStrategy
{
return match ($priority) {
NotificationPriority::URGENT => DispatchStrategy::ALL_OR_NONE, // Critical: all must succeed
NotificationPriority::HIGH => DispatchStrategy::FIRST_SUCCESS, // Quick delivery
NotificationPriority::NORMAL => DispatchStrategy::FALLBACK, // Graceful degradation
NotificationPriority::LOW => DispatchStrategy::ALL, // Best effort
};
}
$urgentNotification = Notification::create(
recipientId: 'user_303',
type: new SystemNotificationType('payment.failed'),
title: 'Payment Failed',
body: 'Your payment could not be processed.',
NotificationChannel::EMAIL,
NotificationChannel::SMS
)->withPriority(NotificationPriority::URGENT);
$strategy = selectStrategy($urgentNotification->priority);
echo "Priority: {$urgentNotification->priority->value}\n";
echo "Selected Strategy: {$strategy->value}\n";
$result = $dispatcher->sendNow($urgentNotification, $strategy);
echo "Result: " . ($result->isSuccess() ? "✅ SUCCESS" : "❌ FAILURE") . "\n";
echo "\n";
// Summary
echo "=== Strategy Summary ===\n";
echo "ALL: Send to all channels, continue even if some fail\n";
echo " Use case: Non-critical updates, newsletters, marketing\n\n";
echo "FIRST_SUCCESS: Stop after first successful delivery\n";
echo " Use case: Time-sensitive notifications, quick delivery needed\n\n";
echo "FALLBACK: Try next only if previous failed\n";
echo " Use case: Graceful degradation, Telegram -> Email -> SMS chain\n\n";
echo "ALL_OR_NONE: All must succeed or entire dispatch fails\n";
echo " Use case: Critical notifications, legal compliance, account actions\n\n";
echo "✅ Multi-channel dispatch examples completed\n";

View File

@@ -0,0 +1,283 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use App\Framework\Core\AppBootstrapper;
use App\Framework\Notification\Channels\TelegramChannel;
use App\Framework\Notification\Media\MediaManager;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\Notification;
/**
* Rich Media Notification Example
*
* Demonstrates the MediaManager system for sending notifications with:
* - Photos
* - Videos
* - Audio files
* - Documents
* - Location data
*/
echo "🎨 Rich Media Notification System Example\n";
echo "==========================================\n\n";
// Bootstrap application
$app = AppBootstrapper::bootstrap();
$container = $app->getContainer();
// Get TelegramChannel with injected MediaManager
$telegramChannel = $container->get(TelegramChannel::class);
$mediaManager = $telegramChannel->mediaManager;
// Create sample notification
$notification = new Notification(
userId: 'user_123',
title: 'Rich Media Test',
body: 'Testing media capabilities',
channel: NotificationChannel::TELEGRAM,
type: 'media_test'
);
echo "📋 Testing MediaManager Capabilities\n";
echo "------------------------------------\n\n";
// 1. Check Telegram capabilities
echo "1⃣ Checking Telegram channel capabilities...\n";
$capabilities = $mediaManager->getCapabilities(NotificationChannel::TELEGRAM);
echo " Supported media types:\n";
echo " - Photos: " . ($capabilities->supportsPhoto ? '✅' : '❌') . "\n";
echo " - Videos: " . ($capabilities->supportsVideo ? '✅' : '❌') . "\n";
echo " - Audio: " . ($capabilities->supportsAudio ? '✅' : '❌') . "\n";
echo " - Documents: " . ($capabilities->supportsDocument ? '✅' : '❌') . "\n";
echo " - Location: " . ($capabilities->supportsLocation ? '✅' : '❌') . "\n";
echo " - Voice: " . ($capabilities->supportsVoice ? '✅' : '❌') . "\n\n";
// 2. Test photo support
echo "2⃣ Testing photo support...\n";
if ($mediaManager->supportsPhoto(NotificationChannel::TELEGRAM)) {
echo " ✅ Telegram supports photos\n";
// Example: Send photo with caption
try {
$photoNotification = new Notification(
userId: 'user_123',
title: 'Photo Notification',
body: 'Check out this image!',
channel: NotificationChannel::TELEGRAM,
type: 'photo_test'
);
// Note: In real usage, you would provide a valid file path or Telegram file_id
// $mediaManager->sendPhoto(
// NotificationChannel::TELEGRAM,
// $photoNotification,
// photoPath: '/path/to/image.jpg',
// caption: 'Beautiful landscape photo'
// );
echo " 📸 Photo sending method available\n";
} catch (\Exception $e) {
echo " ⚠️ Photo test skipped: {$e->getMessage()}\n";
}
} else {
echo " ❌ Telegram does not support photos\n";
}
echo "\n";
// 3. Test video support
echo "3⃣ Testing video support...\n";
if ($mediaManager->supportsVideo(NotificationChannel::TELEGRAM)) {
echo " ✅ Telegram supports videos\n";
// Example: Send video with thumbnail
try {
$videoNotification = new Notification(
userId: 'user_123',
title: 'Video Notification',
body: 'Watch this video!',
channel: NotificationChannel::TELEGRAM,
type: 'video_test'
);
// Note: In real usage, you would provide valid file paths
// $mediaManager->sendVideo(
// NotificationChannel::TELEGRAM,
// $videoNotification,
// videoPath: '/path/to/video.mp4',
// caption: 'Tutorial video',
// thumbnailPath: '/path/to/thumbnail.jpg'
// );
echo " 🎥 Video sending method available\n";
} catch (\Exception $e) {
echo " ⚠️ Video test skipped: {$e->getMessage()}\n";
}
} else {
echo " ❌ Telegram does not support videos\n";
}
echo "\n";
// 4. Test audio support
echo "4⃣ Testing audio support...\n";
if ($mediaManager->supportsAudio(NotificationChannel::TELEGRAM)) {
echo " ✅ Telegram supports audio\n";
// Example: Send audio file
try {
$audioNotification = new Notification(
userId: 'user_123',
title: 'Audio Notification',
body: 'Listen to this audio!',
channel: NotificationChannel::TELEGRAM,
type: 'audio_test'
);
// Note: In real usage, you would provide a valid audio file
// $mediaManager->sendAudio(
// NotificationChannel::TELEGRAM,
// $audioNotification,
// audioPath: '/path/to/audio.mp3',
// caption: 'Podcast episode',
// duration: 300 // 5 minutes
// );
echo " 🎵 Audio sending method available\n";
} catch (\Exception $e) {
echo " ⚠️ Audio test skipped: {$e->getMessage()}\n";
}
} else {
echo " ❌ Telegram does not support audio\n";
}
echo "\n";
// 5. Test document support
echo "5⃣ Testing document support...\n";
if ($mediaManager->supportsDocument(NotificationChannel::TELEGRAM)) {
echo " ✅ Telegram supports documents\n";
// Example: Send document
try {
$documentNotification = new Notification(
userId: 'user_123',
title: 'Document Notification',
body: 'Here is your document!',
channel: NotificationChannel::TELEGRAM,
type: 'document_test'
);
// Note: In real usage, you would provide a valid document
// $mediaManager->sendDocument(
// NotificationChannel::TELEGRAM,
// $documentNotification,
// documentPath: '/path/to/document.pdf',
// caption: 'Monthly report',
// filename: 'Report_2024.pdf'
// );
echo " 📄 Document sending method available\n";
} catch (\Exception $e) {
echo " ⚠️ Document test skipped: {$e->getMessage()}\n";
}
} else {
echo " ❌ Telegram does not support documents\n";
}
echo "\n";
// 6. Test location support
echo "6⃣ Testing location support...\n";
if ($mediaManager->supportsLocation(NotificationChannel::TELEGRAM)) {
echo " ✅ Telegram supports location sharing\n";
// Example: Send location
try {
$locationNotification = new Notification(
userId: 'user_123',
title: 'Location Notification',
body: 'Meet me here!',
channel: NotificationChannel::TELEGRAM,
type: 'location_test'
);
// Note: In real usage, you would provide actual coordinates
// $mediaManager->sendLocation(
// NotificationChannel::TELEGRAM,
// $locationNotification,
// latitude: 52.5200, // Berlin
// longitude: 13.4050,
// title: 'Meeting Point',
// address: 'Brandenburger Tor, Berlin'
// );
echo " 📍 Location sending method available\n";
} catch (\Exception $e) {
echo " ⚠️ Location test skipped: {$e->getMessage()}\n";
}
} else {
echo " ❌ Telegram does not support location sharing\n";
}
echo "\n";
// 7. Test error handling for unsupported channel
echo "7⃣ Testing error handling for unsupported channel...\n";
try {
// Try to check capabilities for a channel without registered driver
$emailCapabilities = $mediaManager->getCapabilities(NotificationChannel::EMAIL);
if (!$emailCapabilities->hasAnyMediaSupport()) {
echo " ✅ Email channel has no media support (as expected)\n";
}
} catch (\Exception $e) {
echo " ⚠️ Expected behavior: {$e->getMessage()}\n";
}
echo "\n";
// 8. Demonstrate runtime capability checking
echo "8⃣ Runtime capability checking pattern...\n";
$testChannel = NotificationChannel::TELEGRAM;
echo " Example: Sending media with runtime checks\n";
echo " \n";
echo " if (\$mediaManager->supportsPhoto(\$channel)) {\n";
echo " \$mediaManager->sendPhoto(\$channel, \$notification, \$photoPath);\n";
echo " } else {\n";
echo " // Fallback to text-only notification\n";
echo " \$channel->send(\$notification);\n";
echo " }\n";
echo "\n";
// Summary
echo "✅ Rich Media System Summary\n";
echo "============================\n\n";
echo "Architecture:\n";
echo "- MediaManager: Central management with driver registration\n";
echo "- MediaDriver: Marker interface with atomic capability interfaces\n";
echo "- Atomic Interfaces: SupportsPhotoAttachments, SupportsVideoAttachments, etc.\n";
echo "- TelegramMediaDriver: Full media support implementation\n\n";
echo "Key Features:\n";
echo "- ✅ Runtime capability detection via instanceof\n";
echo "- ✅ Type-safe media sending with validation\n";
echo "- ✅ Optional media support per channel\n";
echo "- ✅ Public MediaManager property on channels\n";
echo "- ✅ Graceful degradation for unsupported features\n\n";
echo "Usage:\n";
echo "1. Access MediaManager via channel: \$channel->mediaManager\n";
echo "2. Check capabilities before sending: \$mediaManager->supportsPhoto(\$channel)\n";
echo "3. Send media with validation: \$mediaManager->sendPhoto(...)\n";
echo "4. Handle unsupported media gracefully with fallbacks\n\n";
echo "✨ Example completed successfully!\n";

View File

@@ -0,0 +1,309 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use App\Framework\Notification\Templates\NotificationTemplate;
use App\Framework\Notification\Templates\TemplateRenderer;
use App\Framework\Notification\Templates\ChannelTemplate;
use App\Framework\Notification\Templates\InMemoryTemplateRegistry;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\ValueObjects\NotificationPriority;
use App\Framework\Notification\ValueObjects\SystemNotificationType;
/**
* Notification Template System Example
*
* Demonstrates:
* - Template creation with placeholders
* - Variable substitution
* - Per-channel customization
* - Template registry
* - Required and default variables
*/
echo "=== Notification Template System Examples ===\n\n";
// Setup
$registry = new InMemoryTemplateRegistry();
$renderer = new TemplateRenderer();
// Example 1: Basic Template with Simple Placeholders
echo "1. Basic Template - Order Shipped\n";
echo str_repeat("-", 50) . "\n";
$orderShippedTemplate = NotificationTemplate::create(
name: 'order.shipped',
titleTemplate: 'Order {{order_id}} Shipped',
bodyTemplate: 'Your order {{order_id}} has been shipped and will arrive by {{delivery_date}}. Track your package: {{tracking_url}}'
)->withPriority(NotificationPriority::HIGH)
->withRequiredVariables('order_id', 'delivery_date', 'tracking_url');
$registry->register($orderShippedTemplate);
// Render notification
$notification = $renderer->render(
template: $orderShippedTemplate,
recipientId: 'user_123',
variables: [
'order_id' => '#12345',
'delivery_date' => 'December 25, 2024',
'tracking_url' => 'https://example.com/track/ABC123',
],
channels: [NotificationChannel::EMAIL, NotificationChannel::TELEGRAM],
type: new SystemNotificationType('order.shipped')
);
echo "Title: {$notification->title}\n";
echo "Body: {$notification->body}\n";
echo "Priority: {$notification->priority->value}\n";
echo "Template Data: " . json_encode($notification->data, JSON_PRETTY_PRINT) . "\n";
echo "\n";
// Example 2: Template with Nested Variables
echo "2. Nested Variables - User Welcome\n";
echo str_repeat("-", 50) . "\n";
$welcomeTemplate = NotificationTemplate::create(
name: 'user.welcome',
titleTemplate: 'Welcome to {{app.name}}, {{user.name}}!',
bodyTemplate: 'Hi {{user.name}}, welcome to {{app.name}}! Your account has been created successfully. Get started here: {{app.url}}'
)->withRequiredVariables('user.name')
->withDefaultVariables([
'app' => [
'name' => 'My Application',
'url' => 'https://example.com/start',
],
]);
$registry->register($welcomeTemplate);
$notification = $renderer->render(
template: $welcomeTemplate,
recipientId: 'user_456',
variables: [
'user' => [
'name' => 'John Doe',
'email' => 'john@example.com',
],
],
channels: [NotificationChannel::EMAIL],
type: new SystemNotificationType('user.welcome')
);
echo "Title: {$notification->title}\n";
echo "Body: {$notification->body}\n";
echo "\n";
// Example 3: Per-Channel Customization
echo "3. Per-Channel Templates - Different Formats\n";
echo str_repeat("-", 50) . "\n";
$securityAlertTemplate = NotificationTemplate::create(
name: 'security.alert',
titleTemplate: 'Security Alert',
bodyTemplate: 'Unusual login activity detected from {{ip_address}} at {{time}}.'
)->withPriority(NotificationPriority::URGENT);
// Telegram: Use Markdown formatting
$telegramTemplate = ChannelTemplate::create(
titleTemplate: '🔒 *Security Alert*',
bodyTemplate: '⚠️ Unusual login activity detected:\n\n📍 IP: `{{ip_address}}`\n⏰ Time: {{time}}\n\nIf this wasn\'t you, secure your account immediately!'
)->withMetadata(['parse_mode' => 'Markdown']);
// Email: Use HTML formatting
$emailTemplate = ChannelTemplate::create(
titleTemplate: '🔒 Security Alert',
bodyTemplate: '<h2>Unusual Login Activity</h2><p>We detected a login from <strong>{{ip_address}}</strong> at {{time}}.</p><p>If this wasn\'t you, please secure your account immediately.</p>'
)->withMetadata(['content_type' => 'text/html']);
// SMS: Keep it short and plain
$smsTemplate = ChannelTemplate::create(
bodyTemplate: 'SECURITY ALERT: Login from {{ip_address}} at {{time}}. If not you, secure account now.'
);
$securityAlertTemplate = $securityAlertTemplate
->withChannelTemplate(NotificationChannel::TELEGRAM, $telegramTemplate)
->withChannelTemplate(NotificationChannel::EMAIL, $emailTemplate)
->withChannelTemplate(NotificationChannel::SMS, $smsTemplate);
$registry->register($securityAlertTemplate);
// Render for each channel
$variables = [
'ip_address' => '203.0.113.42',
'time' => '2024-12-19 15:30:00 UTC',
];
echo "TELEGRAM VERSION:\n";
$telegramContent = $renderer->renderForChannel(
$securityAlertTemplate,
NotificationChannel::TELEGRAM,
$variables
);
echo "Title: {$telegramContent->title}\n";
echo "Body:\n{$telegramContent->body}\n";
echo "Metadata: " . json_encode($telegramContent->metadata) . "\n\n";
echo "EMAIL VERSION:\n";
$emailContent = $renderer->renderForChannel(
$securityAlertTemplate,
NotificationChannel::EMAIL,
$variables
);
echo "Title: {$emailContent->title}\n";
echo "Body:\n{$emailContent->body}\n";
echo "Metadata: " . json_encode($emailContent->metadata) . "\n\n";
echo "SMS VERSION:\n";
$smsContent = $renderer->renderForChannel(
$securityAlertTemplate,
NotificationChannel::SMS,
$variables
);
echo "Body: {$smsContent->body}\n";
echo "\n";
// Example 4: Template with Default Variables
echo "4. Default Variables - Newsletter Template\n";
echo str_repeat("-", 50) . "\n";
$newsletterTemplate = NotificationTemplate::create(
name: 'newsletter.weekly',
titleTemplate: '{{newsletter.title}} - Week {{week_number}}',
bodyTemplate: 'Hi {{user.name}}, here\'s your weekly {{newsletter.title}}! This week\'s highlights: {{highlights}}. Read more: {{newsletter.url}}'
)->withDefaultVariables([
'newsletter' => [
'title' => 'Weekly Update',
'url' => 'https://example.com/newsletter',
],
'highlights' => 'New features, bug fixes, and improvements',
])->withRequiredVariables('user.name', 'week_number');
$registry->register($newsletterTemplate);
$notification = $renderer->render(
template: $newsletterTemplate,
recipientId: 'user_789',
variables: [
'user' => ['name' => 'Jane Smith'],
'week_number' => '51',
// Using default values for newsletter and highlights
],
channels: [NotificationChannel::EMAIL],
type: new SystemNotificationType('newsletter.weekly')
);
echo "Title: {$notification->title}\n";
echo "Body: {$notification->body}\n";
echo "\n";
// Example 5: Template Registry Usage
echo "5. Template Registry - Lookup and Reuse\n";
echo str_repeat("-", 50) . "\n";
echo "Registered templates:\n";
foreach ($registry->all() as $name => $template) {
echo " - {$name} (Priority: {$template->defaultPriority->value})\n";
}
echo "\n";
// Reuse template from registry
$template = $registry->get('order.shipped');
if ($template !== null) {
echo "Retrieved template: {$template->name}\n";
echo "Required variables: " . implode(', ', $template->requiredVariables) . "\n";
}
echo "\n";
// Example 6: Error Handling - Missing Required Variable
echo "6. Error Handling - Validation\n";
echo str_repeat("-", 50) . "\n";
try {
$renderer->render(
template: $orderShippedTemplate,
recipientId: 'user_999',
variables: [
'order_id' => '#67890',
// Missing 'delivery_date' and 'tracking_url'
],
channels: [NotificationChannel::EMAIL],
type: new SystemNotificationType('order.shipped')
);
} catch (\InvalidArgumentException $e) {
echo "❌ Validation Error: {$e->getMessage()}\n";
}
echo "\n";
// Example 7: Complex Object in Variables
echo "7. Complex Objects - Value Object Support\n";
echo str_repeat("-", 50) . "\n";
$paymentTemplate = NotificationTemplate::create(
name: 'payment.received',
titleTemplate: 'Payment Received',
bodyTemplate: 'We received your payment of {{amount}} on {{date}}. Transaction ID: {{transaction.id}}'
);
// Create notification with object variables
$notification = $renderer->render(
template: $paymentTemplate,
recipientId: 'user_101',
variables: [
'amount' => '$99.00',
'date' => '2024-12-19',
'transaction' => [
'id' => 'TXN_123456',
'status' => 'completed',
],
],
channels: [NotificationChannel::EMAIL, NotificationChannel::TELEGRAM],
type: new SystemNotificationType('payment.received')
);
echo "Title: {$notification->title}\n";
echo "Body: {$notification->body}\n";
echo "\n";
// Example 8: Integration with NotificationDispatcher
echo "8. Template + Dispatcher Integration\n";
echo str_repeat("-", 50) . "\n";
echo "Step 1: Create template\n";
$template = NotificationTemplate::create(
name: 'account.deleted',
titleTemplate: 'Account Deletion Confirmation',
bodyTemplate: 'Your account {{username}} has been permanently deleted on {{deletion_date}}.'
)->withPriority(NotificationPriority::URGENT);
echo "Step 2: Render notification from template\n";
$notification = $renderer->render(
template: $template,
recipientId: 'user_202',
variables: [
'username' => 'johndoe',
'deletion_date' => '2024-12-19',
],
channels: [NotificationChannel::EMAIL, NotificationChannel::SMS],
type: new SystemNotificationType('account.deleted')
);
echo "Step 3: Dispatch via NotificationDispatcher\n";
echo " (In real app: \$dispatcher->sendNow(\$notification, DispatchStrategy::ALL_OR_NONE))\n";
echo " Notification ready for dispatch:\n";
echo " - Title: {$notification->title}\n";
echo " - Channels: " . count($notification->channels) . "\n";
echo " - Priority: {$notification->priority->value}\n";
echo "\n";
// Summary
echo "=== Template System Summary ===\n";
echo "✅ Created " . count($registry->all()) . " templates\n";
echo "✅ Demonstrated placeholder substitution ({{variable}})\n";
echo "✅ Demonstrated nested variables ({{user.name}})\n";
echo "✅ Demonstrated per-channel customization\n";
echo "✅ Demonstrated default variables\n";
echo "✅ Demonstrated validation and error handling\n";
echo "✅ Template system ready for production use\n";

View File

@@ -0,0 +1,192 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use App\Framework\Worker\Every;
use App\Framework\Worker\Schedule;
/**
* Example: Scheduled Job that runs every 5 minutes
*
* The #[Schedule] attribute marks this class for automatic registration
* with the Worker's scheduler system.
*
* The Worker will:
* 1. Discover this class via ScheduleDiscoveryService on startup
* 2. Register it with SchedulerService using an IntervalSchedule
* 3. Execute the handle() method every 5 minutes
*/
#[Schedule(at: new Every(minutes: 5))]
final class CleanupTempFilesJob
{
/**
* This method is called by the scheduler when the job is due
*/
public function handle(): array
{
echo "[" . date('Y-m-d H:i:s') . "] Running CleanupTempFilesJob\n";
// Your cleanup logic here
$deletedFiles = $this->cleanupOldTempFiles();
return [
'status' => 'success',
'deleted_files' => $deletedFiles,
'executed_at' => time()
];
}
private function cleanupOldTempFiles(): int
{
// Example cleanup logic
$tempDir = sys_get_temp_dir();
$deletedCount = 0;
// Delete files older than 1 hour
$files = glob($tempDir . '/*.tmp');
foreach ($files as $file) {
if (file_exists($file) && (time() - filemtime($file)) > 3600) {
unlink($file);
$deletedCount++;
}
}
return $deletedCount;
}
}
/**
* Example: Hourly data aggregation job
*
* This job runs every hour and aggregates analytics data
*/
#[Schedule(at: new Every(hours: 1))]
final class AggregateAnalyticsJob
{
public function handle(): array
{
echo "[" . date('Y-m-d H:i:s') . "] Running AggregateAnalyticsJob\n";
// Your aggregation logic here
$recordsProcessed = $this->aggregateLastHourData();
return [
'status' => 'success',
'records_processed' => $recordsProcessed,
'executed_at' => time()
];
}
private function aggregateLastHourData(): int
{
// Example aggregation logic
return rand(100, 1000);
}
}
/**
* Example: Daily backup job
*
* This job runs once per day
*/
#[Schedule(at: new Every(days: 1))]
final class DailyBackupJob
{
public function handle(): array
{
echo "[" . date('Y-m-d H:i:s') . "] Running DailyBackupJob\n";
// Your backup logic here
$backupSize = $this->createDatabaseBackup();
return [
'status' => 'success',
'backup_size_mb' => $backupSize,
'executed_at' => time()
];
}
private function createDatabaseBackup(): float
{
// Example backup logic
return round(rand(50, 200) / 10, 2);
}
}
/**
* Example: Callable job (using __invoke)
*
* Jobs can also be callable instead of using handle() method
*/
#[Schedule(at: new Every(minutes: 10))]
final class MonitorSystemHealthJob
{
public function __invoke(): string
{
echo "[" . date('Y-m-d H:i:s') . "] Running MonitorSystemHealthJob\n";
$memoryUsage = memory_get_usage(true) / 1024 / 1024;
$cpuLoad = sys_getloadavg()[0];
return "System healthy - Memory: {$memoryUsage}MB, CPU Load: {$cpuLoad}";
}
}
/**
* Example: Complex schedule with multiple time units
*/
#[Schedule(at: new Every(days: 1, hours: 2, minutes: 30))]
final class WeeklyReportJob
{
public function handle(): array
{
echo "[" . date('Y-m-d H:i:s') . "] Running WeeklyReportJob\n";
// This runs every 1 day, 2 hours, 30 minutes
// Total: (1 * 86400) + (2 * 3600) + (30 * 60) = 94200 seconds
return [
'status' => 'success',
'report_generated' => true,
'executed_at' => time()
];
}
}
echo <<<'INFO'
=== Scheduled Jobs Example ===
This example shows how to create scheduled jobs using the #[Schedule] attribute.
How it works:
1. Mark your job class with #[Schedule(at: new Every(...))]
2. Implement either a handle() method or make your class callable (__invoke)
3. The Worker will automatically discover and register your job on startup
4. The job will execute at the specified interval
Available Every time units:
- Every(days: 1) - Run once per day
- Every(hours: 1) - Run once per hour
- Every(minutes: 5) - Run every 5 minutes
- Every(seconds: 30) - Run every 30 seconds
- Combine multiple units: Every(days: 1, hours: 2, minutes: 30)
Task ID Generation:
Job class names are automatically converted to kebab-case task IDs:
- CleanupTempFilesJob -> cleanup-temp-files-job
- AggregateAnalyticsJob -> aggregate-analytics-job
- DailyBackupJob -> daily-backup-job
Starting the Worker:
To run these scheduled jobs, start the Worker:
docker exec php php console.php worker:start
The Worker will:
- Discover all classes with #[Schedule] attribute
- Register them with the SchedulerService
- Check for due tasks every 10 seconds
- Execute tasks and log results
INFO;

View File

@@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use App\Framework\Core\AppBootstrapper;
use App\Framework\Notification\Channels\TelegramChannel;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\Notification;
/**
* Practical Telegram Media Sending Example
*
* This script demonstrates actual media sending via Telegram
* using the MediaManager system
*/
echo "📱 Telegram Rich Media Sending Example\n";
echo "=======================================\n\n";
// Bootstrap application
$app = AppBootstrapper::bootstrap();
$container = $app->getContainer();
// Get Telegram channel with MediaManager
$telegramChannel = $container->get(TelegramChannel::class);
$mediaManager = $telegramChannel->mediaManager;
// Check Telegram capabilities
echo "📋 Telegram Media Capabilities:\n";
$capabilities = $mediaManager->getCapabilities(NotificationChannel::TELEGRAM);
echo " Photos: " . ($capabilities->supportsPhoto ? '✅' : '❌') . "\n";
echo " Videos: " . ($capabilities->supportsVideo ? '✅' : '❌') . "\n";
echo " Audio: " . ($capabilities->supportsAudio ? '✅' : '❌') . "\n";
echo " Documents: " . ($capabilities->supportsDocument ? '✅' : '❌') . "\n";
echo " Location: " . ($capabilities->supportsLocation ? '✅' : '❌') . "\n\n";
// Create notification
$notification = new Notification(
userId: 'user_123',
title: 'Media Test',
body: 'Testing Telegram media capabilities',
channel: NotificationChannel::TELEGRAM,
type: 'media_demo'
);
// Example 1: Send Photo
echo "1⃣ Sending photo...\n";
try {
if ($mediaManager->supportsPhoto(NotificationChannel::TELEGRAM)) {
// Using Telegram's sample photo URL for testing
// In production, use local file paths or previously uploaded file_id
$photoUrl = 'https://api.telegram.org/file/bot<token>/photos/file_0.jpg';
$photoNotification = new Notification(
userId: 'user_123',
title: 'Photo Notification',
body: 'This is a test photo',
channel: NotificationChannel::TELEGRAM,
type: 'photo'
);
$mediaManager->sendPhoto(
NotificationChannel::TELEGRAM,
$photoNotification,
photoPath: $photoUrl, // Can be URL, file path, or file_id
caption: '📸 Test photo from MediaManager'
);
echo " ✅ Photo sent successfully\n";
} else {
echo " ❌ Photo not supported\n";
}
} catch (\Exception $e) {
echo " ⚠️ Error: {$e->getMessage()}\n";
}
echo "\n";
// Example 2: Send Location
echo "2⃣ Sending location...\n";
try {
if ($mediaManager->supportsLocation(NotificationChannel::TELEGRAM)) {
$locationNotification = new Notification(
userId: 'user_123',
title: 'Location Share',
body: 'Meeting point',
channel: NotificationChannel::TELEGRAM,
type: 'location'
);
$mediaManager->sendLocation(
NotificationChannel::TELEGRAM,
$locationNotification,
latitude: 52.5200, // Berlin
longitude: 13.4050,
title: 'Brandenburger Tor',
address: '10117 Berlin, Germany'
);
echo " ✅ Location sent successfully\n";
} else {
echo " ❌ Location not supported\n";
}
} catch (\Exception $e) {
echo " ⚠️ Error: {$e->getMessage()}\n";
}
echo "\n";
// Example 3: Send Document
echo "3⃣ Sending document...\n";
try {
if ($mediaManager->supportsDocument(NotificationChannel::TELEGRAM)) {
$documentNotification = new Notification(
userId: 'user_123',
title: 'Document Share',
body: 'Important document',
channel: NotificationChannel::TELEGRAM,
type: 'document'
);
// In production, use actual file path
// $mediaManager->sendDocument(
// NotificationChannel::TELEGRAM,
// $documentNotification,
// documentPath: '/path/to/document.pdf',
// caption: '📄 Monthly Report',
// filename: 'report_2024.pdf'
// );
echo " Document example (requires actual file path)\n";
} else {
echo " ❌ Document not supported\n";
}
} catch (\Exception $e) {
echo " ⚠️ Error: {$e->getMessage()}\n";
}
echo "\n";
// Example 4: Graceful fallback to text-only
echo "4⃣ Demonstrating graceful fallback...\n";
try {
$fallbackNotification = new Notification(
userId: 'user_123',
title: 'Fallback Test',
body: 'This notification tries to send media, but falls back to text if unsupported',
channel: NotificationChannel::TELEGRAM,
type: 'fallback'
);
// Try to send with photo, fallback to text
if ($mediaManager->supportsPhoto(NotificationChannel::TELEGRAM)) {
echo " Attempting to send with photo...\n";
// $mediaManager->sendPhoto(...);
echo " ✅ Would send photo if file path provided\n";
} else {
echo " Photo not supported, falling back to text notification...\n";
$telegramChannel->send($fallbackNotification);
echo " ✅ Text notification sent as fallback\n";
}
} catch (\Exception $e) {
echo " ⚠️ Error: {$e->getMessage()}\n";
echo " Falling back to text notification...\n";
$telegramChannel->send($fallbackNotification);
echo " ✅ Fallback successful\n";
}
echo "\n";
// Example 5: Using MediaCapabilities for multi-media notifications
echo "5⃣ Smart multi-media notification...\n";
$multiMediaNotification = new Notification(
userId: 'user_123',
title: 'Order Confirmed',
body: 'Your order #12345 has been confirmed',
channel: NotificationChannel::TELEGRAM,
type: 'order_confirmed'
);
$capabilities = $mediaManager->getCapabilities(NotificationChannel::TELEGRAM);
if ($capabilities->supportsPhoto) {
echo " 📸 Could attach product photo\n";
}
if ($capabilities->supportsDocument) {
echo " 📄 Could attach order receipt PDF\n";
}
if ($capabilities->supportsLocation) {
echo " 📍 Could share delivery location\n";
}
echo " ✅ Multi-media notification planned\n\n";
// Summary
echo "✨ Summary\n";
echo "=========\n\n";
echo "MediaManager provides:\n";
echo "- Runtime capability checking before sending\n";
echo "- Type-safe media sending methods\n";
echo "- Graceful fallback support\n";
echo "- Unified API across all channels\n\n";
echo "Best Practices:\n";
echo "1. Always check capabilities before sending media\n";
echo "2. Provide fallback to text notifications\n";
echo "3. Handle exceptions gracefully\n";
echo "4. Use appropriate media types for context\n\n";
echo "✅ Example completed!\n";