Skip to content

Commit 5d4ea81

Browse files
[Contracts] Rename ServiceSubscriberTrait to ServiceMethodsSubscriberTrait
1 parent 16679ac commit 5d4ea81

8 files changed

+375
-92
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add `ServiceCollectionInterface`
8+
* Deprecate `ServiceSubscriberTrait`, use `ServiceMethodsSubscriberTrait` instead
89

910
3.4
1011
---

Service/Attribute/SubscribedService.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111

1212
namespace Symfony\Contracts\Service\Attribute;
1313

14+
use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait;
1415
use Symfony\Contracts\Service\ServiceSubscriberInterface;
15-
use Symfony\Contracts\Service\ServiceSubscriberTrait;
1616

1717
/**
1818
* For use as the return value for {@see ServiceSubscriberInterface}.
1919
*
2020
* @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi'))
2121
*
22-
* Use with {@see ServiceSubscriberTrait} to mark a method's return type
22+
* Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type
2323
* as a subscribed service.
2424
*
2525
* @author Kevin Bond <[email protected]>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Contracts\Service\Attribute\Required;
16+
use Symfony\Contracts\Service\Attribute\SubscribedService;
17+
18+
/**
19+
* Implementation of ServiceSubscriberInterface that determines subscribed services
20+
* from methods that have the #[SubscribedService] attribute.
21+
*
22+
* Service ids are available as "ClassName::methodName" so that the implementation
23+
* of subscriber methods can be just `return $this->container->get(__METHOD__);`.
24+
*
25+
* @author Kevin Bond <[email protected]>
26+
*/
27+
trait ServiceMethodsSubscriberTrait
28+
{
29+
protected ContainerInterface $container;
30+
31+
public static function getSubscribedServices(): array
32+
{
33+
$services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];
34+
35+
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
36+
if (self::class !== $method->getDeclaringClass()->name) {
37+
continue;
38+
}
39+
40+
if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) {
41+
continue;
42+
}
43+
44+
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
45+
throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name));
46+
}
47+
48+
if (!$returnType = $method->getReturnType()) {
49+
throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class));
50+
}
51+
52+
/* @var SubscribedService $attribute */
53+
$attribute = $attribute->newInstance();
54+
$attribute->key ??= self::class.'::'.$method->name;
55+
$attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType;
56+
$attribute->nullable = $returnType->allowsNull();
57+
58+
if ($attribute->attributes) {
59+
$services[] = $attribute;
60+
} else {
61+
$services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type;
62+
}
63+
}
64+
65+
return $services;
66+
}
67+
68+
#[Required]
69+
public function setContainer(ContainerInterface $container): ?ContainerInterface
70+
{
71+
$ret = null;
72+
if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) {
73+
$ret = parent::setContainer($container);
74+
}
75+
76+
$this->container = $container;
77+
78+
return $ret;
79+
}
80+
}

Service/ServiceSubscriberTrait.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@
1515
use Symfony\Contracts\Service\Attribute\Required;
1616
use Symfony\Contracts\Service\Attribute\SubscribedService;
1717

18+
trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class);
19+
1820
/**
19-
* Implementation of ServiceSubscriberInterface that determines subscribed services from
20-
* method return types. Service ids are available as "ClassName::methodName".
21+
* Implementation of ServiceSubscriberInterface that determines subscribed services
22+
* from methods that have the #[SubscribedService] attribute.
23+
*
24+
* Service ids are available as "ClassName::methodName" so that the implementation
25+
* of subscriber methods can be just `return $this->container->get(__METHOD__);`.
26+
*
27+
* @property ContainerInterface $container
2128
*
2229
* @author Kevin Bond <[email protected]>
30+
*
31+
* @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead
2332
*/
2433
trait ServiceSubscriberTrait
2534
{
26-
/** @var ContainerInterface */
27-
protected $container;
28-
2935
public static function getSubscribedServices(): array
3036
{
3137
$services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];

Service/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
],
1818
"require": {
1919
"php": ">=8.1",
20-
"psr/container": "^1.1|^2.0"
20+
"psr/container": "^1.1|^2.0",
21+
"symfony/deprecation-contracts": "^2.5|^3"
2122
},
2223
"conflict": {
2324
"ext-psr": "<1.1|>=2"

Tests/Service/LegacyTestService.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Service;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Contracts\Service\Attribute\Required;
16+
use Symfony\Contracts\Service\Attribute\SubscribedService;
17+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
18+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
19+
20+
class LegacyParentTestService
21+
{
22+
public function aParentService(): Service1
23+
{
24+
}
25+
26+
public function setContainer(ContainerInterface $container): ?ContainerInterface
27+
{
28+
return $container;
29+
}
30+
}
31+
32+
class LegacyTestService extends LegacyParentTestService implements ServiceSubscriberInterface
33+
{
34+
use ServiceSubscriberTrait;
35+
36+
#[SubscribedService]
37+
public function aService(): Service2
38+
{
39+
return $this->container->get(__METHOD__);
40+
}
41+
42+
#[SubscribedService]
43+
public function nullableService(): ?Service2
44+
{
45+
return $this->container->get(__METHOD__);
46+
}
47+
48+
#[SubscribedService(attributes: new Required())]
49+
public function withAttribute(): ?Service2
50+
{
51+
return $this->container->get(__METHOD__);
52+
}
53+
}
54+
55+
class LegacyChildTestService extends LegacyTestService
56+
{
57+
#[SubscribedService()]
58+
public function aChildService(): LegacyService3
59+
{
60+
return $this->container->get(__METHOD__);
61+
}
62+
}
63+
64+
class LegacyParentWithMagicCall
65+
{
66+
public function __call($method, $args)
67+
{
68+
throw new \BadMethodCallException('Should not be called.');
69+
}
70+
71+
public static function __callStatic($method, $args)
72+
{
73+
throw new \BadMethodCallException('Should not be called.');
74+
}
75+
}
76+
77+
class LegacyService3
78+
{
79+
}
80+
81+
class LegacyParentTestService2
82+
{
83+
/** @var ContainerInterface */
84+
protected $container;
85+
86+
public function setContainer(ContainerInterface $container)
87+
{
88+
$previous = $this->container ?? null;
89+
$this->container = $container;
90+
91+
return $previous;
92+
}
93+
}

0 commit comments

Comments
 (0)