Skip to content

Commit 78a6429

Browse files
authored
Merge pull request #18 from MacPaw/feat(PLATECO-1668)/move-messenger-transport-factory-decorator-to-package
feat(PLATECO-1668): add transport factory decorator
2 parents b7cde74 + 3fc5c6a commit 78a6429

9 files changed

Lines changed: 264 additions & 0 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @MacPaw/platform-backend-engineers

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
/phpunit.xml
88
.phpunit.result.cache
99
###< phpunit/phpunit ###
10+
/clover.xml

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
"dealerdirect/phpcodesniffer-composer-installer": true
3131
}
3232
},
33+
"suggest": {
34+
"symfony/doctrine-messenger": "Allow use doctrine as messenger transport"
35+
},
3336
"scripts": {
3437
"composer-validate": [
3538
"composer validate"

phpstan-baseline.neon

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
parameters:
2+
ignoreErrors:
3+
-
4+
message: "#^Class Macpaw\\\\SchemaContextBundle\\\\Messenger\\\\Transport\\\\DoctrineTransportFactoryDecorator implements generic interface Symfony\\\\Component\\\\Messenger\\\\Transport\\\\TransportFactoryInterface but does not specify its types\\: TTransport$#"
5+
count: 1
6+
path: src/Messenger/Transport/DoctrineTransportFactoryDecorator.php
7+
8+
-
9+
message: "#^Method Macpaw\\\\SchemaContextBundle\\\\Messenger\\\\Transport\\\\DoctrineTransportFactoryDecorator\\:\\:__construct\\(\\) has parameter \\$decoratedFactory with generic interface Symfony\\\\Component\\\\Messenger\\\\Transport\\\\TransportFactoryInterface but does not specify its types\\: TTransport$#"
10+
count: 1
11+
path: src/Messenger/Transport/DoctrineTransportFactoryDecorator.php
12+
13+
-
14+
message: "#^Method Macpaw\\\\SchemaContextBundle\\\\Messenger\\\\Transport\\\\DoctrineTransportFactoryDecorator\\:\\:createTransport\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
15+
count: 1
16+
path: src/Messenger/Transport/DoctrineTransportFactoryDecorator.php
17+
18+
-
19+
message: "#^Method Macpaw\\\\SchemaContextBundle\\\\Messenger\\\\Transport\\\\DoctrineTransportFactoryDecorator\\:\\:supports\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
20+
count: 1
21+
path: src/Messenger/Transport/DoctrineTransportFactoryDecorator.php

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
includes:
2+
- phpstan-baseline.neon
3+
14
parameters:
25
level: max
36
paths:
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Macpaw\SchemaContextBundle\DependencyInjection;
6+
7+
use Macpaw\SchemaContextBundle\Messenger\Transport\DoctrineTransportFactoryDecorator;
8+
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
9+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
10+
use Symfony\Component\DependencyInjection\ContainerBuilder;
11+
use Symfony\Component\DependencyInjection\Definition;
12+
use Symfony\Component\DependencyInjection\Reference;
13+
14+
final class SchemaContextCompilerPass implements CompilerPassInterface
15+
{
16+
public const TARGET_ID = 'messenger.transport.doctrine.factory';
17+
public const DECORATOR_ID = 'messenger.doctrine_transport_factory.decorator';
18+
19+
public function process(ContainerBuilder $container): void
20+
{
21+
if ($container->hasDefinition(self::TARGET_ID) === false) {
22+
return;
23+
}
24+
25+
$def = new Definition(DoctrineTransportFactoryDecorator::class);
26+
$def->setAutowired(true); // avoid pulling the chain or adding tags
27+
$def->setAutoconfigured(true);
28+
$def->setPublic(false);
29+
30+
// Decorate the *target* id; explicit inner id is "<decorator>.inner"
31+
$def->setDecoratedService(self::TARGET_ID, self::DECORATOR_ID . '.inner');
32+
33+
// Inject the inner/original factory + your resolver
34+
$def->setArgument('$decoratedFactory', new Reference(self::DECORATOR_ID . '.inner'));
35+
$def->setArgument('$baggageSchemaResolver', new Reference(BaggageSchemaResolver::class));
36+
$def->setArgument('$defaultSchema', $container->getParameter('schema_context.default_schema'));
37+
38+
$container->setDefinition(self::DECORATOR_ID, $def);
39+
}
40+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Macpaw\SchemaContextBundle\Messenger\Transport;
6+
7+
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
8+
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
9+
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
10+
use Symfony\Component\Messenger\Transport\TransportInterface;
11+
12+
/**
13+
* Decorator for DoctrineTransportFactory to support schema prefixes in table_name option.
14+
*
15+
* This decorator extends the default DoctrineTransportFactory to handle table names
16+
* that include schema prefixes using the BaggageSchemaResolver for dynamic schema detection.
17+
*/
18+
final class DoctrineTransportFactoryDecorator implements TransportFactoryInterface
19+
{
20+
public function __construct(
21+
private readonly TransportFactoryInterface $decoratedFactory,
22+
private readonly BaggageSchemaResolver $baggageSchemaResolver,
23+
private readonly string $defaultSchema = 'public',
24+
) {
25+
}
26+
27+
public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
28+
{
29+
// Get current schema from BaggageSchemaResolver
30+
$currentSchema = $this->baggageSchemaResolver->getSchema();
31+
32+
// If we have a schema and it's not the default 'public' schema, modify the table name
33+
if ($currentSchema !== null && $currentSchema !== $this->defaultSchema) {
34+
$originalTableName = sprintf(
35+
'"%s"."%s"',
36+
$currentSchema,
37+
$options['table_name'] ?? 'messenger_messages',
38+
);
39+
40+
// Create transport with schema-prefixed table name
41+
$options['table_name'] = $originalTableName;
42+
}
43+
44+
// Create transport with the original factory
45+
return $this->decoratedFactory->createTransport($dsn, $options, $serializer);
46+
}
47+
48+
public function supports(string $dsn, array $options): bool
49+
{
50+
return $this->decoratedFactory->supports($dsn, $options);
51+
}
52+
}

src/SchemaContextBundle.php

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

55
namespace Macpaw\SchemaContextBundle;
66

7+
use Macpaw\SchemaContextBundle\DependencyInjection\SchemaContextCompilerPass;
8+
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
9+
use Symfony\Component\DependencyInjection\ContainerBuilder;
710
use Symfony\Component\HttpKernel\Bundle\Bundle;
811

912
class SchemaContextBundle extends Bundle
1013
{
14+
public function build(ContainerBuilder $container)
15+
{
16+
$container->addCompilerPass(
17+
new SchemaContextCompilerPass(),
18+
PassConfig::TYPE_BEFORE_OPTIMIZATION,
19+
10
20+
);
21+
}
1122
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Macpaw\SchemaContextBundle\Tests\Messenger\Transport;
6+
7+
use Macpaw\SchemaContextBundle\DependencyInjection\SchemaContextCompilerPass;
8+
use Macpaw\SchemaContextBundle\Messenger\Transport\DoctrineTransportFactoryDecorator;
9+
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
10+
use PHPUnit\Framework\TestCase;
11+
use Symfony\Component\DependencyInjection\ContainerBuilder;
12+
use Symfony\Component\DependencyInjection\Definition;
13+
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
14+
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
15+
16+
final class DoctrineTransportFactoryDecoratorTest extends TestCase
17+
{
18+
public function testCompilerPassNotRegisterDecoratorService(): void
19+
{
20+
$compilerPass = new SchemaContextCompilerPass();
21+
22+
$containerBuilder = $this->createMock(ContainerBuilder::class);
23+
24+
$containerBuilder->expects(self::once())
25+
->method('hasDefinition')
26+
->willReturn(false);
27+
28+
$containerBuilder->expects(self::never())
29+
->method('setDefinition');
30+
31+
$compilerPass->process($containerBuilder);
32+
}
33+
34+
public function testCompilerPassRegisterDecoratorService(): void
35+
{
36+
$compilerPass = new SchemaContextCompilerPass();
37+
38+
$containerBuilder = $this->createMock(ContainerBuilder::class);
39+
40+
$containerBuilder->expects(self::once())
41+
->method('hasDefinition')
42+
->willReturn(true);
43+
44+
$containerBuilder->expects(self::once())
45+
->method('setDefinition')
46+
->with(
47+
self::equalTo(SchemaContextCompilerPass::DECORATOR_ID),
48+
self::callback(function (Definition $definition): bool {
49+
// You can assert partial properties here
50+
self::assertSame(
51+
DoctrineTransportFactoryDecorator::class,
52+
$definition->getClass(),
53+
);
54+
55+
self::assertFalse($definition->isPublic());
56+
self::assertTrue($definition->isAutowired());
57+
self::assertTrue($definition->isAutoconfigured());
58+
self::assertIsArray($definition->getDecoratedService());
59+
self::assertArrayHasKey(0, $definition->getDecoratedService());
60+
self::assertEquals(
61+
SchemaContextCompilerPass::TARGET_ID,
62+
$definition->getDecoratedService()[0],
63+
);
64+
65+
// Optional: check arguments only if needed
66+
$args = $definition->getArguments();
67+
self::assertArrayHasKey('$decoratedFactory', $args);
68+
69+
return true;
70+
}),
71+
);
72+
73+
$compilerPass->process($containerBuilder);
74+
}
75+
76+
public function testSchemaIsOverride(): void
77+
{
78+
$doctrineTransportMock = $this->createMock(TransportFactoryInterface::class);
79+
$baggage = new BaggageSchemaResolver();
80+
$baggage->setSchema('test_schema');
81+
82+
$decorator = new DoctrineTransportFactoryDecorator(
83+
$doctrineTransportMock,
84+
$baggage,
85+
);
86+
87+
$doctrineTransportMock->expects(self::once())
88+
->method('createTransport')
89+
->with(
90+
self::equalTo(''),
91+
self::callback(function (array $options): bool {
92+
self::assertArrayHasKey('table_name', $options);
93+
self::assertEquals('"test_schema"."messenger_messages"', $options['table_name']);
94+
95+
return true;
96+
}),
97+
);
98+
99+
$decorator->createTransport('', [], $this->createMock(SerializerInterface::class));
100+
}
101+
102+
public function testSchemaIsDefault(): void
103+
{
104+
$doctrineTransportMock = $this->createMock(TransportFactoryInterface::class);
105+
$baggage = new BaggageSchemaResolver();
106+
$baggage->setSchema('default');
107+
108+
$decorator = new DoctrineTransportFactoryDecorator(
109+
$doctrineTransportMock,
110+
$baggage,
111+
'default',
112+
);
113+
114+
$doctrineTransportMock->expects(self::once())
115+
->method('createTransport')
116+
->with(
117+
self::equalTo(''),
118+
self::callback(function (array $options): bool {
119+
self::assertArrayHasKey('table_name', $options);
120+
self::assertEquals('messenger_messages', $options['table_name']);
121+
122+
return true;
123+
}),
124+
);
125+
126+
$decorator->createTransport(
127+
'',
128+
['table_name' => 'messenger_messages'],
129+
$this->createMock(SerializerInterface::class),
130+
);
131+
}
132+
}

0 commit comments

Comments
 (0)