Skip to content

Commit

Permalink
refactor(dic): SchedulerAwareInterface started (#221)
Browse files Browse the repository at this point in the history
* refactor(dic): SchedulerAwareInterface started

* feat(dic): extra tag added

* tools(infection): badge configuration improved

* build(composer): improvements

* refactor(core): improvements on extension & tags

* tools(makefile): improvements
  • Loading branch information
Guikingone authored Mar 4, 2022
1 parent af1b2df commit 059c742
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 17 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Docker
docker-compose.override.yml

vendor/
composer.lock
phpunit.xml
Expand Down
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ help:
## Project
##---------------------------------------------------------------------------

.PHONY: boot up down vendor
.PHONY: boot up ps down vendor

boot: ## Launch the project
boot: up vendor
Expand All @@ -26,6 +26,14 @@ up: ## Up the containers
up: .cloud/docker docker-compose.yaml
$(DOCKER_COMPOSE) up -d --build --remove-orphans --force-recreate

down: ## Down the containers
down: .cloud/docker docker-compose.yaml
$(DOCKER_COMPOSE) down --volumes --remove-orphans

ps: ## List the services
ps: docker-compose.yaml
$(DOCKER_COMPOSE) ps

vendor: ## Install the dependencies
vendor: composer.json
$(PHP) composer install
Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,16 @@
"ext-redis": "*",
"doctrine/dbal": "^3.1.4",
"doctrine/orm": "^2.8",
"friendsofphp/php-cs-fixer": "^3.3",
"infection/infection": "^0.25.3",
"marcocesarato/php-conventional-changelog": "^1.12",
"friendsofphp/php-cs-fixer": "^3.5",
"infection/infection": "^0.26.2",
"marcocesarato/php-conventional-changelog": "^1.13",
"phpstan/phpstan": "^0.12.99",
"phpstan/phpstan-deprecation-rules": "^0.12.6",
"phpstan/phpstan-doctrine": "^0.12.44",
"phpstan/phpstan-phpunit": "^0.12.22",
"phpstan/phpstan-strict-rules": "^0.12.11",
"phpstan/phpstan-symfony": "^0.12.44",
"phpunit/phpunit": "^9.5.10",
"phpunit/phpunit": "^9.5.13",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"rector/rector": "^0.11.60",
"symfony/cache": "^5.4",
Expand Down
34 changes: 34 additions & 0 deletions doc/scheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The [Scheduler](../src/Scheduler.php) is the main entrypoint for every action re
- [API](#api)
- [Asynchronous API](#asynchronous-api)
- [Lazy scheduler](#lazy-scheduler)
- [SchedulerAware](#scheduleraware)

## API

Expand Down Expand Up @@ -159,3 +160,36 @@ The [LazyScheduler](../src/LazyScheduler.php) act as a wrapper around
the default `Scheduler`, when enabled via the configuration, each action performed in a "lazy" approach.

The scheduler still available to injection via [SchedulerInterface](../src/SchedulerInterface.php).

## SchedulerAware

_Introduced in `0.9`_

The [SchedulerAwareInterface](../src/SchedulerAwareInterface.php) act as an entrypoint used to schedule tasks directly
from classes that implements it.

For example, this interface can help schedule tasks from within a command:

```php
<?php

declare(strict_types=1);

namespace App\Command;

# ...

final class FooCommand extends Command implements SchedulerAwareInterface
{
protected static $defaultName = 'app:foo';

public function schedule(SchedulerInterface $scheduler): void
{
$scheduler->schedule(new NullTask('foo_command'));
}

# ...
}
```

_The task will be scheduled once you can `bin/console app:foo`._
22 changes: 22 additions & 0 deletions docker-compose.override.yml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '3.7'

services:
php:
volumes:
- code:/srv/app

volumes:
code:

x-mutagen:
sync:
defaults:
permissions:
defaultDirectoryMode: 0755
defaultFileMode: 0644
ignore:
vcs: true
code:
alpha: "."
beta: "volume://code"
mode: "two-way-resolved"
4 changes: 2 additions & 2 deletions infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"configDir": "."
},
"logs": {
"badge": {
"branch": "main"
"stryker": {
"badge": "main"
},
"text": "infection.log",
"summary": "summary.log"
Expand Down
4 changes: 4 additions & 0 deletions src/DependencyInjection/SchedulerBundleExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
use SchedulerBundle\SchedulePolicy\SchedulePolicyOrchestrator;
use SchedulerBundle\SchedulePolicy\SchedulePolicyOrchestratorInterface;
use SchedulerBundle\Scheduler;
use SchedulerBundle\SchedulerAwareInterface;
use SchedulerBundle\SchedulerInterface;
use SchedulerBundle\Serializer\AccessLockBagNormalizer;
use SchedulerBundle\Serializer\NotificationTaskBagNormalizer;
Expand Down Expand Up @@ -225,6 +226,7 @@ private function registerAutoConfigure(ContainerBuilder $container): void
$container->registerForAutoconfiguration(BuilderInterface::class)->addTag(self::SCHEDULER_TASK_BUILDER_TAG);
$container->registerForAutoconfiguration(ProbeInterface::class)->addTag(self::SCHEDULER_PROBE_TAG);
$container->registerForAutoconfiguration(TaskBagInterface::class)->addTag('scheduler.task_bag');
$container->registerForAutoconfiguration(SchedulerAwareInterface::class)->addTag('scheduler.entry_point');
}

private function registerConfigurationFactories(ContainerBuilder $container): void
Expand Down Expand Up @@ -438,6 +440,7 @@ private function registerScheduler(ContainerBuilder $container): void
->addTag('monolog.logger', [
'channel' => 'scheduler',
])
->addTag('container.hot_path')
->addTag('container.preload', [
'class' => Scheduler::class,
])
Expand Down Expand Up @@ -1017,6 +1020,7 @@ private function registerWorker(ContainerBuilder $container): void
->addTag('monolog.logger', [
'channel' => 'scheduler',
])
->addTag('container.hot_path')
->addTag('container.preload', [
'class' => Worker::class,
])
Expand Down
19 changes: 17 additions & 2 deletions src/DependencyInjection/SchedulerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@

namespace SchedulerBundle\DependencyInjection;

use SchedulerBundle\SchedulerInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use function array_keys;

/**
* @author Guillaume Loulier <contact@guillaumeloulier.fr>
*/
final class SchedulerPass implements CompilerPassInterface
{
public function __construct(private string $schedulerExtraTag = 'scheduler.extra')
{
public function __construct(
private string $schedulerExtraTag = 'scheduler.extra',
private string $schedulerEntryPointTag = 'scheduler.entry_point'
) {
}

/**
Expand All @@ -22,6 +27,7 @@ public function __construct(private string $schedulerExtraTag = 'scheduler.extra
public function process(ContainerBuilder $container): void
{
$this->registerExtra($container);
$this->registerSchedulerEntrypoint($container);
}

private function registerExtra(ContainerBuilder $container): void
Expand All @@ -36,4 +42,13 @@ private function registerExtra(ContainerBuilder $container): void
$container->getDefinition($service)->addTag($args[0]['tag']);
}
}

private function registerSchedulerEntrypoint(ContainerBuilder $container): void
{
foreach (array_keys($container->findTaggedServiceIds($this->schedulerEntryPointTag)) as $service) {
$container->getDefinition($service)->addMethodCall('schedule', [
new Reference(SchedulerInterface::class),
]);
}
}
}
8 changes: 1 addition & 7 deletions src/Transport/Dsn.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,8 @@
*/
final class Dsn
{
/**
* @var array
*/
private array $options;

public function __construct(private string $scheme, private string $host, private ?string $path = null, private ?string $user = null, private ?string $password = null, private ?int $port = null, array $options = [], private ?string $root = null)
public function __construct(private string $scheme, private string $host, private ?string $path = null, private ?string $user = null, private ?string $password = null, private ?int $port = null, private array $options = [], private ?string $root = null)
{
$this->options = $options;
}

public static function fromString(string $dsn): self
Expand Down
1 change: 1 addition & 0 deletions tests/.assets/_symfony_scheduler_/bar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"body":{"message":{"class":"stdClass","payload":[]},"name":"bar","arrivalTime":null,"beforeScheduling":null,"beforeSchedulingNotificationBag":null,"afterSchedulingNotificationBag":null,"beforeExecutingNotificationBag":null,"afterExecutingNotificationBag":null,"afterScheduling":null,"beforeExecuting":null,"afterExecuting":null,"description":null,"expression":"* * * * *","executionAbsoluteDeadline":null,"executionComputationTime":null,"executionDelay":null,"executionMemoryUsage":0,"executionPeriod":null,"executionRelativeDeadline":null,"executionStartDate":null,"executionEndDate":null,"executionStartTime":null,"executionEndTime":null,"accessLockBag":null,"lastExecution":null,"maxDuration":null,"maxExecutions":null,"maxRetries":null,"nice":null,"state":"enabled","executionState":null,"output":false,"priority":0,"queued":false,"scheduledAt":"2022-01-20 08:50:06.244581","singleRun":false,"tags":[],"timezone":"UTC","tracked":true},"taskInternalType":"SchedulerBundle\\Task\\MessengerTask"}
18 changes: 17 additions & 1 deletion tests/DependencyInjection/SchedulerBundleExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Tests\SchedulerBundle\DependencyInjection;

use Closure;
use Generator;
use PHPUnit\Framework\TestCase;
use Psr\EventDispatcher\EventDispatcherInterface;
Expand Down Expand Up @@ -85,6 +86,7 @@
use SchedulerBundle\SchedulePolicy\SchedulePolicyOrchestrator;
use SchedulerBundle\SchedulePolicy\SchedulePolicyOrchestratorInterface;
use SchedulerBundle\Scheduler;
use SchedulerBundle\SchedulerAwareInterface;
use SchedulerBundle\SchedulerInterface;
use SchedulerBundle\Serializer\AccessLockBagNormalizer;
use SchedulerBundle\Serializer\NotificationTaskBagNormalizer;
Expand Down Expand Up @@ -216,6 +218,8 @@ public function testInterfacesForAutoconfigureAreRegistered(): void
self::assertTrue($autoconfigurationInterfaces[ProbeInterface::class]->hasTag('scheduler.probe'));
self::assertArrayHasKey(TaskBagInterface::class, $autoconfigurationInterfaces);
self::assertTrue($autoconfigurationInterfaces[TaskBagInterface::class]->hasTag('scheduler.task_bag'));
self::assertArrayHasKey(SchedulerAwareInterface::class, $autoconfigurationInterfaces);
self::assertTrue($autoconfigurationInterfaces[SchedulerAwareInterface::class]->hasTag('scheduler.entry_point'));
}

public function testConfigurationFactoriesAreRegistered(): void
Expand Down Expand Up @@ -449,8 +453,10 @@ public function testSchedulerIsRegistered(): void
self::assertSame(MessageBusInterface::class, (string) $container->getDefinition(Scheduler::class)->getArgument(4));
self::assertSame(ContainerInterface::NULL_ON_INVALID_REFERENCE, $container->getDefinition(Scheduler::class)->getArgument(4)->getInvalidBehavior());
self::assertFalse($container->getDefinition(Scheduler::class)->isPublic());
self::assertCount(3, $container->getDefinition(Scheduler::class)->getTags());
self::assertTrue($container->getDefinition(Scheduler::class)->hasTag('monolog.logger'));
self::assertSame('scheduler', $container->getDefinition(Scheduler::class)->getTag('monolog.logger')[0]['channel']);
self::assertTrue($container->getDefinition(Scheduler::class)->hasTag('container.hot_path'));
self::assertTrue($container->getDefinition(Scheduler::class)->hasTag('container.preload'));
self::assertSame(Scheduler::class, $container->getDefinition(Scheduler::class)->getTag('container.preload')[0]['class']);
}
Expand Down Expand Up @@ -1123,8 +1129,10 @@ public function testWorkerIsRegistered(): void
self::assertInstanceOf(Reference::class, $container->getDefinition(Worker::class)->getArgument(6));
self::assertSame(LoggerInterface::class, (string) $container->getDefinition(Worker::class)->getArgument(6));
self::assertSame(ContainerInterface::NULL_ON_INVALID_REFERENCE, $container->getDefinition(Worker::class)->getArgument(6)->getInvalidBehavior());
self::assertCount(4, $container->getDefinition(Worker::class)->getTags());
self::assertTrue($container->getDefinition(Worker::class)->hasTag('monolog.logger'));
self::assertSame('scheduler', $container->getDefinition(Worker::class)->getTag('monolog.logger')[0]['channel']);
self::assertTrue($container->getDefinition(Worker::class)->hasTag('container.hot_path'));
self::assertTrue($container->getDefinition(Worker::class)->hasTag('container.preload'));
self::assertSame(Worker::class, $container->getDefinition(Worker::class)->getTag('container.preload')[0]['class']);

Expand Down Expand Up @@ -1752,12 +1760,20 @@ public function provideDoctrineDsn(): Generator
/**
* @param array<string, mixed> $configuration
*/
private function getContainer(array $configuration = []): ContainerBuilder
private function getContainer(array $configuration = [], Closure $extraDefinitions = null, Closure $extraPasses = null): ContainerBuilder
{
$containerBuilder = new ContainerBuilder();
$containerBuilder->registerExtension(new SchedulerBundleExtension());
$containerBuilder->loadFromExtension('scheduler_bundle', $configuration);

if ($extraDefinitions instanceof Closure) {
$extraDefinitions($containerBuilder);
}

if ($extraPasses instanceof Closure) {
$extraPasses($containerBuilder);
}

$containerBuilder->getCompilerPassConfig()->setOptimizationPasses([new ResolveChildDefinitionsPass()]);
$containerBuilder->getCompilerPassConfig()->setRemovingPasses([]);
$containerBuilder->getCompilerPassConfig()->setAfterRemovingPasses([]);
Expand Down
64 changes: 64 additions & 0 deletions tests/SchedulerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@
use SchedulerBundle\Serializer\TaskNormalizer;
use SchedulerBundle\Task\LazyTask;
use SchedulerBundle\Task\LazyTaskList;
use SchedulerBundle\Task\MessengerTask;
use SchedulerBundle\Task\NullTask;
use SchedulerBundle\Task\TaskExecutionTracker;
use SchedulerBundle\Task\TaskList;
use SchedulerBundle\TaskBag\NotificationTaskBag;
use SchedulerBundle\Transport\FilesystemTransport;
use SchedulerBundle\Transport\TransportInterface;
use SchedulerBundle\Worker\Worker;
use SchedulerBundle\Worker\WorkerConfiguration;
Expand All @@ -47,6 +49,7 @@
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\InMemoryStore;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\MessageBusInterface;
use SchedulerBundle\Scheduler;
use SchedulerBundle\Task\ShellTask;
Expand Down Expand Up @@ -415,6 +418,24 @@ public function testTaskCanBeScheduledWithEventDispatcherAndMessageBus(TaskInter
self::assertInstanceOf(LazyTaskList::class, $scheduler->getTasks(true));
}

/**
* @throws Exception|Throwable {@see Scheduler::__construct()}
* @throws Throwable {@see SchedulerInterface::schedule()}
*
* @dataProvider provideTransports
*/
public function testMessengerTaskCanBeScheduledWithMessageBus(TransportInterface $transport): void
{
$scheduler = new Scheduler('UTC', $transport, new SchedulerMiddlewareStack(), new EventDispatcher(), new MessageBus());

$scheduler->schedule(new MessengerTask('bar', new stdClass()));

self::assertCount(1, $scheduler->getTasks());
self::assertInstanceOf(TaskList::class, $scheduler->getTasks());
self::assertCount(1, $scheduler->getTasks(true));
self::assertInstanceOf(LazyTaskList::class, $scheduler->getTasks(true));
}

/**
* @throws Throwable {@see Scheduler::__construct()}
*/
Expand Down Expand Up @@ -1693,4 +1714,47 @@ public function provideTasks(): Generator
new ShellTask('Foo', ['echo', 'Symfony']),
];
}

/**
* @return Generator<array<int, TransportInterface>>
*/
public function provideTransports(): Generator
{
$objectNormalizer = new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]));
$notificationTaskBagNormalizer = new NotificationTaskBagNormalizer($objectNormalizer);
$lockTaskBagNormalizer = new AccessLockBagNormalizer($objectNormalizer);

$serializer = new Serializer([
$notificationTaskBagNormalizer,
new TaskNormalizer(
new DateTimeNormalizer(),
new DateTimeZoneNormalizer(),
new DateIntervalNormalizer(),
$objectNormalizer,
$notificationTaskBagNormalizer,
$lockTaskBagNormalizer,
),
new DateTimeNormalizer(),
new DateTimeZoneNormalizer(),
new DateIntervalNormalizer(),
new JsonSerializableNormalizer(),
$objectNormalizer,
], [new JsonEncoder()]);
$objectNormalizer->setSerializer($serializer);

yield 'InMemoryTransport' => [
new InMemoryTransport([
'execution_mode' => 'first_in_first_out',
], new SchedulePolicyOrchestrator([
new FirstInFirstOutPolicy(),
])),
];
yield 'FilesystemTransport' => [
new FilesystemTransport([
'execution_mode' => 'first_in_first_out',
], $serializer, new SchedulePolicyOrchestrator([
new FirstInFirstOutPolicy(),
]), __DIR__ . '/.assets'),
];
}
}

0 comments on commit 059c742

Please sign in to comment.