diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index a22eedd..ab1787c 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -2,7 +2,7 @@ $finder = (new PhpCsFixer\Finder()) ->in(__DIR__) - ->exclude('var') + ->exclude(['var', 'vendor']) ; return (new PhpCsFixer\Config()) diff --git a/Action/Doc.php b/Action/Doc.php deleted file mode 100644 index 4274002..0000000 --- a/Action/Doc.php +++ /dev/null @@ -1,31 +0,0 @@ - new \ReflectionClass($rpcService), - iterator_to_array($this->serviceFinder->getRpcServices()) - ); - - return ($this->docResponder)($rpcServices); - } -} diff --git a/Action/Rpc.php b/Action/Rpc.php deleted file mode 100644 index a1715d2..0000000 --- a/Action/Rpc.php +++ /dev/null @@ -1,29 +0,0 @@ -parser->parse($request); - - foreach ($rpcPayload->getRpcRequests() as $rpcRequest) { - $this->rpcRequestHandler->handle($rpcRequest); - } - - return ($this->responder)($rpcPayload); - } -} diff --git a/Doc/DocResponder.php b/Doc/DocResponder.php deleted file mode 100644 index 08c5f4c..0000000 --- a/Doc/DocResponder.php +++ /dev/null @@ -1,27 +0,0 @@ -> $rpcServices indexed by services keys - * - * @throws Error - */ - public function __invoke(array $rpcServices): Response - { - return new Response($this->environment->render('@NanofelisJsonRpc/doc.html.twig', ['rpcServices' => $rpcServices])); - } -} diff --git a/Event/RpcBeforeResponseEvent.php b/Event/RpcBeforeResponseEvent.php deleted file mode 100644 index 1946475..0000000 --- a/Event/RpcBeforeResponseEvent.php +++ /dev/null @@ -1,25 +0,0 @@ -rpcRequest; - } -} diff --git a/README.md b/README.md index 12a6424..66a7741 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -[![Build Status](https://travis-ci.com/nanofelis/NanofelisJsonRpcBundle.svg?branch=master)](https://travis-ci.com/nanofelis/NanofelisJsonRpcBundle) -[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=nanofelis_NanofelisJsonRpcBundle&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanofelis_NanofelisJsonRpcBundle) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=nanofelis_NanofelisJsonRpcBundle&metric=coverage)](https://sonarcloud.io/dashboard?id=nanofelis_NanofelisJsonRpcBundle) - The NanofelisJsonRpcBundle is a symfony friendly implementation of the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification. Installation ============ +Make sure Composer is installed globally, as explained in the +[installation chapter](https://getcomposer.org/doc/00-intro.md) +of the Composer documentation. + Applications that use Symfony Flex ---------------------------------- Open a command console, enter your project directory and execute: ```console -$ composer require nanofelis/json-rpc-bundle +composer require nanofelis/json-rpc-bundle ``` Applications that don't use Symfony Flex @@ -25,264 +25,24 @@ Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle: ```console -$ composer require nanofelis/json-rpc-bundle +composer require nanofelis/json-rpc-bundle ``` -This command requires you to have Composer installed globally, as explained -in the [installation chapter](https://getcomposer.org/doc/00-intro.md) -of the Composer documentation. - ### Step 2: Enable the Bundle Then, enable the bundle by adding it to the list of registered bundles -in the `app/AppKernel.php` file of your project: - -```php -handler($data); - - return $article; - } -} +return [ + // ... + Nanofelis\JsonRpcBundle\NanofelisJsonRpcBundle::class => ['all' => true], +]; ``` -```php -namespace App\Normalizer; - -use App\Entity\Article; - -class ArticleNormalizer implements NormalizerInterface -{ - public function normalize($vehicle, $format = null, array $context = []) - { - if (in_array('custom', $context) { - ... - } - } - - public function supportsNormalization($data, $format = null): bool - { - return $data instanceof Article; - } -} -``` - -Events Hooks ------------- -You can hook to the 2 following events in the rpc request lifecycle: - -__nanofelis_json_rpc.before_method__ -__Event Class__: RpcBeforeMethodEvent - -This event is dispatched just before the method execution. You can use it to alter the rpc request params. -```php -use Symfony\Component\HttpKernel\Event\ControllerEvent; - -public function onRpcBeforeMethod(RpcBeforeMethodEvent $event) -{ - $rpcRequest = $event->getRpcRequest(); - $serviceDescriptor = $event->getServiceDescriptor(); -} -``` - -__nanofelis_json_rpc.before_response__ -__Event Class__: RpcBeforeResponseEvent - -This event is dispatched just before the error or success response is sent. You can use it to alter the RPC response data. - -```php -use Symfony\Component\HttpKernel\Event\ControllerEvent; - -public function onRpcBeforeResponse(RpcBeforeResponseEvent $event) -{ - $rpcRequest = $event->getRpcRequest(); - $rpcResponse = $rpcRequest->getReponse() ?: $rpcRequest->getReponseError() -} -``` - - Documentation ------------ -The bundle generates a documentation page of all registered RPC services on the route `rpc_doc` - - -GET Support ------------ -The bundle supports GET requests with http query params payload. -It also supports the [cache annotation](https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/cache.html). - -```shell script -# Example GET call -curl http://localhost?jsonrpc=2.0&method=myService.add¶ms[0]=1¶ms[1]=2&id=test-call | jq this +============ -{ - "jsonrpc": "2.0", - "result": 3, - "id": "test-call" -} -``` -![warning](https://img.icons8.com/color/48/000000/warning-shield.png) GET requests are not recommended for RPC payloads. -Unlike a json payload, all query params are sent as string, so for instance the `add(int $a, int $b)` method would fail as -an invalid param request if the file contained a `declare(strict_types=1);` -It is a better option to use regular api endpoints next to the RPC route for specific GET purposes. +Documentation is found in [docs/index.md](docs/index.md). diff --git a/Request/RpcRequest.php b/Request/RpcRequest.php deleted file mode 100644 index 31dcd47..0000000 --- a/Request/RpcRequest.php +++ /dev/null @@ -1,89 +0,0 @@ -|null - */ - private ?array $params = null, - private RpcResponseInterface|null $response = null, - ) { - } - - public function getId(): mixed - { - return $this->id; - } - - public function getMethod(): ?string - { - return $this->method; - } - - public function getServiceKey(): ?string - { - return $this->serviceKey; - } - - public function setServiceKey(?string $serviceKey): void - { - $this->serviceKey = $serviceKey; - } - - public function getMethodKey(): ?string - { - return $this->methodKey; - } - - public function setMethodKey(?string $methodKey): void - { - $this->methodKey = $methodKey; - } - - /** - * @return array|null $params - */ - public function getParams(): ?array - { - return $this->params; - } - - /** - * @param array|null $params - */ - public function setParams(?array $params): void - { - $this->params = $params; - } - - public function getResponse(): RpcResponseInterface|null - { - return $this->response; - } - - public function setResponse(RpcResponseInterface|null $response): void - { - $this->response = $response; - } - - /** - * @return array - */ - public function getResponseContent(): ?array - { - return $this->response?->getContent(); - } -} diff --git a/Request/RpcRequestParser.php b/Request/RpcRequestParser.php deleted file mode 100644 index fdd4e7b..0000000 --- a/Request/RpcRequestParser.php +++ /dev/null @@ -1,137 +0,0 @@ -serializer = new Serializer([new GetSetMethodNormalizer()]); - $this->rpcResolver = (new OptionsResolver()) - ->setRequired(['jsonrpc', 'method']) - ->setDefined(['id']) - ->setDefined(['params']) - ->setAllowedValues('jsonrpc', RpcRequestObject::JSON_RPC_VERSION) - ->setAllowedTypes('method', 'string') - ->setAllowedValues('method', fn ($value) => 1 === preg_match('/^\w+\.\w+$/', $value)) - ->setAllowedTypes('id', ['string', 'int']) - ->setAllowedTypes('params', ['array']); - } - - public function parse(Request $request): RpcPayload - { - try { - $data = $this->getContent($request); - - return $this->getRpcPayload($data); - } catch (AbstractRpcException $e) { - return $this->getRpcPayloadError($e); - } - } - - /** - * @throws RpcParseException - * @throws RpcInvalidRequestException - */ - private function getContent(Request $request): mixed - { - return match ($request->getMethod()) { - Request::METHOD_POST => $this->getPostData($request), - Request::METHOD_GET => $request->query->all(), - default => throw new RpcInvalidRequestException() - }; - } - - /** - * @throws RpcParseException - */ - private function getPostData(Request $request): mixed - { - $data = json_decode((string) $request->getContent(), true); - - if (null === $data) { - throw new RpcParseException(); - } - - return $data; - } - - private function getRpcPayloadError(AbstractRpcException $e): RpcPayload - { - $payload = new RpcPayload(); - $rpcRequest = new RpcRequest(); - $rpcRequest->setResponse(new RpcResponseError($e)); - $payload->addRpcRequest($rpcRequest); - - return $payload; - } - - /** - * @param array $data - * - * @throws RpcInvalidRequestException - */ - private function getRpcPayload(array $data): RpcPayload - { - $payload = new RpcPayload(); - - if (array_is_list($data)) { - $payload->setIsBatch(true); - - foreach ($data as $subData) { - $payload->addRpcRequest($this->getRpcRequest((array) $subData)); - } - } else { - $payload->addRpcRequest($this->getRpcRequest($data)); - } - - return $payload; - } - - /** - * @param array $data - * - * @throws RpcInvalidRequestException - */ - private function getRpcRequest(array $data): RpcRequest - { - try { - /** @var RpcRequest $rpcRequest */ - $rpcRequest = $this->serializer->denormalize($data, RpcRequest::class); - } catch (ExceptionInterface) { - throw new RpcInvalidRequestException(); - } - - try { - $this->rpcResolver->resolve($data); - } catch (OptionResolverException) { - $rpcRequest->setResponse(new RpcResponseError(new RpcInvalidRequestException(), $rpcRequest->getId())); - - return $rpcRequest; - } - - [$serviceKey, $methodKey] = explode('.', $rpcRequest->getMethod()); - $rpcRequest->setServiceKey($serviceKey); - $rpcRequest->setMethodKey($methodKey); - - return $rpcRequest; - } -} diff --git a/Response/RpcResponder.php b/Response/RpcResponder.php deleted file mode 100644 index ef79a63..0000000 --- a/Response/RpcResponder.php +++ /dev/null @@ -1,38 +0,0 @@ -isBatch()) { - foreach ($payload->getRpcRequests() as $rpcRequest) { - $this->eventDispatcher->dispatch(new RpcBeforeResponseEvent($rpcRequest), RpcBeforeResponseEvent::NAME); - $responseContent[] = $rpcRequest->getResponseContent(); - } - } else { - $rpcRequest = $payload->getRpcRequests()[0]; - $this->eventDispatcher->dispatch(new RpcBeforeResponseEvent($rpcRequest), RpcBeforeResponseEvent::NAME); - $responseContent = $rpcRequest->getResponseContent(); - } - - return new JsonResponse($responseContent); - } -} diff --git a/Service/ServiceFinder.php b/Service/ServiceFinder.php deleted file mode 100644 index 73a313a..0000000 --- a/Service/ServiceFinder.php +++ /dev/null @@ -1,40 +0,0 @@ - $rpcServices - */ - public function __construct(private \Traversable $rpcServices) - { - } - - /** - * @throws RpcMethodNotFoundException - */ - public function find(RpcRequest $rpcRequest): ServiceDescriptor - { - $rpcServices = iterator_to_array($this->rpcServices); - - if (!$service = ($rpcServices[$rpcRequest->getServiceKey()] ?? null)) { - throw new RpcMethodNotFoundException(); - } - - return new ServiceDescriptor($service, $rpcRequest->getMethodKey()); - } - - /** - * @return \Traversable - */ - public function getRpcServices(): \Traversable - { - return $this->rpcServices; - } -} diff --git a/Tests/Action/DocTest.php b/Tests/Action/DocTest.php deleted file mode 100644 index 7475e8a..0000000 --- a/Tests/Action/DocTest.php +++ /dev/null @@ -1,31 +0,0 @@ -router = self::$client->getContainer()->get('router'); - } - - public function testDoc() - { - $crawler = self::$client->request('GET', $this->router->generate('nanofelis_json_rpc.doc')); - - $this->assertTrue(self::$client->getResponse()->isSuccessful()); - $this->assertSame(MockService::getServiceKey(), $crawler->filterXPath('//a')->text()); - } -} diff --git a/Tests/Request/RpcRequestParserTest.php b/Tests/Request/RpcRequestParserTest.php deleted file mode 100644 index 565cad1..0000000 --- a/Tests/Request/RpcRequestParserTest.php +++ /dev/null @@ -1,84 +0,0 @@ -parser = new RpcRequestParser(); - } - - public function testParsePostRequest() - { - $request = Request::create(uri: '/', method: 'POST', content: json_encode([ - 'jsonrpc' => '2.0', - 'method' => 'mockService.add', - 'params' => [1, 2], - 'id' => 'test', - ])); - - $payload = $this->parser->parse($request); - - $this->assertInstanceOf(RpcPayload::class, $payload); - $rpcRequest = $payload->getRpcRequests()[0]; - - $this->assertNull($rpcRequest->getResponse()); - $this->assertSame('add', $rpcRequest->getMethodKey()); - $this->assertSame('mockService', $rpcRequest->getServiceKey()); - $this->assertSame([1, 2], $rpcRequest->getParams()); - $this->assertSame('test', $rpcRequest->getId()); - } - - public function testParseGetRequest() - { - $request = Request::create(sprintf('/?%s', http_build_query([ - 'jsonrpc' => '2.0', - 'method' => 'mockService.add', - 'params' => [1, 2], - 'id' => 'test', - ]))); - - $payload = $this->parser->parse($request); - - $this->assertInstanceOf(RpcPayload::class, $payload); - $rpcRequest = $payload->getRpcRequests()[0]; - - $this->assertNull($rpcRequest->getResponse()); - $this->assertSame('add', $rpcRequest->getMethodKey()); - $this->assertSame('mockService', $rpcRequest->getServiceKey()); - $this->assertSame(['1', '2'], $rpcRequest->getParams()); - $this->assertSame('test', $rpcRequest->getId()); - } - - public function testParseBadHttpMethod() - { - $request = Request::create('/', 'PUT'); - $payload = $this->parser->parse($request); - $rpcRequest = $payload->getRpcRequests()[0]; - - $this->assertInstanceOf(RpcResponseError::class, $rpcRequest->getResponse()); - } - - public function testParseBadInvalidRpcFormat() - { - $request = Request::create(uri: '/', method: 'POST', content: json_encode([ - 'jsonrpc' => '2.0', - 'wrongFormat' => 'mockService->add', - ])); - $payload = $this->parser->parse($request); - $rpcRequest = $payload->getRpcRequests()[0]; - - $this->assertInstanceOf(RpcResponseError::class, $rpcRequest->getResponse()); - } -} diff --git a/Tests/Service/ServiceFinderTest.php b/Tests/Service/ServiceFinderTest.php deleted file mode 100644 index da2deb4..0000000 --- a/Tests/Service/ServiceFinderTest.php +++ /dev/null @@ -1,63 +0,0 @@ -services = new \ArrayIterator([ - 'mockService' => new MockService(), - ]); - } - - /** - * @dataProvider providePayload - * - * @throws RpcMethodNotFoundException - */ - public function testFind(RpcRequest $payload, ?string $expectedResult, string $expectedException = null) - { - if ($expectedException) { - $this->expectException($expectedException); - } - - $serviceLocator = new ServiceFinder($this->services); - - $this->assertInstanceOf($expectedResult, $serviceLocator->find($payload)); - } - - public function providePayload(): \Generator - { - $rpcRequest = new RpcRequest(); - $rpcRequest->setMethodKey('add'); - $rpcRequest->setServiceKey('mockService'); - - yield [$rpcRequest, ServiceDescriptor::class]; - - $rpcRequestUnknownService = new RpcRequest(); - $rpcRequestUnknownService->setMethodKey('add'); - $rpcRequestUnknownService->setServiceKey('unknown'); - - yield [$rpcRequestUnknownService, null, RpcMethodNotFoundException::class]; - - $rpcRequestUnknownMethod = new RpcRequest(); - $rpcRequestUnknownMethod->setMethodKey('unknown'); - $rpcRequestUnknownMethod->setServiceKey('mockService'); - - yield [$rpcRequestUnknownMethod, null, RpcMethodNotFoundException::class]; - } -} diff --git a/composer.json b/composer.json index bad42bb..d71363f 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,6 @@ "php": "^8.0", "ext-json": "*", "symfony/event-dispatcher": "^5.4|^6.3|^7.0", - "symfony/options-resolver": "^5.4|^6.3|^7.0", "symfony/property-access": "^5.4|^6.3|^7.0", "symfony/serializer": "^5.4|^6.3|^7.0", "symfony/twig-bundle": "^5.4|^6.3|^7.0", @@ -25,18 +24,19 @@ "symfony/var-dumper": "^5.4|^6.3|^7.0", "phpstan/phpstan-symfony": "^1.0", "symfony/browser-kit": "^5.4|^6.3|^7.0", - "friendsofphp/php-cs-fixer": "^3.3" + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.10" }, "autoload": { "psr-4": { - "Nanofelis\\Bundle\\JsonRpcBundle\\": "" + "Nanofelis\\JsonRpcBundle\\": "src" }, "exclude-from-classmap": [ "/Tests/" ] }, "autoload-dev": { - "psr-4": { "Tests\\": "Tests/" } + "psr-4": { "Nanofelis\\JsonRpcBundle\\Tests\\": "tests/" } }, "extra": { "branch-alias": { diff --git a/composer.lock b/composer.lock index c540ed7..3dee7a2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f4c957b1bb393a5e5f043de4da0c2606", + "content-hash": "684023818dae329da6aec895b7856570", "packages": [ { "name": "psr/cache", @@ -210,16 +210,16 @@ }, { "name": "symfony/cache", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "378e30a864c868d635353f103a5a5e7569f029ec" + "reference": "fc822951dd360a593224bb2cef90a087d0dff60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/378e30a864c868d635353f103a5a5e7569f029ec", - "reference": "378e30a864c868d635353f103a5a5e7569f029ec", + "url": "https://api.github.com/repos/symfony/cache/zipball/fc822951dd360a593224bb2cef90a087d0dff60f", + "reference": "fc822951dd360a593224bb2cef90a087d0dff60f", "shasum": "" }, "require": { @@ -286,7 +286,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.0.2" + "source": "https://github.com/symfony/cache/tree/v7.0.4" }, "funding": [ { @@ -302,7 +302,7 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:37:40+00:00" + "time": "2024-02-22T20:27:20+00:00" }, { "name": "symfony/cache-contracts", @@ -382,16 +382,16 @@ }, { "name": "symfony/config", - "version": "v7.0.0", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "8789646600f4e7e451dde9e1dc81cfa429f3857a" + "reference": "44deeba7233f08f383185ffa37dace3b3bc87364" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/8789646600f4e7e451dde9e1dc81cfa429f3857a", - "reference": "8789646600f4e7e451dde9e1dc81cfa429f3857a", + "url": "https://api.github.com/repos/symfony/config/zipball/44deeba7233f08f383185ffa37dace3b3bc87364", + "reference": "44deeba7233f08f383185ffa37dace3b3bc87364", "shasum": "" }, "require": { @@ -437,7 +437,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.0.0" + "source": "https://github.com/symfony/config/tree/v7.0.4" }, "funding": [ { @@ -453,20 +453,20 @@ "type": "tidelift" } ], - "time": "2023-11-09T08:30:23+00:00" + "time": "2024-02-26T07:52:39+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "bd25ef7c937b9da12510bdc4f1c66728f19620e3" + "reference": "47f37af245df8457ea63409fc242b3cc825ce5eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/bd25ef7c937b9da12510bdc4f1c66728f19620e3", - "reference": "bd25ef7c937b9da12510bdc4f1c66728f19620e3", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/47f37af245df8457ea63409fc242b3cc825ce5eb", + "reference": "47f37af245df8457ea63409fc242b3cc825ce5eb", "shasum": "" }, "require": { @@ -517,7 +517,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.0.2" + "source": "https://github.com/symfony/dependency-injection/tree/v7.0.4" }, "funding": [ { @@ -533,7 +533,7 @@ "type": "tidelift" } ], - "time": "2023-12-28T19:18:20+00:00" + "time": "2024-02-22T20:27:20+00:00" }, { "name": "symfony/deprecation-contracts", @@ -604,16 +604,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.0.0", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "80b1258be1b84c12a345d0ec3881bbf2e5270cc2" + "reference": "677b24759decff69e65b1e9d1471d90f95ced880" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/80b1258be1b84c12a345d0ec3881bbf2e5270cc2", - "reference": "80b1258be1b84c12a345d0ec3881bbf2e5270cc2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/677b24759decff69e65b1e9d1471d90f95ced880", + "reference": "677b24759decff69e65b1e9d1471d90f95ced880", "shasum": "" }, "require": { @@ -659,7 +659,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.0.0" + "source": "https://github.com/symfony/error-handler/tree/v7.0.4" }, "funding": [ { @@ -675,20 +675,20 @@ "type": "tidelift" } ], - "time": "2023-10-20T16:35:23+00:00" + "time": "2024-02-22T20:27:20+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.0.2", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "098b62ae81fdd6cbf941f355059f617db28f4f9a" + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/098b62ae81fdd6cbf941f355059f617db28f4f9a", - "reference": "098b62ae81fdd6cbf941f355059f617db28f4f9a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e", "shasum": "" }, "require": { @@ -739,7 +739,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.2" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.3" }, "funding": [ { @@ -755,7 +755,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T22:24:19+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -835,16 +835,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.0.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "7da8ea2362a283771478c5f7729cfcb43a76b8b7" + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7da8ea2362a283771478c5f7729cfcb43a76b8b7", - "reference": "7da8ea2362a283771478c5f7729cfcb43a76b8b7", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2890e3a825bc0c0558526c04499c13f83e1b6b12", + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12", "shasum": "" }, "require": { @@ -878,7 +878,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.0.0" + "source": "https://github.com/symfony/filesystem/tree/v7.0.3" }, "funding": [ { @@ -894,7 +894,7 @@ "type": "tidelift" } ], - "time": "2023-07-27T06:33:22+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/finder", @@ -962,16 +962,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "c647b0162e2190cbcd4a21174482af645e11367c" + "reference": "b58bcb2f9c32405b8fbaa24a1e38c8a10bad7b21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c647b0162e2190cbcd4a21174482af645e11367c", - "reference": "c647b0162e2190cbcd4a21174482af645e11367c", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/b58bcb2f9c32405b8fbaa24a1e38c8a10bad7b21", + "reference": "b58bcb2f9c32405b8fbaa24a1e38c8a10bad7b21", "shasum": "" }, "require": { @@ -1009,7 +1009,7 @@ "symfony/mime": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", - "symfony/scheduler": "<6.4", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", "symfony/security-core": "<6.4", "symfony/security-csrf": "<6.4", "symfony/serializer": "<6.4", @@ -1047,7 +1047,7 @@ "symfony/process": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/rate-limiter": "^6.4|^7.0", - "symfony/scheduler": "^6.4|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", "symfony/security-bundle": "^6.4|^7.0", "symfony/semaphore": "^6.4|^7.0", "symfony/serializer": "^6.4|^7.0", @@ -1088,7 +1088,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.0.2" + "source": "https://github.com/symfony/framework-bundle/tree/v7.0.4" }, "funding": [ { @@ -1104,20 +1104,20 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:37:40+00:00" + "time": "2024-02-26T07:52:39+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.0.0", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "47d72323200934694def5d57083899d774a2b110" + "reference": "439fdfdd344943254b1ef6278613e79040548045" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/47d72323200934694def5d57083899d774a2b110", - "reference": "47d72323200934694def5d57083899d774a2b110", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/439fdfdd344943254b1ef6278613e79040548045", + "reference": "439fdfdd344943254b1ef6278613e79040548045", "shasum": "" }, "require": { @@ -1165,7 +1165,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.0.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.0.4" }, "funding": [ { @@ -1181,20 +1181,20 @@ "type": "tidelift" } ], - "time": "2023-11-07T15:10:37+00:00" + "time": "2024-02-08T19:22:56+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.0.2", + "version": "v7.0.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "237d3008bc3f5db3e066e348dc0a6435d70a52bb" + "reference": "37c24ca28f65e3121a68f3dd4daeb36fb1fa2a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/237d3008bc3f5db3e066e348dc0a6435d70a52bb", - "reference": "237d3008bc3f5db3e066e348dc0a6435d70a52bb", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/37c24ca28f65e3121a68f3dd4daeb36fb1fa2a72", + "reference": "37c24ca28f65e3121a68f3dd4daeb36fb1fa2a72", "shasum": "" }, "require": { @@ -1242,7 +1242,7 @@ "symfony/process": "^6.4|^7.0", "symfony/property-access": "^6.4|^7.0", "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/serializer": "^6.4.4|^7.0.4", "symfony/stopwatch": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", @@ -1277,7 +1277,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.0.2" + "source": "https://github.com/symfony/http-kernel/tree/v7.0.5" }, "funding": [ { @@ -1293,87 +1293,20 @@ "type": "tidelift" } ], - "time": "2023-12-30T15:41:17+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v7.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "700ff4096e346f54cb628ea650767c8130f1001f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f", - "reference": "700ff4096e346f54cb628ea650767c8130f1001f", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.0.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-08-08T10:20:21+00:00" + "time": "2024-03-04T21:05:24+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -1387,9 +1320,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1426,7 +1356,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -1442,20 +1372,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "875e90aeea2777b6f135677f618529449334a612" + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", - "reference": "875e90aeea2777b6f135677f618529449334a612", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { @@ -1466,9 +1396,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1507,7 +1434,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, "funding": [ { @@ -1523,20 +1450,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { @@ -1547,9 +1474,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1591,7 +1515,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { @@ -1607,20 +1531,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -1634,9 +1558,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1674,7 +1595,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -1690,20 +1611,20 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -1711,9 +1632,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1757,7 +1675,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -1773,20 +1691,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" + "reference": "86fcae159633351e5fd145d1c47de6c528f8caff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", - "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff", + "reference": "86fcae159633351e5fd145d1c47de6c528f8caff", "shasum": "" }, "require": { @@ -1795,9 +1713,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1837,7 +1752,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0" }, "funding": [ { @@ -1853,20 +1768,20 @@ "type": "tidelift" } ], - "time": "2023-08-16T06:22:46+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/property-access", - "version": "v7.0.0", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "740e8cb8c54a4f16c82179e8558c29d9fc49901d" + "reference": "44e3746d4de8d0961a44ee332c74dd0918266127" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/740e8cb8c54a4f16c82179e8558c29d9fc49901d", - "reference": "740e8cb8c54a4f16c82179e8558c29d9fc49901d", + "url": "https://api.github.com/repos/symfony/property-access/zipball/44e3746d4de8d0961a44ee332c74dd0918266127", + "reference": "44e3746d4de8d0961a44ee332c74dd0918266127", "shasum": "" }, "require": { @@ -1913,7 +1828,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.0.0" + "source": "https://github.com/symfony/property-access/tree/v7.0.4" }, "funding": [ { @@ -1929,20 +1844,20 @@ "type": "tidelift" } ], - "time": "2023-09-27T14:05:33+00:00" + "time": "2024-02-16T13:44:10+00:00" }, { "name": "symfony/property-info", - "version": "v7.0.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "ce627df05f5629ce4feec536ee827ad0a12689b6" + "reference": "e160f92ea827243abf2dbf36b8460b1377194406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/ce627df05f5629ce4feec536ee827ad0a12689b6", - "reference": "ce627df05f5629ce4feec536ee827ad0a12689b6", + "url": "https://api.github.com/repos/symfony/property-info/zipball/e160f92ea827243abf2dbf36b8460b1377194406", + "reference": "e160f92ea827243abf2dbf36b8460b1377194406", "shasum": "" }, "require": { @@ -1996,7 +1911,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.0.0" + "source": "https://github.com/symfony/property-info/tree/v7.0.3" }, "funding": [ { @@ -2012,20 +1927,20 @@ "type": "tidelift" } ], - "time": "2023-11-25T08:38:27+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/routing", - "version": "v7.0.2", + "version": "v7.0.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "78866be67255f42716271e33d1d8b64eb6e47bd9" + "reference": "ba6bf07d43289c6a4b4591ddb75bc3bc5f069c19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/78866be67255f42716271e33d1d8b64eb6e47bd9", - "reference": "78866be67255f42716271e33d1d8b64eb6e47bd9", + "url": "https://api.github.com/repos/symfony/routing/zipball/ba6bf07d43289c6a4b4591ddb75bc3bc5f069c19", + "reference": "ba6bf07d43289c6a4b4591ddb75bc3bc5f069c19", "shasum": "" }, "require": { @@ -2077,7 +1992,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.0.2" + "source": "https://github.com/symfony/routing/tree/v7.0.5" }, "funding": [ { @@ -2093,20 +2008,20 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:37:40+00:00" + "time": "2024-02-27T12:34:35+00:00" }, { "name": "symfony/serializer", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "dd7d7612f9ae288889caba4bdff79424ce4ffdf0" + "reference": "c71d61c6c37804e10981960e5f5ebc2c8f0a4fbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/dd7d7612f9ae288889caba4bdff79424ce4ffdf0", - "reference": "dd7d7612f9ae288889caba4bdff79424ce4ffdf0", + "url": "https://api.github.com/repos/symfony/serializer/zipball/c71d61c6c37804e10981960e5f5ebc2c8f0a4fbb", + "reference": "c71d61c6c37804e10981960e5f5ebc2c8f0a4fbb", "shasum": "" }, "require": { @@ -2172,7 +2087,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.0.2" + "source": "https://github.com/symfony/serializer/tree/v7.0.4" }, "funding": [ { @@ -2188,7 +2103,7 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:37:40+00:00" + "time": "2024-02-22T20:27:20+00:00" }, { "name": "symfony/service-contracts", @@ -2274,16 +2189,16 @@ }, { "name": "symfony/string", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "cc78f14f91f5e53b42044d0620961c48028ff9f5" + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/cc78f14f91f5e53b42044d0620961c48028ff9f5", - "reference": "cc78f14f91f5e53b42044d0620961c48028ff9f5", + "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b", "shasum": "" }, "require": { @@ -2340,7 +2255,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.0.2" + "source": "https://github.com/symfony/string/tree/v7.0.4" }, "funding": [ { @@ -2356,7 +2271,7 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:54:46+00:00" + "time": "2024-02-01T13:17:36+00:00" }, { "name": "symfony/translation-contracts", @@ -2438,16 +2353,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "d6236c6e75ee70317a27f0fd4c3f9bb956f22366" + "reference": "d16aa4eb5bdaeb6e7407782431dc70530f3b1df5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d6236c6e75ee70317a27f0fd4c3f9bb956f22366", - "reference": "d6236c6e75ee70317a27f0fd4c3f9bb956f22366", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d16aa4eb5bdaeb6e7407782431dc70530f3b1df5", + "reference": "d16aa4eb5bdaeb6e7407782431dc70530f3b1df5", "shasum": "" }, "require": { @@ -2490,7 +2405,7 @@ "symfony/security-core": "^6.4|^7.0", "symfony/security-csrf": "^6.4|^7.0", "symfony/security-http": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", "symfony/stopwatch": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", "symfony/web-link": "^6.4|^7.0", @@ -2526,7 +2441,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.0.2" + "source": "https://github.com/symfony/twig-bridge/tree/v7.0.4" }, "funding": [ { @@ -2542,20 +2457,20 @@ "type": "tidelift" } ], - "time": "2023-12-15T12:36:57+00:00" + "time": "2024-02-15T11:33:06+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.0.0", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "42c4a60f1b83894cd85a6b00533f8216c413ac11" + "reference": "acab2368f53491e018bf31ef48b39df55a6812ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/42c4a60f1b83894cd85a6b00533f8216c413ac11", - "reference": "42c4a60f1b83894cd85a6b00533f8216c413ac11", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/acab2368f53491e018bf31ef48b39df55a6812ef", + "reference": "acab2368f53491e018bf31ef48b39df55a6812ef", "shasum": "" }, "require": { @@ -2610,7 +2525,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v7.0.0" + "source": "https://github.com/symfony/twig-bundle/tree/v7.0.4" }, "funding": [ { @@ -2626,20 +2541,20 @@ "type": "tidelift" } ], - "time": "2023-11-26T15:16:53+00:00" + "time": "2024-02-15T11:33:06+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5f6f1a527002068f6d40fda068399220eabebf71" + "reference": "e03ad7c1535e623edbb94c22cc42353e488c6670" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5f6f1a527002068f6d40fda068399220eabebf71", - "reference": "5f6f1a527002068f6d40fda068399220eabebf71", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e03ad7c1535e623edbb94c22cc42353e488c6670", + "reference": "e03ad7c1535e623edbb94c22cc42353e488c6670", "shasum": "" }, "require": { @@ -2693,7 +2608,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.0.2" + "source": "https://github.com/symfony/var-dumper/tree/v7.0.4" }, "funding": [ { @@ -2709,20 +2624,20 @@ "type": "tidelift" } ], - "time": "2023-12-28T19:18:20+00:00" + "time": "2024-02-15T11:33:06+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "345c62fefe92243c3a06fc0cc65f2ec1a47e0764" + "reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/345c62fefe92243c3a06fc0cc65f2ec1a47e0764", - "reference": "345c62fefe92243c3a06fc0cc65f2ec1a47e0764", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41", + "reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41", "shasum": "" }, "require": { @@ -2767,7 +2682,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.0.2" + "source": "https://github.com/symfony/var-exporter/tree/v7.0.4" }, "funding": [ { @@ -2783,7 +2698,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T08:42:13+00:00" + "time": "2024-02-26T10:35:24+00:00" }, { "name": "twig/twig", @@ -2861,16 +2776,16 @@ "packages-dev": [ { "name": "composer/pcre", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" + "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace", + "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace", "shasum": "" }, "require": { @@ -2912,7 +2827,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.1" + "source": "https://github.com/composer/pcre/tree/3.1.2" }, "funding": [ { @@ -2928,7 +2843,7 @@ "type": "tidelift" } ], - "time": "2023-10-11T07:11:09+00:00" + "time": "2024-03-07T15:38:35+00:00" }, { "name": "composer/semver", @@ -3079,16 +2994,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.46.0", + "version": "v3.52.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2" + "reference": "a3564bd66f4bce9bc871ef18b690e2dc67a7f969" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/be6831c9af1740470d2a773119b9273f8ac1c3d2", - "reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a3564bd66f4bce9bc871ef18b690e2dc67a7f969", + "reference": "a3564bd66f4bce9bc871ef18b690e2dc67a7f969", "shasum": "" }, "require": { @@ -3098,7 +3013,7 @@ "ext-json": "*", "ext-tokenizer": "*", "php": "^7.4 || ^8.0", - "sebastian/diff": "^4.0 || ^5.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", @@ -3119,7 +3034,8 @@ "php-cs-fixer/accessible-object": "^1.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", - "phpunit/phpunit": "^9.6 || ^10.5.5", + "phpunit/phpunit": "^9.6 || ^10.5.5 || ^11.0.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { @@ -3158,7 +3074,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.46.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.52.0" }, "funding": [ { @@ -3166,7 +3082,7 @@ "type": "github" } ], - "time": "2024-01-03T21:38:46+00:00" + "time": "2024-03-18T18:40:11+00:00" }, { "name": "masterminds/html5", @@ -3237,16 +3153,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.55", + "version": "1.10.63", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949" + "reference": "ad12836d9ca227301f5fb9960979574ed8628339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9a88f9d18ddf4cf54c922fbeac16c4cb164c5949", - "reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ad12836d9ca227301f5fb9960979574ed8628339", + "reference": "ad12836d9ca227301f5fb9960979574ed8628339", "shasum": "" }, "require": { @@ -3295,26 +3211,26 @@ "type": "tidelift" } ], - "time": "2024-01-08T12:32:40+00:00" + "time": "2024-03-18T16:53:53+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.6", + "version": "1.3.9", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "34b3c43684834f6a20aa51af8d455480d9de8b88" + "reference": "a32bc86da24495025d7aafd1ba62444d4a364a98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/34b3c43684834f6a20aa51af8d455480d9de8b88", - "reference": "34b3c43684834f6a20aa51af8d455480d9de8b88", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/a32bc86da24495025d7aafd1ba62444d4a364a98", + "reference": "a32bc86da24495025d7aafd1ba62444d4a364a98", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.36" + "phpstan/phpstan": "^1.10.62" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -3365,35 +3281,35 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.6" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.9" }, - "time": "2023-12-22T11:22:34+00:00" + "time": "2024-03-16T16:50:20+00:00" }, { "name": "sebastian/diff", - "version": "5.1.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" + "reference": "ab83243ecc233de5655b76f577711de9f842e712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", - "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ab83243ecc233de5655b76f577711de9f842e712", + "reference": "ab83243ecc233de5655b76f577711de9f842e712", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -3426,7 +3342,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.1" }, "funding": [ { @@ -3434,20 +3350,20 @@ "type": "github" } ], - "time": "2023-12-22T10:55:06+00:00" + "time": "2024-03-02T07:30:33+00:00" }, { "name": "symfony/browser-kit", - "version": "v7.0.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "c53a6e9bcb4528be535d458450b07aa81620459e" + "reference": "725d5b15681685ac17b20b575254c75639722488" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c53a6e9bcb4528be535d458450b07aa81620459e", - "reference": "c53a6e9bcb4528be535d458450b07aa81620459e", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/725d5b15681685ac17b20b575254c75639722488", + "reference": "725d5b15681685ac17b20b575254c75639722488", "shasum": "" }, "require": { @@ -3486,7 +3402,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v7.0.0" + "source": "https://github.com/symfony/browser-kit/tree/v7.0.3" }, "funding": [ { @@ -3502,20 +3418,20 @@ "type": "tidelift" } ], - "time": "2023-10-31T17:37:24+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/console", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f8587c4cdc5acad67af71c37db34ef03af91e59c" + "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f8587c4cdc5acad67af71c37db34ef03af91e59c", - "reference": "f8587c4cdc5acad67af71c37db34ef03af91e59c", + "url": "https://api.github.com/repos/symfony/console/zipball/6b099f3306f7c9c2d2786ed736d0026b2903205f", + "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f", "shasum": "" }, "require": { @@ -3579,7 +3495,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.0.2" + "source": "https://github.com/symfony/console/tree/v7.0.4" }, "funding": [ { @@ -3595,20 +3511,20 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:54:46+00:00" + "time": "2024-02-22T20:27:20+00:00" }, { "name": "symfony/dom-crawler", - "version": "v7.0.0", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "d13205f444a535f4a6e52186aedbc99664f66a86" + "reference": "6cb272cbec4dc7a30a853d2931766b03bea92dda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d13205f444a535f4a6e52186aedbc99664f66a86", - "reference": "d13205f444a535f4a6e52186aedbc99664f66a86", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/6cb272cbec4dc7a30a853d2931766b03bea92dda", + "reference": "6cb272cbec4dc7a30a853d2931766b03bea92dda", "shasum": "" }, "require": { @@ -3646,7 +3562,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.0.0" + "source": "https://github.com/symfony/dom-crawler/tree/v7.0.4" }, "funding": [ { @@ -3662,20 +3578,87 @@ "type": "tidelift" } ], - "time": "2023-11-20T16:43:42+00:00" + "time": "2024-02-12T11:15:03+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "700ff4096e346f54cb628ea650767c8130f1001f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f", + "reference": "700ff4096e346f54cb628ea650767c8130f1001f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-08T10:20:21+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "92df075808c9437beca9540e25ae0c40eea1c061" + "reference": "54ca13ec990a40411ad978e08d994fca6cdd865f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/92df075808c9437beca9540e25ae0c40eea1c061", - "reference": "92df075808c9437beca9540e25ae0c40eea1c061", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/54ca13ec990a40411ad978e08d994fca6cdd865f", + "reference": "54ca13ec990a40411ad978e08d994fca6cdd865f", "shasum": "" }, "require": { @@ -3727,7 +3710,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.2" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.4" }, "funding": [ { @@ -3743,20 +3726,20 @@ "type": "tidelift" } ], - "time": "2023-12-19T11:23:03+00:00" + "time": "2024-02-08T19:22:56+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", "shasum": "" }, "require": { @@ -3764,9 +3747,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3806,7 +3786,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" }, "funding": [ { @@ -3822,20 +3802,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", - "version": "v7.0.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a" + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a", - "reference": "acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a", + "url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9", "shasum": "" }, "require": { @@ -3867,7 +3847,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.0.2" + "source": "https://github.com/symfony/process/tree/v7.0.4" }, "funding": [ { @@ -3883,20 +3863,20 @@ "type": "tidelift" } ], - "time": "2023-12-24T09:15:37+00:00" + "time": "2024-02-22T20:27:20+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.0.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "7bbfa3dd564a0ce12eb4acaaa46823c740f9cb7a" + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/7bbfa3dd564a0ce12eb4acaaa46823c740f9cb7a", - "reference": "7bbfa3dd564a0ce12eb4acaaa46823c740f9cb7a", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/983900d6fddf2b0cbaacacbbad07610854bd8112", + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112", "shasum": "" }, "require": { @@ -3929,7 +3909,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.0.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.0.3" }, "funding": [ { @@ -3945,7 +3925,7 @@ "type": "tidelift" } ], - "time": "2023-07-05T13:06:06+00:00" + "time": "2024-01-23T15:02:46+00:00" } ], "aliases": [], diff --git a/Resources/config/routing/routing.xml b/config/routes.xml similarity index 72% rename from Resources/config/routing/routing.xml rename to config/routes.xml index 49f6892..a0d98d8 100644 --- a/Resources/config/routing/routing.xml +++ b/config/routes.xml @@ -5,11 +5,7 @@ xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + nanofelis_json_rpc.action.rpc - - - nanofelis_json_rpc.action.doc - diff --git a/Resources/config/services.xml b/config/services.xml similarity index 64% rename from Resources/config/services.xml rename to config/services.xml index 8262182..eccba7a 100644 --- a/Resources/config/services.xml +++ b/config/services.xml @@ -4,44 +4,33 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - + class="Nanofelis\JsonRpcBundle\Request\RpcRequestHandler" public="false"> - - - - - - - - - - - diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..50746ad --- /dev/null +++ b/docs/index.md @@ -0,0 +1,195 @@ + +Configuration +============ + +Load the routing configuration +------------------------------ +Import the main routing file or create a custom route: + +```yaml +# config/routes.yaml +rpc: + resource: '@NanofelisJsonRpcBundle/Resources/config/routing.xml' +``` +or +```yaml +# config/routes.yaml +rpc: + path: / + controller: nanofelis_json_rpc.action.rpc + methods: POST + +rpc_doc: + path: /doc + controller: nanofelis_json_rpc.action.doc + methods: GET +``` + +Usage +===== + +Simply Tag the services you want to expose and send a json-rpc payload to the RPC endpoint. + +The method parameter must follow the convention `{serviceKey}.{method}` + +```yaml +# config/services.yaml + +App\RpcServices: + resource: src/RpcServices + tag: ['nanofelis_json_rpc'] +``` + +```php +namespace App\RpcServices; + +use Nanofelis\JsonRpcBundle\Service\AbstractRpcService; + +class MyService extends AbstractRpcService +{ + public static function getServiceKey(): string + { + return 'myService'; + } + + function add(int $a, int $b): int + { + return $a + $b; + } +} +``` + +```shell script +# Example call with success response +curl -d '{"jsonrpc": "2.0", "method": "myService.add", "params": [1, 2], "id": "test-call"}' http://localhost | fx this + +{ + "jsonrpc": "2.0", + "result": 3, + "id": "test-call" +} + + +# Example call with wrong method parameters +curl -d '{"jsonrpc": 2.0, "method": "myService.add", "params": [1], "id": "test-call"}' http://localhost | fx this + +{ + "jsonrpc": "2.0", + "error": { + "code": -32602, + "message": "invalid params", + "data": null + }, + "id": "test-call" +} + +``` + +Only exceptions that extend the [AbstractRpcException.php](../src/Exception/AbstractRpcException.php) will be cast +to a [JSON-RPC error](https://www.jsonrpc.org/specification#error_object). + +Batch Requests +-------------- +As described by the RFC, multiple requests can be sent in a single call. + + ```shell script +# Example batch call +curl -d '[{"jsonrpc": "2.0", "method": "myService.add", "params": [1, 2], "id": "test-call-0"}, {"jsonrpc": "2.0", "method": "myService.add", "params": [3, 4], "id": "test-call-0"}]' http://localhost | fx this + +[ + { + "jsonrpc": "2.0", + "result": 3, + "id": "test-call-0" + }, + { + "jsonrpc": "2.0", + "result": 7, + "id": "test-call-1" + } +] + +``` + +Arguments Resolver +---------------- +This bundle supports the built-in [Argument Resolver](https://symfony.com/doc/current/controller/value_resolver.html) from the Symfony Core for RPC methods. + +As for a regular controller method, dates and Doctrine entities for example are automatically converted if a parameter's name matches a method argument with the correct type hinting or attribute. + +```php +namespace App\RpcServices; + +class MyService +{ + function workWithEntity(MyEntity $entity, #[MapDateTime(format: 'Y-m-d')] \DateTime $date) + { + // + } +} + +```shell script +# Example call with type hinting +curl -d '{'jsonrpc': "2.0", "method": "myService.workWithEntity", "params": ["entity": 1, "date": "2017-01-01"]}' http://localhost +``` + +Normalization and Contexts +-------------------------- +Responses are always processed by a Symfony normalizer. If you need to specify a normalization context, you can use the `RpcNormalizationContext` attribute: + +```php +namespace App\RpcServices; + +use Nanofelis\JsonRpcBundle\Attribute\RpcNormalizationContext; + +class MyService +{ + + #[RpcNormalizationContext(contexts: ['custom'])] + function doSomething($data): Article + { + $article = $this->handler($data); + + return $article; + } +} +``` + +```php +namespace App\Normalizer; + +use App\Entity\Article; + +class ArticleNormalizer implements NormalizerInterface +{ + public function normalize($vehicle, $format = null, array $context = []) + { + if (in_array('custom', $context) { + ... + } + } + + public function supportsNormalization($data, $format = null): bool + { + return $data instanceof Article; + } +} +``` + +Events Hooks +------------ +You can hook to the following event in the rpc request lifecycle: + +__nanofelis_json_rpc.before_method__ +__Event Class__: RpcBeforeMethodEvent + +This event is dispatched just before the method execution. You can use it to alter the rpc request params. +```php +use Symfony\Component\HttpKernel\Event\ControllerEvent; + +public function onRpcBeforeMethod(RpcBeforeMethodEvent $event) +{ + $rpcRequest = $event->getRpcRequest(); + $serviceDescriptor = $event->getServiceDescriptor(); +} +``` diff --git a/phpstan.neon b/phpstan.neon index 0db8264..92a14fc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,4 @@ parameters: paths: - - . - excludePaths: - - %rootDir%/../../../vendor - - %rootDir%/../../../Tests - - %rootDir%/../../../var - level: 6 + - %rootDir%/../../../src + level: 9 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 44b4e87..f33e946 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,11 +13,11 @@ - + - ./Tests + ./tests diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 937aaca..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,9 +0,0 @@ -sonar.language=php -sonar.projectKey=nanofelis_NanofelisJsonRpcBundle -sonar.projectName=NanofelisJsonRpcBundle -sonar.projectVersion=1.0 -sonar.sources=. -sonar.inclusions=**/*.php -sonar.exclusions=Tests/**/* -sonar.php.coverage.reportPaths=phpunit.coverage.xml -sonar.php.tests.reportPath=phpunit.report.xml \ No newline at end of file diff --git a/src/Action/Rpc.php b/src/Action/Rpc.php new file mode 100644 index 0000000..2f7d07f --- /dev/null +++ b/src/Action/Rpc.php @@ -0,0 +1,32 @@ +parser->parse($request); + + foreach ($rpcPayload->getRpcRequests() as $rpcRequest) { + $rpcPayload->addRpcResponse($this->rpcRequestHandler->handle($rpcRequest)); + } + + return ($this->responder)($rpcPayload); + } +} diff --git a/Attribute/RpcNormalizationContext.php b/src/Attribute/RpcNormalizationContext.php similarity index 82% rename from Attribute/RpcNormalizationContext.php rename to src/Attribute/RpcNormalizationContext.php index 514409a..0e09b50 100644 --- a/Attribute/RpcNormalizationContext.php +++ b/src/Attribute/RpcNormalizationContext.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Attribute; +namespace Nanofelis\JsonRpcBundle\Attribute; #[\Attribute(\Attribute::TARGET_METHOD)] class RpcNormalizationContext diff --git a/DependencyInjection/NanofelisJsonRpcExtension.php b/src/DependencyInjection/NanofelisJsonRpcExtension.php similarity index 88% rename from DependencyInjection/NanofelisJsonRpcExtension.php rename to src/DependencyInjection/NanofelisJsonRpcExtension.php index eca5acd..5c88cb2 100644 --- a/DependencyInjection/NanofelisJsonRpcExtension.php +++ b/src/DependencyInjection/NanofelisJsonRpcExtension.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\DependencyInjection; +namespace Nanofelis\JsonRpcBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; @@ -19,7 +19,7 @@ class NanofelisJsonRpcExtension extends Extension implements CompilerPassInterfa */ public function load(array $configs, ContainerBuilder $container): void { - $loader = new XmlFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); + $loader = new XmlFileLoader($container, new FileLocator(\dirname(__DIR__).'/../config')); $loader->load('services.xml'); } diff --git a/Event/RpcBeforeMethodEvent.php b/src/Event/RpcBeforeMethodEvent.php similarity index 77% rename from Event/RpcBeforeMethodEvent.php rename to src/Event/RpcBeforeMethodEvent.php index 7ced2c9..bce0f5c 100644 --- a/Event/RpcBeforeMethodEvent.php +++ b/src/Event/RpcBeforeMethodEvent.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Event; +namespace Nanofelis\JsonRpcBundle\Event; -use Nanofelis\Bundle\JsonRpcBundle\Request\RpcRequest; -use Nanofelis\Bundle\JsonRpcBundle\Service\ServiceDescriptor; +use Nanofelis\JsonRpcBundle\Request\RpcRequest; +use Nanofelis\JsonRpcBundle\Service\ServiceDescriptor; use Symfony\Contracts\EventDispatcher\Event; class RpcBeforeMethodEvent extends Event diff --git a/Exception/AbstractRpcException.php b/src/Exception/AbstractRpcException.php similarity index 93% rename from Exception/AbstractRpcException.php rename to src/Exception/AbstractRpcException.php index 593e818..00bc2cf 100644 --- a/Exception/AbstractRpcException.php +++ b/src/Exception/AbstractRpcException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Exception; +namespace Nanofelis\JsonRpcBundle\Exception; abstract class AbstractRpcException extends \Exception implements RpcDataExceptionInterface { @@ -25,7 +25,7 @@ abstract class AbstractRpcException extends \Exception implements RpcDataExcepti */ private ?array $data = null; - public function __construct(string $message = '', int $code = 0, \Throwable $previous = null) + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null) { if (empty($message) && isset(self::MESSAGES[$code])) { $message = self::MESSAGES[$code]; diff --git a/Exception/RpcApplicationException.php b/src/Exception/RpcApplicationException.php similarity index 87% rename from Exception/RpcApplicationException.php rename to src/Exception/RpcApplicationException.php index 9397238..c8f328b 100644 --- a/Exception/RpcApplicationException.php +++ b/src/Exception/RpcApplicationException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Exception; +namespace Nanofelis\JsonRpcBundle\Exception; class RpcApplicationException extends AbstractRpcException { @@ -11,7 +11,7 @@ class RpcApplicationException extends AbstractRpcException /** * RpcApplicationException constructor. */ - public function __construct(string $message = '', int $code = 0, \Throwable $previous = null) + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null) { if ($code >= self::CODE_RANGE[0] && $code <= self::CODE_RANGE[1]) { throw new \InvalidArgumentException(sprintf('application exception code should be outside range %d to %d, given: %d', self::CODE_RANGE[0], self::CODE_RANGE[1], $code)); diff --git a/Exception/RpcDataExceptionInterface.php b/src/Exception/RpcDataExceptionInterface.php similarity index 84% rename from Exception/RpcDataExceptionInterface.php rename to src/Exception/RpcDataExceptionInterface.php index b011bac..61a0945 100644 --- a/Exception/RpcDataExceptionInterface.php +++ b/src/Exception/RpcDataExceptionInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Exception; +namespace Nanofelis\JsonRpcBundle\Exception; interface RpcDataExceptionInterface { diff --git a/Exception/RpcInternalException.php b/src/Exception/RpcInternalException.php similarity index 56% rename from Exception/RpcInternalException.php rename to src/Exception/RpcInternalException.php index bc2a095..0f904d8 100644 --- a/Exception/RpcInternalException.php +++ b/src/Exception/RpcInternalException.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Exception; +namespace Nanofelis\JsonRpcBundle\Exception; class RpcInternalException extends AbstractRpcException { - public function __construct(string $message = '', \Throwable $previous = null) + public function __construct(string $message = '', ?\Throwable $previous = null) { parent::__construct($message, parent::INTERNAL, $previous); } diff --git a/Exception/RpcInvalidParamsException.php b/src/Exception/RpcInvalidParamsException.php similarity index 57% rename from Exception/RpcInvalidParamsException.php rename to src/Exception/RpcInvalidParamsException.php index 9dec744..4bd1050 100644 --- a/Exception/RpcInvalidParamsException.php +++ b/src/Exception/RpcInvalidParamsException.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Exception; +namespace Nanofelis\JsonRpcBundle\Exception; class RpcInvalidParamsException extends AbstractRpcException { - public function __construct(string $message = '', \Throwable $previous = null) + public function __construct(string $message = '', ?\Throwable $previous = null) { parent::__construct($message, parent::INVALID_PARAMS, $previous); } diff --git a/Exception/RpcInvalidRequestException.php b/src/Exception/RpcInvalidRequestException.php similarity index 58% rename from Exception/RpcInvalidRequestException.php rename to src/Exception/RpcInvalidRequestException.php index 057c4c8..8f994bf 100644 --- a/Exception/RpcInvalidRequestException.php +++ b/src/Exception/RpcInvalidRequestException.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Exception; +namespace Nanofelis\JsonRpcBundle\Exception; class RpcInvalidRequestException extends AbstractRpcException { - public function __construct(string $message = '', \Throwable $previous = null) + public function __construct(string $message = '', ?\Throwable $previous = null) { parent::__construct($message, parent::INVALID_REQUEST, $previous); } diff --git a/Exception/RpcMethodNotFoundException.php b/src/Exception/RpcMethodNotFoundException.php similarity index 58% rename from Exception/RpcMethodNotFoundException.php rename to src/Exception/RpcMethodNotFoundException.php index 3eace5e..14b44c1 100644 --- a/Exception/RpcMethodNotFoundException.php +++ b/src/Exception/RpcMethodNotFoundException.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Exception; +namespace Nanofelis\JsonRpcBundle\Exception; class RpcMethodNotFoundException extends AbstractRpcException { - public function __construct(string $message = '', \Throwable $previous = null) + public function __construct(string $message = '', ?\Throwable $previous = null) { parent::__construct($message, parent::METHOD_NOT_FOUND, $previous); } diff --git a/Exception/RpcParseException.php b/src/Exception/RpcParseException.php similarity index 55% rename from Exception/RpcParseException.php rename to src/Exception/RpcParseException.php index ab91578..76868a8 100644 --- a/Exception/RpcParseException.php +++ b/src/Exception/RpcParseException.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Exception; +namespace Nanofelis\JsonRpcBundle\Exception; class RpcParseException extends AbstractRpcException { - public function __construct(string $message = '', \Throwable $previous = null) + public function __construct(string $message = '', ?\Throwable $previous = null) { parent::__construct($message, parent::PARSE, $previous); } diff --git a/NanofelisJsonRpcBundle.php b/src/NanofelisJsonRpcBundle.php similarity index 52% rename from NanofelisJsonRpcBundle.php rename to src/NanofelisJsonRpcBundle.php index c48d2f7..8f74346 100644 --- a/NanofelisJsonRpcBundle.php +++ b/src/NanofelisJsonRpcBundle.php @@ -2,10 +2,14 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle; +namespace Nanofelis\JsonRpcBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; class NanofelisJsonRpcBundle extends Bundle { + public function getPath(): string + { + return \dirname(__DIR__); + } } diff --git a/Request/RpcPayload.php b/src/Request/RpcPayload.php similarity index 56% rename from Request/RpcPayload.php rename to src/Request/RpcPayload.php index 656c4c6..171473d 100644 --- a/Request/RpcPayload.php +++ b/src/Request/RpcPayload.php @@ -2,7 +2,9 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Request; +namespace Nanofelis\JsonRpcBundle\Request; + +use Nanofelis\JsonRpcBundle\Response\RpcResponseInterface; class RpcPayload { @@ -11,8 +13,21 @@ class RpcPayload */ private array $rpcRequests = []; + /** + * @var RpcResponseInterface[] + */ + private array $rpcResponses = []; + private bool $isBatch = false; + /** + * @return RpcResponseInterface[] + */ + public function getRpcResponses(): array + { + return $this->rpcResponses; + } + /** * @return RpcRequest[] */ @@ -26,6 +41,11 @@ public function addRpcRequest(RpcRequest $rpcRequest): void $this->rpcRequests[] = $rpcRequest; } + public function addRpcResponse(RpcResponseInterface $rpcResponse): void + { + $this->rpcResponses[] = $rpcResponse; + } + public function isBatch(): bool { return $this->isBatch; diff --git a/src/Request/RpcRequest.php b/src/Request/RpcRequest.php new file mode 100644 index 0000000..ebb3bbe --- /dev/null +++ b/src/Request/RpcRequest.php @@ -0,0 +1,44 @@ +|null + */ + private ?array $params = null, + ) { + } + + public function getId(): string|int|null + { + return $this->id; + } + + public function getServiceKey(): string + { + return $this->serviceKey; + } + + public function getMethodKey(): string + { + return $this->methodKey; + } + + /** + * @return array|null $params + */ + public function getParams(): ?array + { + return $this->params; + } +} diff --git a/Request/RpcRequestHandler.php b/src/Request/RpcRequestHandler.php similarity index 55% rename from Request/RpcRequestHandler.php rename to src/Request/RpcRequestHandler.php index 351cacf..703ad10 100644 --- a/Request/RpcRequestHandler.php +++ b/src/Request/RpcRequestHandler.php @@ -2,20 +2,18 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Request; - -use Nanofelis\Bundle\JsonRpcBundle\Attribute\RpcNormalizationContext; -use Nanofelis\Bundle\JsonRpcBundle\Event\RpcBeforeMethodEvent; -use Nanofelis\Bundle\JsonRpcBundle\Exception\AbstractRpcException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcApplicationException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcDataExceptionInterface; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcInvalidParamsException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcMethodNotFoundException; -use Nanofelis\Bundle\JsonRpcBundle\Response\RpcResponse; -use Nanofelis\Bundle\JsonRpcBundle\Response\RpcResponseError; -use Nanofelis\Bundle\JsonRpcBundle\Service\ServiceDescriptor; -use Nanofelis\Bundle\JsonRpcBundle\Service\ServiceFinder; -use PHPStan\BetterReflection\Reflection\ReflectionAttribute; +namespace Nanofelis\JsonRpcBundle\Request; + +use Nanofelis\JsonRpcBundle\Attribute\RpcNormalizationContext; +use Nanofelis\JsonRpcBundle\Event\RpcBeforeMethodEvent; +use Nanofelis\JsonRpcBundle\Exception\AbstractRpcException; +use Nanofelis\JsonRpcBundle\Exception\RpcInvalidParamsException; +use Nanofelis\JsonRpcBundle\Exception\RpcMethodNotFoundException; +use Nanofelis\JsonRpcBundle\Response\RpcResponse; +use Nanofelis\JsonRpcBundle\Response\RpcResponseError; +use Nanofelis\JsonRpcBundle\Response\RpcResponseInterface; +use Nanofelis\JsonRpcBundle\Service\ServiceDescriptor; +use Nanofelis\JsonRpcBundle\Service\ServiceFinder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\Serializer\Exception\ExceptionInterface; @@ -32,18 +30,20 @@ public function __construct( ) { } - public function handle(RpcRequest $rpcRequest): void + /** + * @throws AbstractRpcException + * @throws ExceptionInterface + */ + public function handle(RpcRequest $rpcRequest): RpcResponseInterface { - if ($rpcRequest->getResponse()) { - return; - } - try { - $result = $this->execute($rpcRequest); - $rpcRequest->setResponse(new RpcResponse($result, $rpcRequest->getId())); + return new RpcResponse($this->execute($rpcRequest), $rpcRequest->getId()); } catch (\Throwable $e) { - $e = $this->castToRpcException($e); - $rpcRequest->setResponse(new RpcResponseError($e, $rpcRequest->getId())); + if (!$e instanceof AbstractRpcException) { + throw $e; + } + + return new RpcResponseError($e, $rpcRequest->getId()); } } @@ -55,21 +55,24 @@ public function handle(RpcRequest $rpcRequest): void private function execute(RpcRequest $rpcRequest): mixed { $serviceDescriptor = $this->serviceFinder->find($rpcRequest); - [$service, $method] = [$serviceDescriptor->getService(), $serviceDescriptor->getMethodName()]; + $service = $serviceDescriptor->getService(); + $method = $serviceDescriptor->getMethodName(); + /** @var callable $callable */ + $callable = [$service, $method]; $this->eventDispatcher->dispatch(new RpcBeforeMethodEvent($rpcRequest, $serviceDescriptor), RpcBeforeMethodEvent::NAME); try { $arguments = $this->argumentResolver->getArguments( new Request(attributes: $rpcRequest->getParams() ?? []), - [$service, $method] + $callable ); } catch (\Exception $e) { throw new RpcInvalidParamsException(previous: $e); } try { - $result = $service->$method(...$arguments); + $result = $callable(...$arguments); } catch (\TypeError $e) { if ($this->isInvalidParamsException($e, $serviceDescriptor)) { throw new RpcInvalidParamsException(previous: $e); @@ -84,30 +87,16 @@ private function isInvalidParamsException(\TypeError $e, ServiceDescriptor $serv { $trace = $e->getTrace(); + /* @phpstan-ignore-next-line */ return $trace[0]['class'] === $serviceDescriptor->getServiceClass() && $trace[0]['function'] === $serviceDescriptor->getMethodName(); } - private function castToRpcException(\Throwable $e): AbstractRpcException - { - if ($e instanceof AbstractRpcException) { - return $e; - } - - $rpcException = new RpcApplicationException($e->getMessage(), $e->getCode(), $e); - - if ($e instanceof RpcDataExceptionInterface) { - $rpcException->setData($e->getData()); - } - - return $rpcException; - } - /** * @throws ExceptionInterface */ private function normalizeResult(mixed $result, ServiceDescriptor $serviceDescriptor): mixed { - /** @var ReflectionAttribute|null $normalizationConfig */ + /** @var \ReflectionAttribute|null $normalizationConfig */ $normalizationConfig = $serviceDescriptor->getMethodAttribute(RpcNormalizationContext::class); $contexts = $normalizationConfig?->getArguments()[0] ?? []; diff --git a/src/Request/RpcRequestParser.php b/src/Request/RpcRequestParser.php new file mode 100644 index 0000000..23dc263 --- /dev/null +++ b/src/Request/RpcRequestParser.php @@ -0,0 +1,104 @@ +serializer = new Serializer([new GetSetMethodNormalizer()]); + } + + public function parse(Request $request): RpcPayload + { + try { + $data = $this->getPostData($request); + + return $this->getRpcPayload($data); + } catch (AbstractRpcException $e) { + $payload = new RpcPayload(); + $payload->addRpcResponse(new RpcResponseError($e)); + + return $payload; + } + } + + /** + * @return mixed[] + * + * @throws RpcParseException|RpcInvalidRequestException + */ + private function getPostData(Request $request): array + { + try { + $data = json_decode((string) $request->getContent(), true, flags: \JSON_THROW_ON_ERROR); + } catch (\Exception $e) { + throw new RpcParseException(previous: $e); + } + if (!\is_array($data)) { + throw new RpcInvalidRequestException(); + } + + return $data; + } + + /** + * @param array $data + * + * @throws RpcInvalidRequestException + */ + private function getRpcPayload(mixed $data): RpcPayload + { + $payload = new RpcPayload(); + + if (!array_is_list($data)) { + $payload->addRpcRequest($this->getRpcRequest($data)); + + return $payload; + } + $payload->setIsBatch(true); + + foreach ($data as $subData) { + $payload->addRpcRequest($this->getRpcRequest((array) $subData)); + } + + return $payload; + } + + /** + * @param array $data + * + * @throws RpcInvalidRequestException + */ + private function getRpcRequest(array $data): RpcRequest + { + if ($data['jsonrpc'] !== RpcRequest::JSON_RPC_VERSION) { + throw new RpcInvalidRequestException(); + } + $methodParts = explode('.', $data['method'] ?? ''); + + if (count($methodParts) !== 2) { + throw new RpcInvalidRequestException(); + } + + return new RpcRequest( + serviceKey: $methodParts[0], + methodKey: $methodParts[1], + id: $data['id'] ?? null, + params: $data['params'] ?? null + ); + } +} diff --git a/src/Responder/RpcResponder.php b/src/Responder/RpcResponder.php new file mode 100644 index 0000000..3518979 --- /dev/null +++ b/src/Responder/RpcResponder.php @@ -0,0 +1,25 @@ +getRpcResponses(); + + if ($payload->isBatch()) { + $responseContent = array_map(fn (RpcResponseInterface $rpcResponse) => $rpcResponse->getContent(), $responses); + } else { + $responseContent = $responses[0]->getContent(); + } + + return new JsonResponse($responseContent); + } +} diff --git a/Response/RpcResponse.php b/src/Response/RpcResponse.php similarity index 75% rename from Response/RpcResponse.php rename to src/Response/RpcResponse.php index 22f989b..c90d3df 100644 --- a/Response/RpcResponse.php +++ b/src/Response/RpcResponse.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Response; +namespace Nanofelis\JsonRpcBundle\Response; -use Nanofelis\Bundle\JsonRpcBundle\Request\RpcRequest; +use Nanofelis\JsonRpcBundle\Request\RpcRequest; class RpcResponse implements RpcResponseInterface { /** * RpcResponse constructor. */ - public function __construct(private mixed $data, private mixed $id = null) + public function __construct(private mixed $data, private int|string|null $id = null) { } diff --git a/Response/RpcResponseError.php b/src/Response/RpcResponseError.php similarity index 68% rename from Response/RpcResponseError.php rename to src/Response/RpcResponseError.php index 15d9b25..3d2d85a 100644 --- a/Response/RpcResponseError.php +++ b/src/Response/RpcResponseError.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Response; +namespace Nanofelis\JsonRpcBundle\Response; -use Nanofelis\Bundle\JsonRpcBundle\Exception\AbstractRpcException; -use Nanofelis\Bundle\JsonRpcBundle\Request\RpcRequest; +use Nanofelis\JsonRpcBundle\Exception\AbstractRpcException; +use Nanofelis\JsonRpcBundle\Request\RpcRequest; class RpcResponseError implements RpcResponseInterface { /** * RpcResponseError constructor. */ - public function __construct(private AbstractRpcException $rpcException, private mixed $id = null) + public function __construct(private AbstractRpcException $rpcException, private int|string|null $id = null) { } @@ -36,9 +36,4 @@ public function getRpcException(): AbstractRpcException { return $this->rpcException; } - - public function setRpcException(AbstractRpcException $rpcException): void - { - $this->rpcException = $rpcException; - } } diff --git a/Response/RpcResponseInterface.php b/src/Response/RpcResponseInterface.php similarity index 75% rename from Response/RpcResponseInterface.php rename to src/Response/RpcResponseInterface.php index 6404ea0..902ee74 100644 --- a/Response/RpcResponseInterface.php +++ b/src/Response/RpcResponseInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Response; +namespace Nanofelis\JsonRpcBundle\Response; interface RpcResponseInterface { diff --git a/Service/AbstractRpcService.php b/src/Service/AbstractRpcService.php similarity index 72% rename from Service/AbstractRpcService.php rename to src/Service/AbstractRpcService.php index acdcd4f..f694142 100644 --- a/Service/AbstractRpcService.php +++ b/src/Service/AbstractRpcService.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Service; +namespace Nanofelis\JsonRpcBundle\Service; abstract class AbstractRpcService { diff --git a/Service/ServiceDescriptor.php b/src/Service/ServiceDescriptor.php similarity index 90% rename from Service/ServiceDescriptor.php rename to src/Service/ServiceDescriptor.php index 3a84164..0109875 100644 --- a/Service/ServiceDescriptor.php +++ b/src/Service/ServiceDescriptor.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Service; +namespace Nanofelis\JsonRpcBundle\Service; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcMethodNotFoundException; +use Nanofelis\JsonRpcBundle\Exception\RpcMethodNotFoundException; class ServiceDescriptor { diff --git a/src/Service/ServiceFinder.php b/src/Service/ServiceFinder.php new file mode 100644 index 0000000..f022126 --- /dev/null +++ b/src/Service/ServiceFinder.php @@ -0,0 +1,45 @@ + + */ + private array $rpcServices; + + /** + * @param \Traversable $rpcServices + */ + public function __construct(\Traversable $rpcServices) + { + $this->rpcServices = iterator_to_array($rpcServices); + } + + /** + * @throws RpcMethodNotFoundException + */ + public function find(RpcRequest $rpcRequest): ServiceDescriptor + { + if (!$service = ($this->rpcServices[$rpcRequest->getServiceKey()] ?? null)) { + throw new RpcMethodNotFoundException(); + } + + return new ServiceDescriptor($service, $rpcRequest->getMethodKey()); + } + + /** + * @return array + */ + public function getRpcServices(): array + { + return $this->rpcServices; + } +} diff --git a/Resources/views/doc.html.twig b/templates/doc.html.twig similarity index 100% rename from Resources/views/doc.html.twig rename to templates/doc.html.twig diff --git a/Tests/Action/RpcTest.php b/tests/Action/RpcTest.php similarity index 83% rename from Tests/Action/RpcTest.php rename to tests/Action/RpcTest.php index de4f866..e85d52e 100644 --- a/Tests/Action/RpcTest.php +++ b/tests/Action/RpcTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Action; +namespace Nanofelis\JsonRpcBundle\Tests\Action; -use Nanofelis\Bundle\JsonRpcBundle\Exception\AbstractRpcException; -use Nanofelis\Bundle\JsonRpcBundle\Tests\TestKernel; +use Nanofelis\JsonRpcBundle\Exception\AbstractRpcException; +use Nanofelis\JsonRpcBundle\Tests\TestKernel; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Routing\RouterInterface; @@ -27,7 +27,7 @@ protected static function getKernelClass(): string return TestKernel::class; } - public function testInvalidJson() + public function testInvalidJson(): void { self::$client->request(method: 'POST', uri: $this->router->generate('nanofelis_json_rpc.endpoint'), content: '@'); $expected = [ @@ -45,7 +45,7 @@ public function testInvalidJson() /** * @dataProvider provideRpcRequest */ - public function testRpc(array $requestData, array $expected) + public function testRpc(array $requestData, array $expected): void { self::$client->request(method: 'POST', uri: $this->router->generate('nanofelis_json_rpc.endpoint'), content: json_encode($requestData)); @@ -54,25 +54,25 @@ public function testRpc(array $requestData, array $expected) public function provideRpcRequest(): \Generator { - // Test regular rpc request + // regular rpc request yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.add', 'params' => ['arg1' => 1, 'arg2' => 2], 'id' => 'test'], ['jsonrpc' => '2.0', 'result' => 3, 'id' => 'test'], ]; - // Test regular rpc request with params in wrong order + // regular rpc request with params in wrong order yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.add', 'params' => ['arg2' => 1, 'arg1' => 3], 'id' => 'test'], ['jsonrpc' => '2.0', 'result' => 4, 'id' => 'test'], ]; - // Test request resolver + // request resolver yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.requestValueResolver', 'params' => ['date' => '2017/01/01'], 'id' => 'test'], ['jsonrpc' => '2.0', 'result' => 'GET', 'id' => 'test'], ]; - // Test batch of regular rpc request + // batch of regular rpc request yield [ [ ['jsonrpc' => '2.0', 'method' => 'mockService.add', 'params' => ['arg1' => 1, 'arg2' => 2], 'id' => 'test_0'], @@ -84,19 +84,19 @@ public function provideRpcRequest(): \Generator ], ]; - // Test rpc request with an array parameter + // rpc request with an array parameter yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.arrayParam', 'params' => ['a' => [1, 2], 'b' => 3]], ['jsonrpc' => '2.0', 'result' => [1, 2, 3], 'id' => null], ]; - // Test rpc method which returns an object + // rpc method which returns an object yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.returnObject'], ['jsonrpc' => '2.0', 'result' => ['prop' => 'test'], 'id' => null], ]; - // Test unknown method + // unknown method yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.unknownMethod', 'id' => 'test'], ['jsonrpc' => '2.0', 'error' => [ @@ -106,7 +106,7 @@ public function provideRpcRequest(): \Generator ], 'id' => 'test'], ]; - // Test wrong parameter type + // wrong parameter type yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.add', 'params' => ['arg1' => '', 'arg2' => 2], 'id' => 'test'], ['jsonrpc' => '2.0', 'error' => [ @@ -116,7 +116,7 @@ public function provideRpcRequest(): \Generator ], 'id' => 'test'], ]; - // Test wrong params names + // wrong params names yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.add', 'params' => ['wrongParam' => 1]], ['jsonrpc' => '2.0', 'error' => [ @@ -126,7 +126,7 @@ public function provideRpcRequest(): \Generator ], 'id' => null], ]; - // Test application exception handling + // application exception handling yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.willThrowException'], ['jsonrpc' => '2.0', 'error' => [ @@ -136,10 +136,16 @@ public function provideRpcRequest(): \Generator ], 'id' => null], ]; - // Test nullable arguments + // nullable arguments yield [ ['jsonrpc' => '2.0', 'method' => 'mockService.withNullables', 'params' => ['b' => 'beta']], ['jsonrpc' => '2.0', 'result' => ['a' => null, 'b' => 'beta'], 'id' => null], ]; + + // todo: MapRequestPayload annotation + // yield [ + // ['jsonrpc' => '2.0', 'method' => 'mockService.withMapRequest', 'params' => ['a' => 10, 'b' => 'alpha', 'c' => true]], + // ['jsonrpc' => '2.0', 'result' => ['a' => 10, 'b' => 'alpha', 'c' => true], 'id' => null], + // ]; } } diff --git a/Tests/Exception/AbstractRpcExceptionTest.php b/tests/Exception/AbstractRpcExceptionTest.php similarity index 69% rename from Tests/Exception/AbstractRpcExceptionTest.php rename to tests/Exception/AbstractRpcExceptionTest.php index 1d7e57e..8e80d71 100644 --- a/Tests/Exception/AbstractRpcExceptionTest.php +++ b/tests/Exception/AbstractRpcExceptionTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Exception; +namespace Nanofelis\JsonRpcBundle\Tests\Exception; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcInvalidRequestException; +use Nanofelis\JsonRpcBundle\Exception\RpcInvalidRequestException; use PHPUnit\Framework\TestCase; class AbstractRpcExceptionTest extends TestCase diff --git a/Tests/Exception/RpcApplicationExceptionTest.php b/tests/Exception/RpcApplicationExceptionTest.php similarity index 78% rename from Tests/Exception/RpcApplicationExceptionTest.php rename to tests/Exception/RpcApplicationExceptionTest.php index 21a11ae..6859d0e 100644 --- a/Tests/Exception/RpcApplicationExceptionTest.php +++ b/tests/Exception/RpcApplicationExceptionTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Exception; +namespace Nanofelis\JsonRpcBundle\Tests\Exception; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcApplicationException; +use Nanofelis\JsonRpcBundle\Exception\RpcApplicationException; use PHPUnit\Framework\TestCase; class RpcApplicationExceptionTest extends TestCase diff --git a/Tests/Exception/RpcInternalExceptionTest.php b/tests/Exception/RpcInternalExceptionTest.php similarity index 67% rename from Tests/Exception/RpcInternalExceptionTest.php rename to tests/Exception/RpcInternalExceptionTest.php index 8c9cfc9..7a0c21b 100644 --- a/Tests/Exception/RpcInternalExceptionTest.php +++ b/tests/Exception/RpcInternalExceptionTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Exception; +namespace Nanofelis\JsonRpcBundle\Tests\Exception; -use Nanofelis\Bundle\JsonRpcBundle\Exception\AbstractRpcException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcInternalException; +use Nanofelis\JsonRpcBundle\Exception\AbstractRpcException; +use Nanofelis\JsonRpcBundle\Exception\RpcInternalException; use PHPUnit\Framework\TestCase; class RpcInternalExceptionTest extends TestCase diff --git a/Tests/Exception/RpcInvalidParamsExceptionTest.php b/tests/Exception/RpcInvalidParamsExceptionTest.php similarity index 67% rename from Tests/Exception/RpcInvalidParamsExceptionTest.php rename to tests/Exception/RpcInvalidParamsExceptionTest.php index b267993..119a425 100644 --- a/Tests/Exception/RpcInvalidParamsExceptionTest.php +++ b/tests/Exception/RpcInvalidParamsExceptionTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Exception; +namespace Nanofelis\JsonRpcBundle\Tests\Exception; -use Nanofelis\Bundle\JsonRpcBundle\Exception\AbstractRpcException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcInvalidParamsException; +use Nanofelis\JsonRpcBundle\Exception\AbstractRpcException; +use Nanofelis\JsonRpcBundle\Exception\RpcInvalidParamsException; use PHPUnit\Framework\TestCase; class RpcInvalidParamsExceptionTest extends TestCase diff --git a/Tests/Exception/RpcInvalidRequestExceptionTest.php b/tests/Exception/RpcInvalidRequestExceptionTest.php similarity index 67% rename from Tests/Exception/RpcInvalidRequestExceptionTest.php rename to tests/Exception/RpcInvalidRequestExceptionTest.php index b4632f6..3a06453 100644 --- a/Tests/Exception/RpcInvalidRequestExceptionTest.php +++ b/tests/Exception/RpcInvalidRequestExceptionTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Exception; +namespace Nanofelis\JsonRpcBundle\Tests\Exception; -use Nanofelis\Bundle\JsonRpcBundle\Exception\AbstractRpcException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcInvalidRequestException; +use Nanofelis\JsonRpcBundle\Exception\AbstractRpcException; +use Nanofelis\JsonRpcBundle\Exception\RpcInvalidRequestException; use PHPUnit\Framework\TestCase; class RpcInvalidRequestExceptionTest extends TestCase diff --git a/Tests/Exception/RpcMethodNotFoundExceptionTest.php b/tests/Exception/RpcMethodNotFoundExceptionTest.php similarity index 67% rename from Tests/Exception/RpcMethodNotFoundExceptionTest.php rename to tests/Exception/RpcMethodNotFoundExceptionTest.php index e895326..f781761 100644 --- a/Tests/Exception/RpcMethodNotFoundExceptionTest.php +++ b/tests/Exception/RpcMethodNotFoundExceptionTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Exception; +namespace Nanofelis\JsonRpcBundle\Tests\Exception; -use Nanofelis\Bundle\JsonRpcBundle\Exception\AbstractRpcException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcMethodNotFoundException; +use Nanofelis\JsonRpcBundle\Exception\AbstractRpcException; +use Nanofelis\JsonRpcBundle\Exception\RpcMethodNotFoundException; use PHPUnit\Framework\TestCase; class RpcMethodNotFoundExceptionTest extends TestCase diff --git a/Tests/Exception/RpcParseExceptionTest.php b/tests/Exception/RpcParseExceptionTest.php similarity index 66% rename from Tests/Exception/RpcParseExceptionTest.php rename to tests/Exception/RpcParseExceptionTest.php index 8da7105..fe8a3e2 100644 --- a/Tests/Exception/RpcParseExceptionTest.php +++ b/tests/Exception/RpcParseExceptionTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Exception; +namespace Nanofelis\JsonRpcBundle\Tests\Exception; -use Nanofelis\Bundle\JsonRpcBundle\Exception\AbstractRpcException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcParseException; +use Nanofelis\JsonRpcBundle\Exception\AbstractRpcException; +use Nanofelis\JsonRpcBundle\Exception\RpcParseException; use PHPUnit\Framework\TestCase; class RpcParseExceptionTest extends TestCase diff --git a/Tests/Request/RpcRequestHandlerTest.php b/tests/Request/RpcRequestHandlerTest.php similarity index 62% rename from Tests/Request/RpcRequestHandlerTest.php rename to tests/Request/RpcRequestHandlerTest.php index d29f777..49fab81 100644 --- a/Tests/Request/RpcRequestHandlerTest.php +++ b/tests/Request/RpcRequestHandlerTest.php @@ -2,15 +2,16 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Request; - -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcApplicationException; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcInvalidRequestException; -use Nanofelis\Bundle\JsonRpcBundle\Request\RpcRequest; -use Nanofelis\Bundle\JsonRpcBundle\Request\RpcRequestHandler; -use Nanofelis\Bundle\JsonRpcBundle\Response\RpcResponseError; -use Nanofelis\Bundle\JsonRpcBundle\Service\ServiceFinder; -use Nanofelis\Bundle\JsonRpcBundle\Tests\Service\MockService; +namespace Nanofelis\JsonRpcBundle\Tests\Request; + +use Nanofelis\JsonRpcBundle\Exception\RpcApplicationException; +use Nanofelis\JsonRpcBundle\Exception\RpcInvalidParamsException; +use Nanofelis\JsonRpcBundle\Request\RpcRequest; +use Nanofelis\JsonRpcBundle\Request\RpcRequestHandler; +use Nanofelis\JsonRpcBundle\Response\RpcResponse; +use Nanofelis\JsonRpcBundle\Response\RpcResponseError; +use Nanofelis\JsonRpcBundle\Service\ServiceFinder; +use Nanofelis\JsonRpcBundle\Tests\Service\MockService; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -41,20 +42,21 @@ protected function setUp(): void * * @param null $expectedResult */ - public function testHandle(RpcRequest $rpcRequest, $expectedResult = null, RpcResponseError $expectedError = null) + public function testHandle(RpcRequest $rpcRequest, ?RpcResponse $expectedResult = null, ?RpcResponseError $expectedError = null): void { $this->argumentResolver->method('getArguments')->willReturn($rpcRequest->getParams() ?? []); + $this->normalizer->method('normalize')->willReturnArgument(0); + + $response = $this->requestHandler->handle($rpcRequest); if ($expectedError) { - $this->assertSame($expectedResult, $rpcRequest->getResponse()); + $this->assertSame($expectedError->getContent(), $response->getContent()); } else { - $this->normalizer->expects($this->once())->method('normalize')->with($expectedResult, null, []); + $this->assertSame($expectedResult->getContent(), $response->getContent()); } - - $this->requestHandler->handle($rpcRequest); } - public function testNormalizationContext() + public function testNormalizationContext(): void { $rpcRequest = new RpcRequest(serviceKey: 'mockService', methodKey: 'returnObject'); @@ -68,23 +70,20 @@ public function testNormalizationContext() public function provideRpcRequest(): \Generator { - $badTypeRpcRequest = new RpcRequest(serviceKey: 'mockService', methodKey: 'add'); - $badTypeRpcRequest->setParams(['arg1' => '5', 'arg2' => 5]); + $badTypeRpcRequest = new RpcRequest(serviceKey: 'mockService', methodKey: 'add', params: ['arg1' => '5', 'arg2' => 5]); - yield [$badTypeRpcRequest, null, new RpcResponseError(new RpcInvalidRequestException())]; + yield [$badTypeRpcRequest, null, new RpcResponseError(new RpcInvalidParamsException())]; - $badArgCountRpcRequest = new RpcRequest(serviceKey: 'mockService', methodKey: 'add'); - $badArgCountRpcRequest->setParams(['arg1' => 5]); + $badArgCountRpcRequest = new RpcRequest(serviceKey: 'mockService', methodKey: 'add', params: ['arg1' => 5]); - yield [$badArgCountRpcRequest, null, new RpcResponseError(new RpcInvalidRequestException())]; + yield [$badArgCountRpcRequest, null, new RpcResponseError(new RpcInvalidParamsException())]; $exceptionRpcRequest = new RpcRequest(serviceKey: 'mockService', methodKey: 'willThrowException'); - yield [$exceptionRpcRequest, null, new RpcResponseError(new RpcApplicationException())]; + yield [$exceptionRpcRequest, null, new RpcResponseError(new RpcApplicationException('it went wrong', 99))]; - $successRpcRequest = new RpcRequest(serviceKey: 'mockService', methodKey: 'add'); - $successRpcRequest->setParams(['arg1' => 5, 'arg2' => 5]); + $successRpcRequest = new RpcRequest(serviceKey: 'mockService', methodKey: 'add', params: ['arg1' => 5, 'arg2' => 5]); - yield [$successRpcRequest, 10, null]; + yield [$successRpcRequest, new RpcResponse(10), null]; } } diff --git a/tests/Request/RpcRequestParserTest.php b/tests/Request/RpcRequestParserTest.php new file mode 100644 index 0000000..71b14fc --- /dev/null +++ b/tests/Request/RpcRequestParserTest.php @@ -0,0 +1,53 @@ +parser = new RpcRequestParser(); + } + + public function testParsePostRequest(): void + { + $request = Request::create(uri: '/', method: 'POST', content: json_encode([ + 'jsonrpc' => '2.0', + 'method' => 'mockService.add', + 'params' => [1, 2], + 'id' => 'test', + ])); + + $payload = $this->parser->parse($request); + + $this->assertInstanceOf(RpcPayload::class, $payload); + $rpcRequest = $payload->getRpcRequests()[0]; + + $this->assertEmpty($payload->getRpcResponses()); + $this->assertSame('add', $rpcRequest->getMethodKey()); + $this->assertSame('mockService', $rpcRequest->getServiceKey()); + $this->assertSame([1, 2], $rpcRequest->getParams()); + $this->assertSame('test', $rpcRequest->getId()); + } + + public function testParseBadInvalidRpcFormat(): void + { + $request = Request::create(uri: '/', method: 'POST', content: json_encode([ + 'jsonrpc' => '2.0', + 'wrongFormat' => 'mockService->add', + ])); + $payload = $this->parser->parse($request); + + $this->assertInstanceOf(RpcResponseError::class, $payload->getRpcResponses()[0]); + } +} diff --git a/Tests/Response/RpcResponderTest.php b/tests/Response/RpcResponderTest.php similarity index 53% rename from Tests/Response/RpcResponderTest.php rename to tests/Response/RpcResponderTest.php index 9e7dd7f..d4ae5bd 100644 --- a/Tests/Response/RpcResponderTest.php +++ b/tests/Response/RpcResponderTest.php @@ -2,28 +2,23 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests\Response; +namespace Nanofelis\JsonRpcBundle\Tests\Response; -use Nanofelis\Bundle\JsonRpcBundle\Exception\RpcApplicationException; -use Nanofelis\Bundle\JsonRpcBundle\Request\RpcPayload; -use Nanofelis\Bundle\JsonRpcBundle\Request\RpcRequest; -use Nanofelis\Bundle\JsonRpcBundle\Response\RpcResponder; -use Nanofelis\Bundle\JsonRpcBundle\Response\RpcResponse; -use Nanofelis\Bundle\JsonRpcBundle\Response\RpcResponseError; -use PHPUnit\Framework\MockObject\MockObject; +use Nanofelis\JsonRpcBundle\Exception\RpcApplicationException; +use Nanofelis\JsonRpcBundle\Request\RpcPayload; +use Nanofelis\JsonRpcBundle\Request\RpcRequest; +use Nanofelis\JsonRpcBundle\Responder\RpcResponder; +use Nanofelis\JsonRpcBundle\Response\RpcResponse; +use Nanofelis\JsonRpcBundle\Response\RpcResponseError; use PHPUnit\Framework\TestCase; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; class RpcResponderTest extends TestCase { - private MockObject $eventDispatcher; - private RpcResponder $responder; protected function setUp(): void { - $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); - $this->responder = new RpcResponder($this->eventDispatcher); + $this->responder = new RpcResponder(); } /** @@ -38,18 +33,12 @@ public function testResponderBatch(RpcPayload $payload, array $expected) public function provideRpcPayload(): \Generator { - $successRequest = new RpcRequest(); - $successRequest->setResponse(new RpcResponse('success', 1)); - - $errorRequest = new RpcRequest(); - $errorException = new RpcApplicationException('error', 99); - $errorException->setData(['details']); - $errorRequest->setResponse(new RpcResponseError($errorException, 2)); - $payload = new RpcPayload(); $payload->setIsBatch(true); - $payload->addRpcRequest($successRequest); - $payload->addRpcRequest($errorRequest); + $payload->addRpcResponse(new RpcResponse('success', 1)); + $errorException = new RpcApplicationException('error', 99); + $errorException->setData(['message' => 'details']); + $payload->addRpcResponse(new RpcResponseError($errorException, 2)); yield [$payload, [ @@ -63,7 +52,7 @@ public function provideRpcPayload(): \Generator 'error' => [ 'code' => 99, 'message' => 'error', - 'data' => ['details'], + 'data' => ['message' => 'details'], ], 'id' => 2, ], @@ -71,7 +60,7 @@ public function provideRpcPayload(): \Generator ]; $payload = new RpcPayload(); - $payload->addRpcRequest($successRequest); + $payload->addRpcResponse(new RpcResponse('success', 1)); yield [$payload, [ 'jsonrpc' => RpcRequest::JSON_RPC_VERSION, diff --git a/tests/Service/MockDTO.php b/tests/Service/MockDTO.php new file mode 100644 index 0000000..c9546ff --- /dev/null +++ b/tests/Service/MockDTO.php @@ -0,0 +1,12 @@ + $a, 'b' => $b]; } + + /** + * @return array{a:int, b: string, c: bool} + */ + public function withMapRequest(#[MapRequestPayload] MockDTO $fakeDTO): array + { + return ['a' => $fakeDTO->a, 'b' => $fakeDTO->b, 'c' => $fakeDTO->c]; + } } diff --git a/tests/Service/ServiceFinderTest.php b/tests/Service/ServiceFinderTest.php new file mode 100644 index 0000000..802faf8 --- /dev/null +++ b/tests/Service/ServiceFinderTest.php @@ -0,0 +1,55 @@ + + */ + private \ArrayIterator $services; + + protected function setUp(): void + { + $this->services = new \ArrayIterator([ + 'mockService' => new MockService(), + ]); + } + + /** + * @throws RpcMethodNotFoundException + */ + public function testFind(): void + { + $serviceLocator = new ServiceFinder($this->services); + $serviceDesc = $serviceLocator->find(new RpcRequest(serviceKey: 'mockService', methodKey: 'add')); + + $this->assertInstanceOf(MockService::class, $serviceDesc->getService()); + $this->assertSame('add', $serviceDesc->getMethodName()); + } + + public function testFindUnknownService(): void + { + $serviceLocator = new ServiceFinder($this->services); + + $this->expectException(RpcMethodNotFoundException::class); + + $serviceLocator->find(new RpcRequest(serviceKey: 'unknown', methodKey: 'add')); + } + + public function testFindUnknownMethod(): void + { + $serviceLocator = new ServiceFinder($this->services); + + $this->expectException(RpcMethodNotFoundException::class); + + $serviceLocator->find(new RpcRequest(serviceKey: 'mockService', methodKey: 'unknown')); + } +} diff --git a/Tests/TestKernel.php b/tests/TestKernel.php similarity index 91% rename from Tests/TestKernel.php rename to tests/TestKernel.php index f004e08..96ea9b7 100644 --- a/Tests/TestKernel.php +++ b/tests/TestKernel.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nanofelis\Bundle\JsonRpcBundle\Tests; +namespace Nanofelis\JsonRpcBundle\Tests; -use Nanofelis\Bundle\JsonRpcBundle\NanofelisJsonRpcBundle; -use Nanofelis\Bundle\JsonRpcBundle\Tests\Service\MockService; +use Nanofelis\JsonRpcBundle\NanofelisJsonRpcBundle; +use Nanofelis\JsonRpcBundle\Tests\Service\MockService; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Bundle\TwigBundle\TwigBundle; @@ -38,7 +38,7 @@ public function registerBundles(): iterable protected function configureRoutes(RoutingConfigurator $routes): void { - $routes->import(__DIR__.'/../Resources/config/routing/routing.xml'); + $routes->import(__DIR__.'/../config/routes.xml'); } protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void