Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #54496 [Contracts] Rename ServiceSubscriberTrait to ServiceMe…
…thodsSubscriberTrait (nicolas-grekas) This PR was merged into the 7.1 branch. Discussion ---------- [Contracts] Rename ServiceSubscriberTrait to ServiceMethodsSubscriberTrait | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | no | Deprecations? | yes | Issues | Fix #54490 | License | MIT As described in the linked PR, AbstractController is incompatible with ServiceSubscriberTrait because of the added type to the AbstractController::$container property, while ServiceSubscriberTrait's $container property cannot have a type without a BC break. There are two parts to this PR: - Deprecate ServiceSubscriberTrait in favor if ServiceMethodsSubscriberTrait, which declares the type of the $container property. The new name better conveys its purpose as a bonus. - Fix the incompatibility with AbstractController by removing the property declaration on ServiceSubscriberTrait. This means the trait will create a dynamic property. Those are deprecated, but since the trait is also deprecated, the upgrade path is clear. I also tried to improve the description of the trait in the meantime. /cc `@kbond` Commits ------- 8f47ced [Contracts] Rename ServiceSubscriberTrait to ServiceMethodsSubscriberTrait
- Loading branch information
Showing
14 changed files
with
395 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
src/Symfony/Contracts/Service/ServiceMethodsSubscriberTrait.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Contracts\Service; | ||
|
||
use Psr\Container\ContainerInterface; | ||
use Symfony\Contracts\Service\Attribute\Required; | ||
use Symfony\Contracts\Service\Attribute\SubscribedService; | ||
|
||
/** | ||
* Implementation of ServiceSubscriberInterface that determines subscribed services | ||
* from methods that have the #[SubscribedService] attribute. | ||
* | ||
* Service ids are available as "ClassName::methodName" so that the implementation | ||
* of subscriber methods can be just `return $this->container->get(__METHOD__);`. | ||
* | ||
* @author Kevin Bond <kevinbond@gmail.com> | ||
*/ | ||
trait ServiceMethodsSubscriberTrait | ||
{ | ||
protected ContainerInterface $container; | ||
|
||
public static function getSubscribedServices(): array | ||
{ | ||
$services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; | ||
|
||
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { | ||
if (self::class !== $method->getDeclaringClass()->name) { | ||
continue; | ||
} | ||
|
||
if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { | ||
continue; | ||
} | ||
|
||
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { | ||
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)); | ||
} | ||
|
||
if (!$returnType = $method->getReturnType()) { | ||
throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); | ||
} | ||
|
||
/* @var SubscribedService $attribute */ | ||
$attribute = $attribute->newInstance(); | ||
$attribute->key ??= self::class.'::'.$method->name; | ||
$attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; | ||
$attribute->nullable = $returnType->allowsNull(); | ||
|
||
if ($attribute->attributes) { | ||
$services[] = $attribute; | ||
} else { | ||
$services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; | ||
} | ||
} | ||
|
||
return $services; | ||
} | ||
|
||
#[Required] | ||
public function setContainer(ContainerInterface $container): ?ContainerInterface | ||
{ | ||
$ret = null; | ||
if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { | ||
$ret = parent::setContainer($container); | ||
} | ||
|
||
$this->container = $container; | ||
|
||
return $ret; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Contracts\Tests\Service; | ||
|
||
use Psr\Container\ContainerInterface; | ||
use Symfony\Contracts\Service\Attribute\Required; | ||
use Symfony\Contracts\Service\Attribute\SubscribedService; | ||
use Symfony\Contracts\Service\ServiceSubscriberInterface; | ||
use Symfony\Contracts\Service\ServiceSubscriberTrait; | ||
|
||
class LegacyParentTestService | ||
{ | ||
public function aParentService(): Service1 | ||
{ | ||
} | ||
|
||
public function setContainer(ContainerInterface $container): ?ContainerInterface | ||
{ | ||
return $container; | ||
} | ||
} | ||
|
||
class LegacyTestService extends LegacyParentTestService implements ServiceSubscriberInterface | ||
{ | ||
use ServiceSubscriberTrait; | ||
|
||
#[SubscribedService] | ||
public function aService(): Service2 | ||
{ | ||
return $this->container->get(__METHOD__); | ||
} | ||
|
||
#[SubscribedService] | ||
public function nullableService(): ?Service2 | ||
{ | ||
return $this->container->get(__METHOD__); | ||
} | ||
|
||
#[SubscribedService(attributes: new Required())] | ||
public function withAttribute(): ?Service2 | ||
{ | ||
return $this->container->get(__METHOD__); | ||
} | ||
} | ||
|
||
class LegacyChildTestService extends LegacyTestService | ||
{ | ||
#[SubscribedService()] | ||
public function aChildService(): LegacyService3 | ||
{ | ||
return $this->container->get(__METHOD__); | ||
} | ||
} | ||
|
||
class LegacyParentWithMagicCall | ||
{ | ||
public function __call($method, $args) | ||
{ | ||
throw new \BadMethodCallException('Should not be called.'); | ||
} | ||
|
||
public static function __callStatic($method, $args) | ||
{ | ||
throw new \BadMethodCallException('Should not be called.'); | ||
} | ||
} | ||
|
||
class LegacyService3 | ||
{ | ||
} | ||
|
||
class LegacyParentTestService2 | ||
{ | ||
/** @var ContainerInterface */ | ||
protected $container; | ||
|
||
public function setContainer(ContainerInterface $container) | ||
{ | ||
$previous = $this->container ?? null; | ||
$this->container = $container; | ||
|
||
return $previous; | ||
} | ||
} |
Oops, something went wrong.