Skip to content

Commit ba69653

Browse files
committed
Add job to job launcher routing
1 parent 1303c6c commit ba69653

File tree

10 files changed

+339
-29
lines changed

10 files changed

+339
-29
lines changed

docs/docs/bridges/symfony-framework.rst

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ you will be able to register these using configuration:
1616
1717
# config/packages/yokai_batch.yaml
1818
yokai_batch:
19-
launcher:
20-
default: simple
21-
launchers:
22-
simple: ...
23-
async: ...
19+
launcher:
20+
default: simple
21+
launchers:
22+
simple: ...
23+
async: ...
2424
2525
.. note::
2626
If you do not configure anything here, you will be using the
@@ -46,6 +46,31 @@ All ``launchers`` are configured using a DSN, every scheme has it’s own associ
4646

4747
* ``service``: the id of the service to use (required, an exception will be thrown otherwise)
4848

49+
50+
| You might define multiple job launchers, and will want to configure the relation between job and launcher.
51+
| For instance, you might prefer running some jobs with an async job launcher, but not all.
52+
| You can configure this routing like the following:
53+
54+
.. code-block:: yaml
55+
56+
# config/packages/yokai_batch.yaml
57+
yokai_batch:
58+
launcher:
59+
default: simple
60+
launchers:
61+
simple: simple://simple
62+
console: console://console
63+
routing:
64+
export_job_name: simple
65+
import_job_name: console
66+
67+
.. note::
68+
It is not required to configure every single job in the ``routing``.
69+
The ``default`` will be the fallback for all jobs you did not not configured in the ``routing``.
70+
71+
.. note::
72+
If you configure ``config.launcher.routing``, it will replace your configured default from autowiring perspective.
73+
4974
.. seealso::
5075
| :doc:`What is a job launcher? </core-concepts/job-launcher>`
5176
@@ -64,12 +89,12 @@ You can have only one storage for your ``JobExecution``, and you have several op
6489
6590
# config/packages/yokai_batch.yaml
6691
yokai_batch:
67-
storage:
68-
filesystem: ~
69-
# Or with yokai/batch-doctrine-dbal (& doctrine/dbal)
70-
# dbal: ~
71-
# Or with a service of yours
72-
# service: ~
92+
storage:
93+
filesystem: ~
94+
# Or with yokai/batch-doctrine-dbal (& doctrine/dbal)
95+
# dbal: ~
96+
# Or with a service of yours
97+
# service: ~
7398
7499
.. note::
75100
| The default storage is ``filesystem``, because it only requires a writeable filesystem.
@@ -98,11 +123,11 @@ You can configure what your id will be like:
98123
99124
# config/packages/yokai_batch.yaml
100125
yokai_batch:
101-
id: uniqid
102-
# Or with yokai/batch-symfony-uid (& symfony/uid)
103-
# id: symfony.uuid.random
104-
# id: symfony.uuid.time
105-
# id: symfony.ulid
126+
id: uniqid
127+
# Or with yokai/batch-symfony-uid (& symfony/uid)
128+
# id: symfony.uuid.random
129+
# id: symfony.uuid.time
130+
# id: symfony.ulid
106131
107132
User interface to visualize ``JobExecution``
108133
------------------------------------------------------------

docs/docs/bridges/symfony-messenger.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ and will not want to run all jobs on the same transport.
6161
6262
# config/packages/yokai_batch.yaml
6363
yokai_batch:
64-
launchers:
65-
messenger:
66-
routing:
67-
export_job_name: async_with_low_priority
68-
import_job_name: async_with_high_priority
64+
launchers:
65+
messenger:
66+
routing:
67+
export_job_name: async_with_low_priority
68+
import_job_name: async_with_high_priority
6969
7070
.. seealso::
7171
| :doc:`What is a job launcher? </core-concepts/job-launcher>`

docs/docs/core-concepts/job-launcher.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ What types of launcher exists?
6565
* `SimpleJobLauncher <https://github.com/yokai-php/batch/tree/0.x/src/Launcher/SimpleJobLauncher.php>`__:
6666
execute the job directly in the same PHP process.
6767

68+
* `RoutingJobLauncher <https://github.com/yokai-php/batch/tree/0.x/src/Launcher/RoutingJobLauncher.php>`__:
69+
pick the appropriate job launcher for each job, based on the configuration you provide.
70+
6871
**Launchers from bridges:**
6972

7073
* From ``symfony/console`` bridge:

src/batch-symfony-framework/src/DependencyInjection/Configuration.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* @phpstan-type LauncherConfig array{
3434
* default: string|null,
3535
* launchers: array<string, string>,
36+
* routing?: array<string, string>,
3637
* messenger?: array{
3738
* routing: array<string, string>,
3839
* },
@@ -138,6 +139,11 @@ private function launcher(): ArrayNodeDefinition
138139
->end()
139140
->end()
140141
->end()
142+
->arrayNode('routing')
143+
->normalizeKeys(false)
144+
->useAttributeAsKey('name')
145+
->scalarPrototype()->end()
146+
->end()
141147
->arrayNode('messenger')
142148
->children()
143149
->arrayNode('routing')

src/batch-symfony-framework/src/DependencyInjection/JobLauncherDefinitionFactory.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection;
66

7+
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
79
use Symfony\Component\DependencyInjection\Definition;
810
use Symfony\Component\DependencyInjection\Exception\LogicException;
911
use Symfony\Component\DependencyInjection\Reference;
@@ -13,6 +15,7 @@
1315
use Yokai\Batch\Bridge\Symfony\Messenger\DispatchMessageJobLauncher;
1416
use Yokai\Batch\Bridge\Symfony\Messenger\MessengerJobsConfiguration;
1517
use Yokai\Batch\Launcher\JobLauncherInterface;
18+
use Yokai\Batch\Launcher\RoutingJobLauncher;
1619
use Yokai\Batch\Launcher\SimpleJobLauncher;
1720
use Yokai\Batch\Storage\JobExecutionStorageInterface;
1821

@@ -40,6 +43,25 @@ public static function fromDsn(string $dsn): Definition|Reference
4043
};
4144
}
4245

46+
/**
47+
* Create the {@see RoutingJobLauncher} service definition when has configuration for it.
48+
*
49+
* @param array<string, string> $launchers
50+
* @param array<string, string> $routing
51+
*/
52+
public static function routing(
53+
ContainerBuilder $container,
54+
array $launchers,
55+
string $default,
56+
array $routing,
57+
): Definition {
58+
return new Definition(RoutingJobLauncher::class, [
59+
'$launchers' => ServiceLocatorTagPass::register($container, $launchers),
60+
'$default' => new Reference($default),
61+
'$routing' => $routing,
62+
]);
63+
}
64+
4365
private static function simple(): Definition
4466
{
4567
return new Definition(SimpleJobLauncher::class, [

src/batch-symfony-framework/src/DependencyInjection/YokaiBatchExtension.php

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ private function configureLauncher(ContainerBuilder $container, array $config):
146146

147147
$container->setParameter('yokai_batch.launcher.messenger_routing', $config['messenger']['routing'] ?? []);
148148

149-
$launcherIdPerLauncherName = [];
149+
$idPerLauncherName = [];
150150
foreach ($config['launchers'] as $name => $dsn) {
151151
$definitionOrReference = JobLauncherDefinitionFactory::fromDsn($dsn);
152152
if ($definitionOrReference instanceof Definition) {
@@ -156,15 +156,23 @@ private function configureLauncher(ContainerBuilder $container, array $config):
156156
$launcherId = (string)$definitionOrReference;
157157
}
158158

159-
$launcherIdPerLauncherName[$name] = $launcherId;
159+
$idPerLauncherName[$name] = $launcherId;
160160
$parameterName = $name . 'JobLauncher';
161161
$container->registerAliasForArgument($launcherId, JobLauncherInterface::class, $parameterName);
162162
}
163163

164-
$container->setAlias(
165-
JobLauncherInterface::class,
166-
$launcherIdPerLauncherName[$config['default']],
167-
);
164+
$default = $idPerLauncherName[$config['default']];
165+
166+
$routing = $config['routing'] ?? [];
167+
if ($routing !== []) {
168+
$container->setDefinition(
169+
$launcherId = 'yokai_batch.job_launcher.routing',
170+
JobLauncherDefinitionFactory::routing($container, $idPerLauncherName, $default, $routing),
171+
);
172+
$default = $launcherId;
173+
}
174+
175+
$container->setAlias(JobLauncherInterface::class, $default);
168176
}
169177

170178
/**

src/batch-symfony-framework/tests/DependencyInjection/YokaiBatchExtensionTest.php

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Yokai\Batch\Factory\JobExecutionParametersBuilderInterface;
3131
use Yokai\Batch\Factory\UniqidJobExecutionIdGenerator;
3232
use Yokai\Batch\Launcher\JobLauncherInterface;
33+
use Yokai\Batch\Launcher\RoutingJobLauncher;
3334
use Yokai\Batch\Launcher\SimpleJobLauncher;
3435
use Yokai\Batch\Storage\FilesystemJobExecutionStorage;
3536
use Yokai\Batch\Storage\JobExecutionStorageInterface;
@@ -77,14 +78,23 @@ public function storage(): \Generator
7778
/**
7879
* @dataProvider launcher
7980
*/
80-
public function testLauncher(array $config, \Closure|null $configure, array $launchers, string $default): void
81-
{
81+
public function testLauncher(
82+
array $config,
83+
\Closure|null $configure,
84+
array $launchers,
85+
string $default,
86+
\Closure|null $assert = null,
87+
): void {
8288
$container = $this->createContainer($config, $configure);
8389

8490
self::assertSame($default, (string)$container->getAlias(JobLauncherInterface::class));
8591
foreach ($launchers as $id => $class) {
8692
self::assertSame($class, $container->getDefinition($id)->getClass());
8793
}
94+
95+
if ($assert) {
96+
$assert($container);
97+
}
8898
}
8999

90100
public function launcher(): \Generator
@@ -112,6 +122,43 @@ public function launcher(): \Generator
112122
],
113123
'yokai_batch.job_launcher.messenger',
114124
];
125+
yield 'Messenger launcher routing' => [
126+
[
127+
'launcher' => [
128+
'default' => 'messenger',
129+
'launchers' => [
130+
'messenger' => 'messenger://messenger',
131+
],
132+
'messenger' => [
133+
'routing' => [
134+
'job1' => 'async',
135+
'job2' => 'sync',
136+
],
137+
],
138+
],
139+
],
140+
null,
141+
[
142+
'yokai_batch.job_launcher.messenger' => DispatchMessageJobLauncher::class,
143+
],
144+
'yokai_batch.job_launcher.messenger',
145+
function (ContainerBuilder $container) {
146+
$messengerJobsConfiguration = $container->getDefinition('yokai_batch.job_launcher.messenger')
147+
->getArgument('$messengerJobsConfiguration');
148+
self::assertInstanceOf(Definition::class, $messengerJobsConfiguration);
149+
self::assertSame(
150+
'%yokai_batch.launcher.messenger_routing%',
151+
$messengerJobsConfiguration->getArgument('$routing'),
152+
);
153+
self::assertSame(
154+
[
155+
'job1' => 'async',
156+
'job2' => 'sync',
157+
],
158+
$container->getParameter('yokai_batch.launcher.messenger_routing'),
159+
);
160+
},
161+
];
115162
yield 'Service launcher' => [
116163
[
117164
'launcher' => [
@@ -128,6 +175,39 @@ public function launcher(): \Generator
128175
['app.job_launcher' => BufferingJobLauncher::class],
129176
'app.job_launcher',
130177
];
178+
yield 'Routing launcher' => [
179+
[
180+
'launcher' => [
181+
'default' => 'simple',
182+
'launchers' => [
183+
'simple' => 'simple://simple',
184+
'messenger' => 'messenger://messenger',
185+
'console' => 'console://console',
186+
],
187+
'routing' => [
188+
'job1' => 'messenger',
189+
'job2' => 'console',
190+
],
191+
],
192+
],
193+
null,
194+
[
195+
'yokai_batch.job_launcher.simple' => SimpleJobLauncher::class,
196+
'yokai_batch.job_launcher.messenger' => DispatchMessageJobLauncher::class,
197+
'yokai_batch.job_launcher.console' => RunCommandJobLauncher::class,
198+
'yokai_batch.job_launcher.routing' => RoutingJobLauncher::class,
199+
],
200+
'yokai_batch.job_launcher.routing',
201+
function (ContainerBuilder $container) {
202+
self::assertSame(
203+
[
204+
'job1' => 'messenger',
205+
'job2' => 'console',
206+
],
207+
$container->getDefinition('yokai_batch.job_launcher.routing')->getArgument('$routing'),
208+
);
209+
},
210+
];
131211
}
132212

133213
/**
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yokai\Batch\Launcher;
6+
7+
use Psr\Container\ContainerInterface;
8+
use Yokai\Batch\Exception\UnexpectedValueException;
9+
use Yokai\Batch\JobExecution;
10+
11+
/**
12+
* This {@see JobLauncherInterface} is a proxy locator to other {@see JobLauncherInterface}.
13+
* It will allow you to configure the relation between job and launcher.
14+
* It requires a collection of {@see JobLauncherInterface} along with a default one, and some configuration.
15+
*
16+
* Whenever it is asked to launch a job, if that job is configured with a routing entry,
17+
* the corresponding {@see JobLauncherInterface} will be used.
18+
* Otherwise, this is the provided default {@see JobLauncherInterface} that will be used.
19+
*/
20+
final class RoutingJobLauncher implements JobLauncherInterface
21+
{
22+
public function __construct(
23+
private ContainerInterface $launchers,
24+
private JobLauncherInterface $default,
25+
/**
26+
* @var array<string, string>
27+
*/
28+
private array $routing,
29+
) {
30+
}
31+
32+
public function launch(string $name, array $configuration = []): JobExecution
33+
{
34+
$launcherName = $this->routing[$name] ?? null;
35+
if ($launcherName === null) {
36+
$launcher = $this->default;
37+
} else {
38+
$launcher = $this->launchers->get($launcherName);
39+
if (!$launcher instanceof JobLauncherInterface) {
40+
throw UnexpectedValueException::type(JobLauncherInterface::class, $launcher);
41+
}
42+
}
43+
44+
return $launcher->launch($name, $configuration);
45+
}
46+
}

0 commit comments

Comments
 (0)