This commit is contained in:
Sampanna Rimal
2024-09-04 12:22:04 +05:45
parent 53c0140f58
commit 82fab174dc
203 changed files with 4255 additions and 1343 deletions

View File

@ -11,15 +11,15 @@
namespace Symfony\Contracts\Service\Attribute;
use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberTrait;
/**
* For use as the return value for {@see ServiceSubscriberInterface}.
*
* @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi'))
*
* Use with {@see ServiceSubscriberTrait} to mark a method's return type
* Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type
* as a subscribed service.
*
* @author Kevin Bond <kevinbond@gmail.com>

View File

@ -0,0 +1,26 @@
<?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;
/**
* A ServiceProviderInterface that is also countable and iterable.
*
* @author Kevin Bond <kevinbond@gmail.com>
*
* @template-covariant T of mixed
*
* @extends ServiceProviderInterface<T>
* @extends \IteratorAggregate<string, T>
*/
interface ServiceCollectionInterface extends ServiceProviderInterface, \Countable, \IteratorAggregate
{
}

View File

@ -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;
}
}

View File

@ -15,17 +15,23 @@ use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\Attribute\Required;
use Symfony\Contracts\Service\Attribute\SubscribedService;
trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class);
/**
* Implementation of ServiceSubscriberInterface that determines subscribed services from
* method return types. Service ids are available as "ClassName::methodName".
* 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__);`.
*
* @property ContainerInterface $container
*
* @author Kevin Bond <kevinbond@gmail.com>
*
* @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead
*/
trait ServiceSubscriberTrait
{
/** @var ContainerInterface */
protected $container;
public static function getSubscribedServices(): array
{
$services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];

View File

@ -12,7 +12,9 @@
namespace Symfony\Contracts\Service\Test;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Contracts\Service\ServiceLocatorTrait;
abstract class ServiceLocatorTestCase extends TestCase
@ -66,27 +68,29 @@ abstract class ServiceLocatorTestCase extends TestCase
public function testThrowsOnUndefinedInternalService()
{
if (!$this->getExpectedException()) {
$this->expectException(\Psr\Container\NotFoundExceptionInterface::class);
$this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.');
}
$locator = $this->getServiceLocator([
'foo' => function () use (&$locator) { return $locator->get('bar'); },
]);
if (!$this->getExpectedException()) {
$this->expectException(NotFoundExceptionInterface::class);
$this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.');
}
$locator->get('foo');
}
public function testThrowsOnCircularReference()
{
$this->expectException(\Psr\Container\ContainerExceptionInterface::class);
$this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');
$locator = $this->getServiceLocator([
'foo' => function () use (&$locator) { return $locator->get('bar'); },
'bar' => function () use (&$locator) { return $locator->get('baz'); },
'baz' => function () use (&$locator) { return $locator->get('bar'); },
]);
$this->expectException(ContainerExceptionInterface::class);
$this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');
$locator->get('foo');
}
}

View File

@ -17,7 +17,8 @@
],
"require": {
"php": ">=8.1",
"psr/container": "^1.1|^2.0"
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
@ -31,7 +32,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",