Skip to content

Commit b9fe153

Browse files
authored
Fixed checking Symfony custom services from bundle config (#134)
* Fixed checking Symfony custom services from bundle config * Add missing comments for introduced compiler passes * Fixed missing compiler pass in bundle test
1 parent 0b742e3 commit b9fe153

12 files changed

+564
-334
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection\CompilerPass;
6+
7+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\Exception\LogicException;
10+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
11+
use Yokai\Batch\Launcher\JobLauncherInterface;
12+
13+
/**
14+
* This compiler pass ensure that service behind {@see JobLauncherInterface} is having the proper interface.
15+
*/
16+
final class ConfigureLauncherPass implements CompilerPassInterface
17+
{
18+
public function process(ContainerBuilder $container): void
19+
{
20+
$templatingActualService = (string)$container->getAlias(JobLauncherInterface::class);
21+
22+
try {
23+
$launcherService = $container->findDefinition($templatingActualService);
24+
} catch (ServiceNotFoundException $exception) {
25+
throw new LogicException(
26+
message: \sprintf(
27+
'Job launcher service "%s" does not exists.',
28+
$templatingActualService,
29+
),
30+
previous: $exception,
31+
);
32+
}
33+
34+
$jobLauncherServiceClass = (string)$launcherService->getClass();
35+
if (!\is_a($jobLauncherServiceClass, JobLauncherInterface::class, true)) {
36+
throw new LogicException(
37+
\sprintf(
38+
'Job launcher service "%s" must implements interface "%s".',
39+
$templatingActualService,
40+
JobLauncherInterface::class,
41+
),
42+
);
43+
}
44+
}
45+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection\CompilerPass;
6+
7+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\Exception\LogicException;
10+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
11+
use Yokai\Batch\Storage\JobExecutionStorageInterface;
12+
use Yokai\Batch\Storage\ListableJobExecutionStorageInterface;
13+
use Yokai\Batch\Storage\QueryableJobExecutionStorageInterface;
14+
15+
/**
16+
* This compiler pass ensure that service behind {@see JobExecutionStorageInterface} is having the proper interface.
17+
* Also, if that service implements some optional interfaces, we create some autowiring alias for these.
18+
*/
19+
final class ConfigureStoragePass implements CompilerPassInterface
20+
{
21+
public function process(ContainerBuilder $container): void
22+
{
23+
$jobExecutionStorageActualService = (string)$container->getAlias(JobExecutionStorageInterface::class);
24+
25+
try {
26+
$jobExecutionStorageService = $container->getDefinition($jobExecutionStorageActualService);
27+
} catch (ServiceNotFoundException $exception) {
28+
throw new LogicException(
29+
message: \sprintf(
30+
'Job execution storage service "%s" does not exists.',
31+
$jobExecutionStorageActualService,
32+
),
33+
previous: $exception,
34+
);
35+
}
36+
37+
$jobExecutionStorageServiceClass = (string)$jobExecutionStorageService->getClass();
38+
if (!\is_a($jobExecutionStorageServiceClass, JobExecutionStorageInterface::class, true)) {
39+
throw new LogicException(
40+
\sprintf(
41+
'Job execution storage service "%s" must implements interface "%s".',
42+
$jobExecutionStorageActualService,
43+
JobExecutionStorageInterface::class,
44+
),
45+
);
46+
}
47+
48+
$optionalInterfaces = [
49+
ListableJobExecutionStorageInterface::class,
50+
QueryableJobExecutionStorageInterface::class,
51+
];
52+
foreach ($optionalInterfaces as $interface) {
53+
if (\is_a($jobExecutionStorageServiceClass, $interface, true)) {
54+
$container
55+
->setAlias($interface, $jobExecutionStorageActualService)
56+
->setPublic(true)
57+
;
58+
}
59+
}
60+
}
61+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection\CompilerPass;
6+
7+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\Exception\LogicException;
10+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
11+
use Yokai\Batch\Bridge\Symfony\Framework\UserInterface\Templating\TemplatingInterface;
12+
13+
/**
14+
* This compiler pass ensure that service behind {@see TemplatingInterface} is having the proper interface.
15+
*/
16+
final class ConfigureTemplatingPass implements CompilerPassInterface
17+
{
18+
public function process(ContainerBuilder $container): void
19+
{
20+
$templatingActualService = (string)$container->getAlias(TemplatingInterface::class);
21+
22+
try {
23+
$templatingService = $container->findDefinition($templatingActualService);
24+
} catch (ServiceNotFoundException $exception) {
25+
throw new LogicException(
26+
message: \sprintf(
27+
'UI templating service "%s" does not exists.',
28+
$templatingActualService,
29+
),
30+
previous: $exception,
31+
);
32+
}
33+
34+
$templatingServiceClass = (string)$templatingService->getClass();
35+
if (!\is_a($templatingServiceClass, TemplatingInterface::class, true)) {
36+
throw new LogicException(
37+
\sprintf(
38+
'UI templating service "%s" must implements interface "%s".',
39+
$templatingActualService,
40+
TemplatingInterface::class,
41+
),
42+
);
43+
}
44+
}
45+
}

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

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

7-
use Symfony\Component\DependencyInjection\ContainerBuilder;
87
use Symfony\Component\DependencyInjection\Definition;
98
use Symfony\Component\DependencyInjection\Exception\LogicException;
109
use Symfony\Component\DependencyInjection\Reference;
@@ -24,7 +23,7 @@ final class JobLauncherDefinitionFactory
2423
/**
2524
* Build a service definition from DSN string.
2625
*/
27-
public static function fromDsn(ContainerBuilder $container, string $dsn): Definition
26+
public static function fromDsn(string $dsn): Definition|Reference
2827
{
2928
$dsnParts = \parse_url($dsn);
3029
$launcherType = $dsnParts['scheme'] ?? null;
@@ -35,7 +34,7 @@ public static function fromDsn(ContainerBuilder $container, string $dsn): Defini
3534
'simple' => self::simple(),
3635
'console' => self::console($launcherConfig),
3736
'messenger' => self::messenger(),
38-
'service' => self::service($container, $launcherConfig),
37+
'service' => self::service($launcherConfig),
3938
default => throw new LogicException('Unsupported job launcher type "' . $launcherType . '".'),
4039
};
4140
}
@@ -78,12 +77,12 @@ private static function messenger(): Definition
7877
/**
7978
* @param array<string, string> $config
8079
*/
81-
private static function service(ContainerBuilder $container, array $config): Definition
80+
private static function service(array $config): Reference
8281
{
8382
$service = $config['service'] ?? throw new LogicException(
8483
'Missing "service" parameter to configure the job launcher.',
8584
);
8685

87-
return $container->getDefinition($service);
86+
return new Reference($service);
8887
}
8988
}

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

Lines changed: 16 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
use Symfony\Component\Config\Loader as ConfigLoader;
1212
use Symfony\Component\Config\Loader\LoaderInterface;
1313
use Symfony\Component\DependencyInjection\ContainerBuilder;
14+
use Symfony\Component\DependencyInjection\Definition;
1415
use Symfony\Component\DependencyInjection\Exception\LogicException;
15-
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1616
use Symfony\Component\DependencyInjection\Loader as DependencyInjectionLoader;
1717
use Symfony\Component\DependencyInjection\Reference;
1818
use Symfony\Component\Form\AbstractType;
@@ -27,8 +27,6 @@
2727
use Yokai\Batch\Launcher\JobLauncherInterface;
2828
use Yokai\Batch\Storage\FilesystemJobExecutionStorage;
2929
use Yokai\Batch\Storage\JobExecutionStorageInterface;
30-
use Yokai\Batch\Storage\ListableJobExecutionStorageInterface;
31-
use Yokai\Batch\Storage\QueryableJobExecutionStorageInterface;
3230

3331
/**
3432
* Dependency injection extension for yokai/batch Symfony Bundle.
@@ -123,47 +121,10 @@ private function configureStorage(ContainerBuilder $container, array $config): v
123121
$defaultStorage = 'yokai_batch.storage.filesystem';
124122
}
125123

126-
try {
127-
$defaultStorageDefinition = $container->getDefinition($defaultStorage);
128-
} catch (ServiceNotFoundException $exception) {
129-
throw new LogicException(
130-
\sprintf('Configured default job execution storage service "%s" does not exists.', $defaultStorage),
131-
0,
132-
$exception
133-
);
134-
}
135-
136-
$defaultStorageClass = $defaultStorageDefinition->getClass();
137-
if ($defaultStorageClass === null) {
138-
throw new LogicException(
139-
\sprintf('Job execution storage service "%s", has no class.', $defaultStorage)
140-
);
141-
}
142-
143-
$interfaces = [
144-
JobExecutionStorageInterface::class => true,
145-
ListableJobExecutionStorageInterface::class => false,
146-
QueryableJobExecutionStorageInterface::class => false,
147-
];
148-
foreach ($interfaces as $interface => $required) {
149-
if (!\is_a($defaultStorageClass, $interface, true)) {
150-
if ($required) {
151-
throw new LogicException(
152-
\sprintf(
153-
'Job execution storage service "%s", is of class "%s", and must implements interface "%s".',
154-
$defaultStorage,
155-
$defaultStorageClass,
156-
$interface
157-
)
158-
);
159-
}
160-
continue;
161-
}
162-
$container
163-
->setAlias($interface, $defaultStorage)
164-
->setPublic(true)
165-
;
166-
}
124+
$container
125+
->setAlias(JobExecutionStorageInterface::class, $defaultStorage)
126+
->setPublic(true)
127+
;
167128
}
168129

169130
/**
@@ -179,17 +140,24 @@ private function configureLauncher(ContainerBuilder $container, array $config):
179140
));
180141
}
181142

143+
$launcherIdPerLauncherName = [];
182144
foreach ($config['launchers'] as $name => $dsn) {
183-
$definition = JobLauncherDefinitionFactory::fromDsn($container, $dsn);
184-
$launcherId = 'yokai_batch.job_launcher.' . $name;
185-
$container->setDefinition($launcherId, $definition);
145+
$definitionOrReference = JobLauncherDefinitionFactory::fromDsn($dsn);
146+
if ($definitionOrReference instanceof Definition) {
147+
$launcherId = 'yokai_batch.job_launcher.' . $name;
148+
$container->setDefinition($launcherId, $definitionOrReference);
149+
} else {
150+
$launcherId = (string)$definitionOrReference;
151+
}
152+
153+
$launcherIdPerLauncherName[$name] = $launcherId;
186154
$parameterName = $name . 'JobLauncher';
187155
$container->registerAliasForArgument($launcherId, LoggerInterface::class, $parameterName);
188156
}
189157

190158
$container->setAlias(
191159
JobLauncherInterface::class,
192-
'yokai_batch.job_launcher.' . $config['default'],
160+
$launcherIdPerLauncherName[$config['default']],
193161
);
194162
}
195163

@@ -240,25 +208,6 @@ private function configureUserInterface(ContainerBuilder $container, LoaderInter
240208

241209
$templating = $config['templating'];
242210
if ($templating['service'] !== null) {
243-
try {
244-
$templatingClass = $container->getDefinition($templating['service'])->getClass();
245-
if ($templatingClass === null || !\is_a($templatingClass, TemplatingInterface::class, true)) {
246-
throw new LogicException(
247-
\sprintf(
248-
'Configured UI templating service "%s" must implements interface "%s".',
249-
$templating['service'],
250-
TemplatingInterface::class,
251-
),
252-
);
253-
}
254-
} catch (ServiceNotFoundException $exception) {
255-
throw new LogicException(
256-
\sprintf('Configured UI templating service "%s" does not exists.', $templating['service']),
257-
0,
258-
$exception
259-
);
260-
}
261-
262211
$container->setAlias(TemplatingInterface::class, $templating['service']);
263212
} elseif ($templating['prefix'] !== null) {
264213
$container->register('yokai_batch.ui.templating', ConfigurableTemplating::class)

src/batch-symfony-framework/src/YokaiBatchBundle.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
use Symfony\Component\DependencyInjection\ContainerBuilder;
88
use Symfony\Component\HttpKernel\Bundle\Bundle;
9+
use Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection\CompilerPass\ConfigureLauncherPass;
10+
use Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection\CompilerPass\ConfigureStoragePass;
11+
use Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection\CompilerPass\ConfigureTemplatingPass;
912
use Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection\CompilerPass\RegisterJobsCompilerPass;
1013

1114
/**
@@ -15,6 +18,9 @@ final class YokaiBatchBundle extends Bundle
1518
{
1619
public function build(ContainerBuilder $container): void
1720
{
21+
$container->addCompilerPass(new ConfigureLauncherPass());
22+
$container->addCompilerPass(new ConfigureStoragePass());
23+
$container->addCompilerPass(new ConfigureTemplatingPass());
1824
$container->addCompilerPass(new RegisterJobsCompilerPass());
1925
}
2026
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DependencyInjection\CompilerPass;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\Exception\LogicException;
10+
use Yokai\Batch\Bridge\Symfony\Framework\DependencyInjection\CompilerPass\ConfigureLauncherPass;
11+
use Yokai\Batch\Launcher\JobLauncherInterface;
12+
use Yokai\Batch\Launcher\SimpleJobLauncher;
13+
14+
final class ConfigureLauncherPassTest extends TestCase
15+
{
16+
public function testNominal(): void
17+
{
18+
$this->process(function (ContainerBuilder $container) {
19+
$container->register('yokai_batch.launcher.simple', SimpleJobLauncher::class);
20+
$container->setAlias(JobLauncherInterface::class, 'yokai_batch.launcher.simple');
21+
});
22+
23+
self::assertTrue(true, 'No exception was raised');
24+
}
25+
26+
public function testMissingService(): void
27+
{
28+
$this->expectExceptionObject(
29+
new LogicException('Job launcher service "app.yokai_batch_job_launcher" does not exists.')
30+
);
31+
$this->process(function (ContainerBuilder $container) {
32+
$container->setAlias(JobLauncherInterface::class, 'app.yokai_batch_job_launcher');
33+
});
34+
}
35+
36+
public function testInvalidService(): void
37+
{
38+
$this->expectExceptionObject(
39+
new LogicException(
40+
'Job launcher service "app.yokai_batch_job_launcher" must implements interface "' . JobLauncherInterface::class . '".',
41+
),
42+
);
43+
$this->process(function (ContainerBuilder $container) {
44+
$container->register('app.yokai_batch_job_launcher', self::class);
45+
$container->setAlias(JobLauncherInterface::class, 'app.yokai_batch_job_launcher');
46+
});
47+
}
48+
49+
private function process(\Closure $configure): void
50+
{
51+
$container = new ContainerBuilder();
52+
$configure($container);
53+
(new ConfigureLauncherPass())->process($container);
54+
}
55+
}

0 commit comments

Comments
 (0)