Skip to content

Commit fee84f7

Browse files
committed
add SubscribedService attribute, deprecate current ServiceSubscriberTrait usage
1 parent c1c973c commit fee84f7

9 files changed

+316
-11
lines changed

src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424
use Symfony\Component\DependencyInjection\Reference;
2525
use Symfony\Component\DependencyInjection\ServiceLocator;
2626
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
27+
use Symfony\Component\DependencyInjection\Tests\Fixtures\LegacyTestServiceSubscriberChild;
28+
use Symfony\Component\DependencyInjection\Tests\Fixtures\LegacyTestServiceSubscriberParent;
2729
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1;
2830
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition2;
2931
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition3;
3032
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber;
3133
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberChild;
3234
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberParent;
3335
use Symfony\Component\DependencyInjection\TypedReference;
36+
use Symfony\Contracts\Service\Attribute\SubscribedService;
3437
use Symfony\Contracts\Service\ServiceSubscriberInterface;
3538
use Symfony\Contracts\Service\ServiceSubscriberTrait;
3639

@@ -143,11 +146,14 @@ public function testExtraServiceSubscriber()
143146
$container->compile();
144147
}
145148

149+
/**
150+
* @group legacy
151+
*/
146152
public function testServiceSubscriberTrait()
147153
{
148154
$container = new ContainerBuilder();
149155

150-
$container->register('foo', TestServiceSubscriberChild::class)
156+
$container->register('foo', LegacyTestServiceSubscriberChild::class)
151157
->addMethodCall('setContainer', [new Reference(PsrContainerInterface::class)])
152158
->addTag('container.service_subscriber')
153159
;
@@ -159,15 +165,18 @@ public function testServiceSubscriberTrait()
159165
$locator = $container->getDefinition((string) $foo->getMethodCalls()[0][1][0]);
160166

161167
$expected = [
162-
TestServiceSubscriberChild::class.'::invalidDefinition' => new ServiceClosureArgument(new TypedReference('Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', 'Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
163-
TestServiceSubscriberChild::class.'::testDefinition2' => new ServiceClosureArgument(new TypedReference(TestDefinition2::class, TestDefinition2::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
164-
TestServiceSubscriberChild::class.'::testDefinition3' => new ServiceClosureArgument(new TypedReference(TestDefinition3::class, TestDefinition3::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
165-
TestServiceSubscriberParent::class.'::testDefinition1' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class, TestDefinition1::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
168+
LegacyTestServiceSubscriberChild::class.'::invalidDefinition' => new ServiceClosureArgument(new TypedReference('Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', 'Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
169+
LegacyTestServiceSubscriberChild::class.'::testDefinition2' => new ServiceClosureArgument(new TypedReference(TestDefinition2::class, TestDefinition2::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
170+
LegacyTestServiceSubscriberChild::class.'::testDefinition3' => new ServiceClosureArgument(new TypedReference(TestDefinition3::class, TestDefinition3::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
171+
LegacyTestServiceSubscriberParent::class.'::testDefinition1' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class, TestDefinition1::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
166172
];
167173

168174
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
169175
}
170176

177+
/**
178+
* @group legacy
179+
*/
171180
public function testServiceSubscriberTraitWithGetter()
172181
{
173182
$container = new ContainerBuilder();
@@ -195,6 +204,132 @@ public function getFoo(): \stdClass
195204
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
196205
}
197206

207+
/**
208+
* @requires PHP 8
209+
*/
210+
public function testServiceSubscriberTraitWithSubscribedServiceAttribute()
211+
{
212+
$container = new ContainerBuilder();
213+
214+
$container->register('foo', TestServiceSubscriberChild::class)
215+
->addMethodCall('setContainer', [new Reference(PsrContainerInterface::class)])
216+
->addTag('container.service_subscriber')
217+
;
218+
219+
(new RegisterServiceSubscribersPass())->process($container);
220+
(new ResolveServiceSubscribersPass())->process($container);
221+
222+
$foo = $container->getDefinition('foo');
223+
$locator = $container->getDefinition((string) $foo->getMethodCalls()[0][1][0]);
224+
225+
$expected = [
226+
TestServiceSubscriberChild::class.'::invalidDefinition' => new ServiceClosureArgument(new TypedReference('Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', 'Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
227+
TestServiceSubscriberChild::class.'::testDefinition2' => new ServiceClosureArgument(new TypedReference(TestDefinition2::class, TestDefinition2::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
228+
TestServiceSubscriberChild::class.'::testDefinition4' => new ServiceClosureArgument(new TypedReference(TestDefinition3::class, TestDefinition3::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
229+
TestServiceSubscriberParent::class.'::testDefinition1' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class, TestDefinition1::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
230+
'custom_name' => new ServiceClosureArgument(new TypedReference(TestDefinition3::class, TestDefinition3::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'custom_name')),
231+
];
232+
233+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
234+
}
235+
236+
/**
237+
* @requires PHP 8
238+
*/
239+
public function testServiceSubscriberTraitWithSubscribedServiceAttributeOnStaticMethod()
240+
{
241+
$subscriber = new class() implements ServiceSubscriberInterface {
242+
use ServiceSubscriberTrait;
243+
244+
#[SubscribedService]
245+
public static function method(): TestDefinition1
246+
{
247+
}
248+
};
249+
250+
$this->expectException(\LogicException::class);
251+
252+
$subscriber::getSubscribedServices();
253+
}
254+
255+
/**
256+
* @requires PHP 8
257+
*/
258+
public function testServiceSubscriberTraitWithSubscribedServiceAttributeOnMethodWithRequiredParameters()
259+
{
260+
$subscriber = new class() implements ServiceSubscriberInterface {
261+
use ServiceSubscriberTrait;
262+
263+
#[SubscribedService]
264+
public function method($param1, $param2 = null): TestDefinition1
265+
{
266+
}
267+
};
268+
269+
$this->expectException(\LogicException::class);
270+
271+
$subscriber::getSubscribedServices();
272+
}
273+
274+
/**
275+
* @requires PHP 8
276+
*/
277+
public function testServiceSubscriberTraitWithSubscribedServiceAttributeOnMethodMissingReturnType()
278+
{
279+
$subscriber = new class() implements ServiceSubscriberInterface {
280+
use ServiceSubscriberTrait;
281+
282+
#[SubscribedService]
283+
public function method()
284+
{
285+
}
286+
};
287+
288+
$this->expectException(\LogicException::class);
289+
290+
$subscriber::getSubscribedServices();
291+
}
292+
293+
/**
294+
* @requires PHP 8
295+
*/
296+
public function testServiceSubscriberTraitWithSubscribedServiceAttributeOnMethodWithBuiltInReturnType()
297+
{
298+
$subscriber = new class() implements ServiceSubscriberInterface {
299+
use ServiceSubscriberTrait;
300+
301+
#[SubscribedService]
302+
public function method(): string
303+
{
304+
}
305+
};
306+
307+
$this->expectException(\LogicException::class);
308+
309+
$subscriber::getSubscribedServices();
310+
}
311+
312+
/**
313+
* @requires PHP 8
314+
*/
315+
public function testServiceSubscriberTraitWithSubscribedServiceAttributeOnMethodWithUnionReturnType()
316+
{
317+
eval('
318+
$subscriber = new class() implements Symfony\Contracts\Service\ServiceSubscriberInterface {
319+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
320+
321+
#[Symfony\Contracts\Service\Attribute\SubscribedService]
322+
public function method(): TestDefinition1|TestDefinition2
323+
{
324+
}
325+
};
326+
');
327+
328+
$this->expectException(\LogicException::class);
329+
330+
$subscriber::getSubscribedServices();
331+
}
332+
198333
public function testServiceSubscriberWithSemanticId()
199334
{
200335
$container = new ContainerBuilder();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
6+
7+
class LegacyTestServiceSubscriberChild extends LegacyTestServiceSubscriberParent
8+
{
9+
use ServiceSubscriberTrait;
10+
use LegacyTestServiceSubscriberTrait;
11+
12+
private function testDefinition2(): TestDefinition2
13+
{
14+
return $this->container->get(__METHOD__);
15+
}
16+
17+
private function invalidDefinition(): InvalidDefinition
18+
{
19+
return $this->container->get(__METHOD__);
20+
}
21+
22+
private function privateFunction1(): string
23+
{
24+
}
25+
26+
private function privateFunction2(): string
27+
{
28+
}
29+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
6+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
7+
8+
class LegacyTestServiceSubscriberParent implements ServiceSubscriberInterface
9+
{
10+
use ServiceSubscriberTrait;
11+
12+
private function testDefinition1(): TestDefinition1
13+
{
14+
return $this->container->get(__METHOD__);
15+
}
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
trait LegacyTestServiceSubscriberTrait
6+
{
7+
private function testDefinition3(): TestDefinition3
8+
{
9+
return $this->container->get(__CLASS__.'::'.__FUNCTION__);
10+
}
11+
}

src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberChild.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,27 @@
22

33
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
44

5+
use Symfony\Contracts\Service\Attribute\SubscribedService;
56
use Symfony\Contracts\Service\ServiceSubscriberTrait;
67

78
class TestServiceSubscriberChild extends TestServiceSubscriberParent
89
{
910
use ServiceSubscriberTrait;
1011
use TestServiceSubscriberTrait;
1112

13+
#[SubscribedService]
1214
private function testDefinition2(): TestDefinition2
1315
{
1416
return $this->container->get(__METHOD__);
1517
}
1618

19+
#[SubscribedService('custom_name')]
20+
private function testDefinition3(): TestDefinition3
21+
{
22+
return $this->container->get('custom_name');
23+
}
24+
25+
#[SubscribedService]
1726
private function invalidDefinition(): InvalidDefinition
1827
{
1928
return $this->container->get(__METHOD__);
@@ -26,4 +35,8 @@ private function privateFunction1(): string
2635
private function privateFunction2(): string
2736
{
2837
}
38+
39+
private function privateFunction3(): AnotherClass
40+
{
41+
}
2942
}

src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberParent.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
44

5+
use Symfony\Contracts\Service\Attribute\SubscribedService;
56
use Symfony\Contracts\Service\ServiceSubscriberInterface;
67
use Symfony\Contracts\Service\ServiceSubscriberTrait;
78

89
class TestServiceSubscriberParent implements ServiceSubscriberInterface
910
{
1011
use ServiceSubscriberTrait;
1112

13+
public function publicFunction1(): SomeClass
14+
{
15+
}
16+
17+
#[SubscribedService]
1218
private function testDefinition1(): TestDefinition1
1319
{
1420
return $this->container->get(__METHOD__);

src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberTrait.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22

33
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
44

5+
use Symfony\Contracts\Service\Attribute\SubscribedService;
6+
57
trait TestServiceSubscriberTrait
68
{
7-
private function testDefinition3(): TestDefinition3
9+
protected function protectedFunction1(): SomeClass
10+
{
11+
}
12+
13+
#[SubscribedService]
14+
private function testDefinition4(): TestDefinition3
815
{
916
return $this->container->get(__CLASS__.'::'.__FUNCTION__);
1017
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Contracts\Service\Attribute;
13+
14+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
15+
16+
/**
17+
* Use with {@see ServiceSubscriberTrait} to mark a method's return type
18+
* as a subscribed service.
19+
*
20+
* @author Kevin Bond <kevinbond@gmail.com>
21+
*/
22+
#[\Attribute(\Attribute::TARGET_METHOD)]
23+
final class SubscribedService
24+
{
25+
public ?string $key;
26+
27+
/**
28+
* @param string|null $key The key to use for the service
29+
* If null, use "ClassName::methodName"
30+
*/
31+
public function __construct(string $key = null)
32+
{
33+
$this->key = $key;
34+
}
35+
}

0 commit comments

Comments
 (0)