Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/cookbook/third-party-integrations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ Feel free to pull-request your own writings. Team members will review it, give f

---

- [Symfony Messenger integration guide](symfony-messenger-integration-guide.md)
- [Symfony Messenger integration guide](symfony-messenger-integration-guide.md)
- [Symfony Scheduler integration guide](symfony-scheduler-integration-guide.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
# Symfony Scheduler integration guide

The [Symfony Scheduler](https://symfony.com/doc/current/scheduler.html) component manages task scheduling within your PHP application, like running a task each night at 3 AM, every two weeks except for holidays or any other custom schedule you might need.

## Installation

```shell
composer require symfony/scheduler
```

## How to use with Yii

### 1. Create a Message and Handler.

```php
namespace App\Messages;

final readonly class MyMessage
{
public function __construct(public string $content)
{
}
}
```

```php
namespace App\Messages;

use Psr\Log\LoggerInterface;

final readonly class MyMessageHandler
{
public function __construct(private LoggerInterface $logger)
{
}
public function __invoke(MyMessage $message): void
{
// ...
$this->logger->info("The message with the content '$message->content' has been received.");
}
}
```

### 2. Attaching Recurring Messages to a Schedule.

```php
namespace App\Scheduler;

use App\Messages\MyMessage;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\ScheduleProviderInterface;

class MyScheduleProvider implements ScheduleProviderInterface
{
public function getSchedule(): Schedule
{
return (new Schedule())
->with(
RecurringMessage::every(
'10 seconds',
new MyMessage('Hello Symfony Scheduler!'))
);
}
}
```

### 3. Configure the Symfony Scheduler.

Depending on your needs, the Symfony Scheduler component offers two ways to consume messages: using the `messenger:consume` command or [creating a worker programmatically](https://symfony.com/doc/current/scheduler.html#creating-a-consumer-programmatically). The example uses the [symfony/messenger](https://packagist.org/packages/symfony/messenger) package to consume messages.


Install the Symfony Messenger package:
```shell
composer require symfony/messenger
```

Install the Symfony Cache package:
```shell
composer require ymfony/cache
```

Implement the ServiceProviderInterface. This instance of the ServiceProviderInterface is used to configure certain Symfony Messenger commands.

```php
namespace App\Services;

use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;

final readonly class ServiceProvider implements ServiceProviderInterface
{
/**
* @param ContainerInterface $container
* @param array<string, string> $serviceMap
*/
public function __construct(
private ContainerInterface $container,
private array $serviceMap)
{
}

/**
* @inheritDoc
*/
public function get(string $id): mixed
{
return $this->container->get($id);
}

public function has(string $id): bool
{
return $this->container->has($id);
}

/**
* @inheritDoc
*/
public function getProvidedServices(): array
{
return $this->serviceMap;
}
}
```

Configure the Symfony Scheduler and Symfony Messenger dependencies in the `config/common/di/application.php`:
```php
use App\Messages\MyMessage;
use App\Messages\MyMessageHandler;
use App\Scheduler\MyScheduleProvider;
use App\Services\ServiceProvider;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
use Symfony\Component\Messenger\Command\DebugCommand;
use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand;
use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand;
use Symfony\Component\Messenger\Command\FailedMessagesShowCommand;
use Symfony\Component\Messenger\Command\SetupTransportsCommand;
use Symfony\Component\Messenger\Command\StatsCommand;
use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener;
use Symfony\Component\Messenger\Handler\HandlersLocator;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
use Symfony\Component\Messenger\RoutableMessageBus;
use Symfony\Component\Scheduler\Generator\MessageGenerator;
use Symfony\Component\Scheduler\Messenger\SchedulerTransport;
use Symfony\Contracts\Service\ServiceProviderInterface;
use Yiisoft\Aliases\Aliases;
use Yiisoft\Definitions\Reference;

class_alias(MessageBus::class, 'MyMessageBus');

return [
// ...
CacheItemPoolInterface::class => static function (ContainerInterface $container) {
return new FilesystemAdapter(directory: $container->get(Aliases::class)->get('@runtime'));
}, //One of the following adapters can be used instead: Psr16Adapter, RedisAdapter, MemcachedAdapter, DoctrineDbalAdapter, and so forth.


'SchedulerTransport' => static function () {
return new SchedulerTransport(
new MessageGenerator(
new MyScheduleProvider(),
'MySchedule'
)
);
},

MyMessageBus::class => static function (ContainerInterface $container) {
return new MyMessageBus([
new HandleMessageMiddleware(new HandlersLocator([
MyMessage::class => [$container->get(MyMessageHandler::class)]
])),
]);
},

EventDispatcherInterface::class => static function (ContainerInterface $container) {
$eventDispatcher = new EventDispatcher();
$eventDispatcher->addSubscriber($container->get(StopWorkerOnRestartSignalListener::class));
return $eventDispatcher;
},
ServiceProviderInterface::class => [
'class' => ServiceProvider::class,
'__construct()' => [
'container' => Reference::to(ContainerInterface::class),
'serviceMap' => [
'SchedulerTransport' => SchedulerTransport::class
]
],
],

ConsumeMessagesCommand::class => static function (ContainerInterface $container) {
return new ConsumeMessagesCommand(
$container->get(RoutableMessageBus::class),
$container,
$container->get(EventDispatcherInterface::class),
$container->get(LoggerInterface::class),
array_keys($container->get(ServiceProviderInterface::class)->getProvidedServices())
);
},
DebugCommand::class => [
'__construct()' => [
'mapping' => [
MyMessageBus::class => [Reference::to(MyMessageHandler::class)]
]
],
],
FailedMessagesRemoveCommand::class => [
'__construct()' => [
'globalFailureReceiverName' => null,
'failureTransports' => Reference::to(ServiceProviderInterface::class)
],
],
FailedMessagesRetryCommand::class => [
'__construct()' => [
'globalReceiverName' => null,
'failureTransports' => Reference::to(ServiceProviderInterface::class),
'messageBus' => Reference::to(MyMessageBus::class),
'eventDispatcher' => Reference::to(EventDispatcherInterface::class),
'logger' => Reference::to(LoggerInterface::class),
],
],
FailedMessagesShowCommand::class => [
'__construct()' => [
'globalFailureReceiverName' => null,
'failureTransports' => Reference::to(ServiceProviderInterface::class)
],
],
SetupTransportsCommand::class => static function (ContainerInterface $container) {
return new SetupTransportsCommand(
$container,
array_keys($container->get(ServiceProviderInterface::class)->getProvidedServices())
);
},
StatsCommand::class => static function (ContainerInterface $container) {
return new StatsCommand(
$container,
array_keys($container->get(ServiceProviderInterface::class)->getProvidedServices())
);
},
];
```

### 4. Integrate the Symfony Messenger commands into the Yii console.

Add the commands to `config/console/commands.php`:
```php
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
use Symfony\Component\Messenger\Command\DebugCommand;
use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand;
use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand;
use Symfony\Component\Messenger\Command\FailedMessagesShowCommand;
use Symfony\Component\Messenger\Command\SetupTransportsCommand;
use Symfony\Component\Messenger\Command\StatsCommand;
use Symfony\Component\Messenger\Command\StopWorkersCommand;

return [
// ...
'symfony:messenger:consume' => ConsumeMessagesCommand::class,
'symfony:messenger:debug' => DebugCommand::class,
'symfony:messenger:failed:remove' => FailedMessagesRemoveCommand::class,
'symfony:messenger:failed:retry' => FailedMessagesRetryCommand::class,
'symfony:messenger:failed:show' => FailedMessagesShowCommand::class,
'symfony:messenger:setup-transports' => SetupTransportsCommand::class,
'symfony:messenger:stats' => StatsCommand::class,
'symfony:messenger:stop-workers' => StopWorkersCommand::class
];
```

### 5. Consume messages.

Example:
```bash
./yii symfony:messenger:consume SchedulerTransport --bus=MyMessageBus
```