From 171f57f93b254dbf80473eb5841287bcd038052f Mon Sep 17 00:00:00 2001 From: Dominik Zogg Date: Sun, 4 Feb 2024 20:09:55 +0100 Subject: [PATCH 1/5] middleware --- README.md | 4 +- composer.json | 6 +- src/Middleware/AcceptLanguageMiddleware.php | 55 +++++++++++ src/Middleware/AcceptMiddleware.php | 55 +++++++++++ src/Middleware/ContentTypeMiddleware.php | 55 +++++++++++ .../AcceptLanguageMiddlewareFactory.php | 21 ++++ .../AcceptMiddlewareFactory.php | 21 ++++ .../ContentTypeMiddlewareFactory.php | 21 ++++ .../NegotiationServiceFactory.php | 7 +- .../NegotiationServiceProvider.php | 9 ++ .../AcceptLanguageMiddlewareTest.php | 95 +++++++++++++++++++ .../Unit/Middleware/AcceptMiddlewareTest.php | 94 ++++++++++++++++++ .../Middleware/ContentTypeMiddlewareTest.php | 95 +++++++++++++++++++ .../AcceptLanguageMiddlewareFactoryTest.php | 57 +++++++++++ .../AcceptMiddlewareFactoryTest.php | 57 +++++++++++ .../ContentTypeMiddlewareFactoryTest.php | 57 +++++++++++ .../NegotiationServiceFactoryTest.php | 9 ++ .../NegotiationServiceProviderTest.php | 9 ++ 18 files changed, 723 insertions(+), 4 deletions(-) create mode 100644 src/Middleware/AcceptLanguageMiddleware.php create mode 100644 src/Middleware/AcceptMiddleware.php create mode 100644 src/Middleware/ContentTypeMiddleware.php create mode 100644 src/ServiceFactory/AcceptLanguageMiddlewareFactory.php create mode 100644 src/ServiceFactory/AcceptMiddlewareFactory.php create mode 100644 src/ServiceFactory/ContentTypeMiddlewareFactory.php create mode 100644 tests/Unit/Middleware/AcceptLanguageMiddlewareTest.php create mode 100644 tests/Unit/Middleware/AcceptMiddlewareTest.php create mode 100644 tests/Unit/Middleware/ContentTypeMiddlewareTest.php create mode 100644 tests/Unit/ServiceFactory/AcceptLanguageMiddlewareFactoryTest.php create mode 100644 tests/Unit/ServiceFactory/AcceptMiddlewareFactoryTest.php create mode 100644 tests/Unit/ServiceFactory/ContentTypeMiddlewareFactoryTest.php diff --git a/README.md b/README.md index 2fbb369..87379f6 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,9 @@ A simple negotiation library. ## Requirements * php: ^8.1 + * chubbyphp/chubbyphp-http-exception: ^1.1 * psr/http-message: ^1.1|^2.0 + * psr/http-server-middleware: ^1.0 ## Suggest @@ -39,7 +41,7 @@ A simple negotiation library. Through [Composer](http://getcomposer.org) as [chubbyphp/chubbyphp-negotiation][1]. ```sh -composer require chubbyphp/chubbyphp-negotiation "^2.0" +composer require chubbyphp/chubbyphp-negotiation "^2.1" ``` ## Usage diff --git a/composer.json b/composer.json index 23ec12c..a805909 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,9 @@ ], "require": { "php": "^8.1", - "psr/http-message": "^1.1|^2.0" + "chubbyphp/chubbyphp-http-exception": "^1.1", + "psr/http-message": "^1.1|^2.0", + "psr/http-server-middleware": "^1.0" }, "require-dev": { "chubbyphp/chubbyphp-container": "^2.2", @@ -47,7 +49,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "scripts": { diff --git a/src/Middleware/AcceptLanguageMiddleware.php b/src/Middleware/AcceptLanguageMiddleware.php new file mode 100644 index 0000000..1867178 --- /dev/null +++ b/src/Middleware/AcceptLanguageMiddleware.php @@ -0,0 +1,55 @@ +acceptLanguageNegotiator->negotiate($request)) { + throw HttpException::createNotAcceptable( + $this->aggregateData( + 'acceptLanguage', + $request->getHeaderLine('Accept-Language'), + $this->acceptLanguageNegotiator->getSupportedLocales() + ) + ); + } + + $request = $request->withAttribute('acceptLanguage', $acceptLanguage->getValue()); + + return $handler->handle($request); + } + + /** + * @param array $supportedValues + * + * @return array|string> + */ + private function aggregateData(string $header, string $value, array $supportedValues): array + { + return [ + 'detail' => sprintf( + '%s %s, supportedValues: "%s"', + '' !== $value ? 'Not supported' : 'Missing', + $header, + implode('", ', $supportedValues) + ), + 'value' => $value, + 'supportedValues' => $supportedValues, + ]; + } +} diff --git a/src/Middleware/AcceptMiddleware.php b/src/Middleware/AcceptMiddleware.php new file mode 100644 index 0000000..31003e5 --- /dev/null +++ b/src/Middleware/AcceptMiddleware.php @@ -0,0 +1,55 @@ +acceptNegotiator->negotiate($request)) { + throw HttpException::createNotAcceptable( + $this->aggregateData( + 'accept', + $request->getHeaderLine('Accept'), + $this->acceptNegotiator->getSupportedMediaTypes() + ) + ); + } + + $request = $request->withAttribute('accept', $accept->getValue()); + + return $handler->handle($request); + } + + /** + * @param array $supportedValues + * + * @return array|string> + */ + private function aggregateData(string $header, string $value, array $supportedValues): array + { + return [ + 'detail' => sprintf( + '%s %s, supportedValues: "%s"', + '' !== $value ? 'Not supported' : 'Missing', + $header, + implode('", ', $supportedValues) + ), + 'value' => $value, + 'supportedValues' => $supportedValues, + ]; + } +} diff --git a/src/Middleware/ContentTypeMiddleware.php b/src/Middleware/ContentTypeMiddleware.php new file mode 100644 index 0000000..a0211f0 --- /dev/null +++ b/src/Middleware/ContentTypeMiddleware.php @@ -0,0 +1,55 @@ +contentTypeNegotiator->negotiate($request)) { + throw HttpException::createUnsupportedMediaType( + $this->aggregateData( + 'content-type', + $request->getHeaderLine('Content-Type'), + $this->contentTypeNegotiator->getSupportedMediaTypes() + ) + ); + } + + $request = $request->withAttribute('contentType', $contentType->getValue()); + + return $handler->handle($request); + } + + /** + * @param array $supportedValues + * + * @return array|string> + */ + private function aggregateData(string $header, string $value, array $supportedValues): array + { + return [ + 'detail' => sprintf( + '%s %s, supportedValues: "%s"', + '' !== $value ? 'Not supported' : 'Missing', + $header, + implode('", ', $supportedValues) + ), + 'value' => $value, + 'supportedValues' => $supportedValues, + ]; + } +} diff --git a/src/ServiceFactory/AcceptLanguageMiddlewareFactory.php b/src/ServiceFactory/AcceptLanguageMiddlewareFactory.php new file mode 100644 index 0000000..a390342 --- /dev/null +++ b/src/ServiceFactory/AcceptLanguageMiddlewareFactory.php @@ -0,0 +1,21 @@ +get(AcceptLanguageNegotiatorInterface::class.$this->name) + ); + } +} diff --git a/src/ServiceFactory/AcceptMiddlewareFactory.php b/src/ServiceFactory/AcceptMiddlewareFactory.php new file mode 100644 index 0000000..84d041e --- /dev/null +++ b/src/ServiceFactory/AcceptMiddlewareFactory.php @@ -0,0 +1,21 @@ +get(AcceptNegotiatorInterface::class.$this->name) + ); + } +} diff --git a/src/ServiceFactory/ContentTypeMiddlewareFactory.php b/src/ServiceFactory/ContentTypeMiddlewareFactory.php new file mode 100644 index 0000000..713a38e --- /dev/null +++ b/src/ServiceFactory/ContentTypeMiddlewareFactory.php @@ -0,0 +1,21 @@ +get(ContentTypeNegotiatorInterface::class.$this->name) + ); + } +} diff --git a/src/ServiceFactory/NegotiationServiceFactory.php b/src/ServiceFactory/NegotiationServiceFactory.php index 216f8b9..c3046f2 100644 --- a/src/ServiceFactory/NegotiationServiceFactory.php +++ b/src/ServiceFactory/NegotiationServiceFactory.php @@ -7,6 +7,8 @@ use Chubbyphp\Negotiation\AcceptLanguageNegotiator; use Chubbyphp\Negotiation\AcceptNegotiator; use Chubbyphp\Negotiation\ContentTypeNegotiator; +use Chubbyphp\Negotiation\Middleware\AcceptMiddleware; +use Chubbyphp\Negotiation\Middleware\ContentTypeMiddleware; use Psr\Container\ContainerInterface; final class NegotiationServiceFactory @@ -17,8 +19,11 @@ final class NegotiationServiceFactory public function __invoke(): array { return [ - 'negotiator.acceptNegotiator' => static fn (ContainerInterface $container) => new AcceptNegotiator($container->get('negotiator.acceptNegotiator.values')), + 'negotiator.acceptLanguageMiddleware' => static fn (ContainerInterface $container) => new AcceptLanguageNegotiator($container->get('negotiator.acceptLanguageMiddleware')), 'negotiator.acceptLanguageNegotiator' => static fn (ContainerInterface $container) => new AcceptLanguageNegotiator($container->get('negotiator.acceptLanguageNegotiator.values')), + 'negotiator.acceptMiddleware' => static fn (ContainerInterface $container) => new AcceptMiddleware($container->get('negotiator.acceptNegotiator')), + 'negotiator.acceptNegotiator' => static fn (ContainerInterface $container) => new AcceptNegotiator($container->get('negotiator.acceptNegotiator.values')), + 'negotiator.contentTypeMiddleware' => static fn (ContainerInterface $container) => new ContentTypeMiddleware($container->get('negotiator.contentTypeNegotiator')), 'negotiator.contentTypeNegotiator' => static fn (ContainerInterface $container) => new ContentTypeNegotiator($container->get('negotiator.contentTypeNegotiator.values')), 'negotiator.acceptNegotiator.values' => static fn () => [], 'negotiator.acceptLanguageNegotiator.values' => static fn () => [], diff --git a/src/ServiceProvider/NegotiationServiceProvider.php b/src/ServiceProvider/NegotiationServiceProvider.php index 0f64852..68a3228 100644 --- a/src/ServiceProvider/NegotiationServiceProvider.php +++ b/src/ServiceProvider/NegotiationServiceProvider.php @@ -7,6 +7,9 @@ use Chubbyphp\Negotiation\AcceptLanguageNegotiator; use Chubbyphp\Negotiation\AcceptNegotiator; use Chubbyphp\Negotiation\ContentTypeNegotiator; +use Chubbyphp\Negotiation\Middleware\AcceptLanguageMiddleware; +use Chubbyphp\Negotiation\Middleware\AcceptMiddleware; +use Chubbyphp\Negotiation\Middleware\ContentTypeMiddleware; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -16,10 +19,16 @@ public function register(Container $container): void { $container['negotiator.acceptNegotiator'] = static fn () => new AcceptNegotiator($container['negotiator.acceptNegotiator.values']); + $container['negotiator.acceptMiddleware'] = static fn () => new AcceptMiddleware($container['negotiator.acceptNegotiator']); + $container['negotiator.acceptLanguageNegotiator'] = static fn () => new AcceptLanguageNegotiator($container['negotiator.acceptLanguageNegotiator.values']); + $container['negotiator.acceptLanguageMiddleware'] = static fn () => new AcceptLanguageMiddleware($container['negotiator.acceptLanguageNegotiator']); + $container['negotiator.contentTypeNegotiator'] = static fn () => new ContentTypeNegotiator($container['negotiator.contentTypeNegotiator.values']); + $container['negotiator.contentTypeMiddleware'] = static fn () => new ContentTypeMiddleware($container['negotiator.contentTypeNegotiator']); + $container['negotiator.acceptNegotiator.values'] = static fn () => []; $container['negotiator.acceptLanguageNegotiator.values'] = static fn () => []; diff --git a/tests/Unit/Middleware/AcceptLanguageMiddlewareTest.php b/tests/Unit/Middleware/AcceptLanguageMiddlewareTest.php new file mode 100644 index 0000000..c68baab --- /dev/null +++ b/tests/Unit/Middleware/AcceptLanguageMiddlewareTest.php @@ -0,0 +1,95 @@ +getMockByCalls(ServerRequestInterface::class, [ + Call::create('getHeaderLine')->with('Accept-Language')->willReturn('en-US, en;q=0.9'), + ]); + + /** @var MockObject|RequestHandlerInterface $handler */ + $handler = $this->getMockByCalls(RequestHandlerInterface::class, []); + + /** @var AcceptLanguageNegotiatorInterface|MockObject $acceptLanguageNegotiator */ + $acceptLanguageNegotiator = $this->getMockByCalls(AcceptLanguageNegotiatorInterface::class, [ + Call::create('negotiate')->with($request)->willReturn(null), + Call::create('getSupportedLocales')->with()->willReturn(['en-US']), + ]); + + $middleware = new AcceptLanguageMiddleware($acceptLanguageNegotiator); + + try { + $middleware->process($request, $handler); + + throw new \Exception('code should not be reached'); + } catch (HttpException $e) { + self::assertSame('Not Acceptable', $e->getMessage()); + self::assertSame([ + 'type' => 'https://datatracker.ietf.org/doc/html/rfc2616#section-10.4.7', + 'status' => 406, + 'title' => 'Not Acceptable', + 'detail' => 'Not supported acceptLanguage, supportedValues: "en-US"', + 'instance' => null, + 'value' => 'en-US, en;q=0.9', + 'supportedValues' => [ + 0 => 'en-US', + ], + ], $e->jsonSerialize()); + } + } + + public function testProcessWithMatching(): void + { + /** @var MockObject|ServerRequestInterface $request */ + $request = $this->getMockByCalls(ServerRequestInterface::class, [ + Call::create('withAttribute')->with('acceptLanguage', 'en-US')->willReturnSelf(), + ]); + + /** @var MockObject|ResponseInterface $response */ + $response = $this->getMockByCalls(ResponseInterface::class, []); + + /** @var MockObject|RequestHandlerInterface $handler */ + $handler = $this->getMockByCalls(RequestHandlerInterface::class, [ + Call::create('handle')->with($request)->willReturn($response), + ]); + + /** @var MockObject|NegotiatedValueInterface $negotiatedValue */ + $negotiatedValue = $this->getMockByCalls(NegotiatedValueInterface::class, [ + Call::create('getValue')->with()->willReturn('en-US'), + ]); + + /** @var AcceptLanguageNegotiatorInterface|MockObject $acceptLanguageNegotiator */ + $acceptLanguageNegotiator = $this->getMockByCalls(AcceptLanguageNegotiatorInterface::class, [ + Call::create('negotiate')->with($request)->willReturn($negotiatedValue), + ]); + + $middleware = new AcceptLanguageMiddleware($acceptLanguageNegotiator); + + $response = $middleware->process($request, $handler); + } +} diff --git a/tests/Unit/Middleware/AcceptMiddlewareTest.php b/tests/Unit/Middleware/AcceptMiddlewareTest.php new file mode 100644 index 0000000..b899b1d --- /dev/null +++ b/tests/Unit/Middleware/AcceptMiddlewareTest.php @@ -0,0 +1,94 @@ +getMockByCalls(ServerRequestInterface::class, [ + Call::create('getHeaderLine')->with('Accept')->willReturn('application/xml, application/x-yaml;q=0.9'), + ]); + + /** @var MockObject|RequestHandlerInterface $handler */ + $handler = $this->getMockByCalls(RequestHandlerInterface::class, []); + + /** @var AcceptNegotiatorInterface|MockObject $acceptNegotiator */ + $acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, [ + Call::create('negotiate')->with($request)->willReturn(null), + Call::create('getSupportedMediaTypes')->with()->willReturn(['application/json']), + ]); + + $middleware = new AcceptMiddleware($acceptNegotiator); + + try { + $middleware->process($request, $handler); + + throw new \Exception('code should not be reached'); + } catch (HttpException $e) { + self::assertSame('Not Acceptable', $e->getMessage()); + self::assertSame(['type' => 'https://datatracker.ietf.org/doc/html/rfc2616#section-10.4.7', + 'status' => 406, + 'title' => 'Not Acceptable', + 'detail' => 'Not supported accept, supportedValues: "application/json"', + 'instance' => null, + 'value' => 'application/xml, application/x-yaml;q=0.9', + 'supportedValues' => [ + 0 => 'application/json', + ], + ], $e->jsonSerialize()); + } + } + + public function testProcessWithMatching(): void + { + /** @var MockObject|ServerRequestInterface $request */ + $request = $this->getMockByCalls(ServerRequestInterface::class, [ + Call::create('withAttribute')->with('accept', 'application/json')->willReturnSelf(), + ]); + + /** @var MockObject|ResponseInterface $response */ + $response = $this->getMockByCalls(ResponseInterface::class, []); + + /** @var MockObject|RequestHandlerInterface $handler */ + $handler = $this->getMockByCalls(RequestHandlerInterface::class, [ + Call::create('handle')->with($request)->willReturn($response), + ]); + + /** @var MockObject|NegotiatedValueInterface $negotiatedValue */ + $negotiatedValue = $this->getMockByCalls(NegotiatedValueInterface::class, [ + Call::create('getValue')->with()->willReturn('application/json'), + ]); + + /** @var AcceptNegotiatorInterface|MockObject $acceptNegotiator */ + $acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, [ + Call::create('negotiate')->with($request)->willReturn($negotiatedValue), + ]); + + $middleware = new AcceptMiddleware($acceptNegotiator); + + $response = $middleware->process($request, $handler); + } +} diff --git a/tests/Unit/Middleware/ContentTypeMiddlewareTest.php b/tests/Unit/Middleware/ContentTypeMiddlewareTest.php new file mode 100644 index 0000000..786089a --- /dev/null +++ b/tests/Unit/Middleware/ContentTypeMiddlewareTest.php @@ -0,0 +1,95 @@ +getMockByCalls(ServerRequestInterface::class, [ + Call::create('getHeaderLine')->with('Content-Type')->willReturn('application/xml'), + ]); + + /** @var MockObject|RequestHandlerInterface $handler */ + $handler = $this->getMockByCalls(RequestHandlerInterface::class, []); + + /** @var ContentTypeNegotiatorInterface|MockObject $contentTypeNegotiator */ + $contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, [ + Call::create('negotiate')->with($request)->willReturn(null), + Call::create('getSupportedMediaTypes')->with()->willReturn(['application/json']), + ]); + + $middleware = new ContentTypeMiddleware($contentTypeNegotiator); + + try { + $middleware->process($request, $handler); + + throw new \Exception('code should not be reached'); + } catch (HttpException $e) { + self::assertSame('Unsupported Media Type', $e->getMessage()); + self::assertSame([ + 'type' => 'https://datatracker.ietf.org/doc/html/rfc2616#section-10.4.16', + 'status' => 415, + 'title' => 'Unsupported Media Type', + 'detail' => 'Not supported content-type, supportedValues: "application/json"', + 'instance' => null, + 'value' => 'application/xml', + 'supportedValues' => [ + 0 => 'application/json', + ], + ], $e->jsonSerialize()); + } + } + + public function testProcessWithMatching(): void + { + /** @var MockObject|ServerRequestInterface $request */ + $request = $this->getMockByCalls(ServerRequestInterface::class, [ + Call::create('withAttribute')->with('contentType', 'application/json')->willReturnSelf(), + ]); + + /** @var MockObject|ResponseInterface $response */ + $response = $this->getMockByCalls(ResponseInterface::class, []); + + /** @var MockObject|RequestHandlerInterface $handler */ + $handler = $this->getMockByCalls(RequestHandlerInterface::class, [ + Call::create('handle')->with($request)->willReturn($response), + ]); + + /** @var MockObject|NegotiatedValueInterface $negotiatedValue */ + $negotiatedValue = $this->getMockByCalls(NegotiatedValueInterface::class, [ + Call::create('getValue')->with()->willReturn('application/json'), + ]); + + /** @var ContentTypeNegotiatorInterface|MockObject $contentTypeNegotiator */ + $contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, [ + Call::create('negotiate')->with($request)->willReturn($negotiatedValue), + ]); + + $middleware = new ContentTypeMiddleware($contentTypeNegotiator); + + $response = $middleware->process($request, $handler); + } +} diff --git a/tests/Unit/ServiceFactory/AcceptLanguageMiddlewareFactoryTest.php b/tests/Unit/ServiceFactory/AcceptLanguageMiddlewareFactoryTest.php new file mode 100644 index 0000000..26f2850 --- /dev/null +++ b/tests/Unit/ServiceFactory/AcceptLanguageMiddlewareFactoryTest.php @@ -0,0 +1,57 @@ +getMockByCalls(AcceptLanguageNegotiatorInterface::class, []); + + /** @var ContainerInterface $container */ + $container = $this->getMockByCalls(ContainerInterface::class, [ + Call::create('get')->with(AcceptLanguageNegotiatorInterface::class)->willReturn($acceptLanguageNegotiator), + ]); + + $factory = new AcceptLanguageMiddlewareFactory(); + + $service = $factory($container); + + self::assertInstanceOf(MiddlewareInterface::class, $service); + } + + public function testCallStatic(): void + { + /** @var AcceptLanguageNegotiatorInterface $acceptLanguageNegotiator */ + $acceptLanguageNegotiator = $this->getMockByCalls(AcceptLanguageNegotiatorInterface::class, []); + + /** @var ContainerInterface $container */ + $container = $this->getMockByCalls(ContainerInterface::class, [ + Call::create('get')->with(AcceptLanguageNegotiatorInterface::class.'default')->willReturn($acceptLanguageNegotiator), + ]); + + $factory = [AcceptLanguageMiddlewareFactory::class, 'default']; + + $service = $factory($container); + + self::assertInstanceOf(MiddlewareInterface::class, $service); + } +} diff --git a/tests/Unit/ServiceFactory/AcceptMiddlewareFactoryTest.php b/tests/Unit/ServiceFactory/AcceptMiddlewareFactoryTest.php new file mode 100644 index 0000000..08dcc37 --- /dev/null +++ b/tests/Unit/ServiceFactory/AcceptMiddlewareFactoryTest.php @@ -0,0 +1,57 @@ +getMockByCalls(AcceptNegotiatorInterface::class, []); + + /** @var ContainerInterface $container */ + $container = $this->getMockByCalls(ContainerInterface::class, [ + Call::create('get')->with(AcceptNegotiatorInterface::class)->willReturn($acceptNegotiator), + ]); + + $factory = new AcceptMiddlewareFactory(); + + $service = $factory($container); + + self::assertInstanceOf(MiddlewareInterface::class, $service); + } + + public function testCallStatic(): void + { + /** @var AcceptNegotiatorInterface $acceptNegotiator */ + $acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, []); + + /** @var ContainerInterface $container */ + $container = $this->getMockByCalls(ContainerInterface::class, [ + Call::create('get')->with(AcceptNegotiatorInterface::class.'default')->willReturn($acceptNegotiator), + ]); + + $factory = [AcceptMiddlewareFactory::class, 'default']; + + $service = $factory($container); + + self::assertInstanceOf(MiddlewareInterface::class, $service); + } +} diff --git a/tests/Unit/ServiceFactory/ContentTypeMiddlewareFactoryTest.php b/tests/Unit/ServiceFactory/ContentTypeMiddlewareFactoryTest.php new file mode 100644 index 0000000..ecfd2fc --- /dev/null +++ b/tests/Unit/ServiceFactory/ContentTypeMiddlewareFactoryTest.php @@ -0,0 +1,57 @@ +getMockByCalls(ContentTypeNegotiatorInterface::class, []); + + /** @var ContainerInterface $container */ + $container = $this->getMockByCalls(ContainerInterface::class, [ + Call::create('get')->with(ContentTypeNegotiatorInterface::class)->willReturn($contentTypeNegotiator), + ]); + + $factory = new ContentTypeMiddlewareFactory(); + + $service = $factory($container); + + self::assertInstanceOf(MiddlewareInterface::class, $service); + } + + public function testCallStatic(): void + { + /** @var ContentTypeNegotiatorInterface $contentTypeNegotiator */ + $contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, []); + + /** @var ContainerInterface $container */ + $container = $this->getMockByCalls(ContainerInterface::class, [ + Call::create('get')->with(ContentTypeNegotiatorInterface::class.'default')->willReturn($contentTypeNegotiator), + ]); + + $factory = [ContentTypeMiddlewareFactory::class, 'default']; + + $service = $factory($container); + + self::assertInstanceOf(MiddlewareInterface::class, $service); + } +} diff --git a/tests/Unit/ServiceFactory/NegotiationServiceFactoryTest.php b/tests/Unit/ServiceFactory/NegotiationServiceFactoryTest.php index 7d4abf0..8429610 100644 --- a/tests/Unit/ServiceFactory/NegotiationServiceFactoryTest.php +++ b/tests/Unit/ServiceFactory/NegotiationServiceFactoryTest.php @@ -8,6 +8,9 @@ use Chubbyphp\Negotiation\AcceptLanguageNegotiator; use Chubbyphp\Negotiation\AcceptNegotiator; use Chubbyphp\Negotiation\ContentTypeNegotiator; +use Chubbyphp\Negotiation\Middleware\AcceptLanguageMiddleware; +use Chubbyphp\Negotiation\Middleware\AcceptMiddleware; +use Chubbyphp\Negotiation\Middleware\ContentTypeMiddleware; use Chubbyphp\Negotiation\ServiceFactory\NegotiationServiceFactory; use PHPUnit\Framework\TestCase; @@ -24,15 +27,21 @@ public function testRegister(): void $container->factories((new NegotiationServiceFactory())()); self::assertTrue($container->has('negotiator.acceptNegotiator')); + self::assertTrue($container->has('negotiator.acceptMiddleware')); self::assertTrue($container->has('negotiator.acceptLanguageNegotiator')); + self::assertTrue($container->has('negotiator.acceptLanguageMiddleware')); self::assertTrue($container->has('negotiator.contentTypeNegotiator')); + self::assertTrue($container->has('negotiator.contentTypeMiddleware')); self::assertTrue($container->has('negotiator.acceptNegotiator.values')); self::assertTrue($container->has('negotiator.acceptLanguageNegotiator.values')); self::assertTrue($container->has('negotiator.contentTypeNegotiator.values')); self::assertInstanceOf(AcceptNegotiator::class, $container->get('negotiator.acceptNegotiator')); + self::assertInstanceOf(AcceptMiddleware::class, $container->get('negotiator.acceptMiddleware')); self::assertInstanceOf(AcceptLanguageNegotiator::class, $container->get('negotiator.acceptLanguageNegotiator')); + self::assertInstanceOf(AcceptLanguageMiddleware::class, $container->get('negotiator.acceptLanguageMiddleware')); self::assertInstanceOf(ContentTypeNegotiator::class, $container->get('negotiator.contentTypeNegotiator')); + self::assertInstanceOf(ContentTypeMiddleware::class, $container->get('negotiator.contentTypeMiddleware')); self::assertEquals([], $container->get('negotiator.acceptNegotiator.values')); self::assertEquals([], $container->get('negotiator.acceptLanguageNegotiator.values')); self::assertEquals([], $container->get('negotiator.contentTypeNegotiator.values')); diff --git a/tests/Unit/ServiceProvider/NegotiationServiceProviderTest.php b/tests/Unit/ServiceProvider/NegotiationServiceProviderTest.php index 8ec17df..ec016ea 100644 --- a/tests/Unit/ServiceProvider/NegotiationServiceProviderTest.php +++ b/tests/Unit/ServiceProvider/NegotiationServiceProviderTest.php @@ -7,6 +7,9 @@ use Chubbyphp\Negotiation\AcceptLanguageNegotiator; use Chubbyphp\Negotiation\AcceptNegotiator; use Chubbyphp\Negotiation\ContentTypeNegotiator; +use Chubbyphp\Negotiation\Middleware\AcceptLanguageMiddleware; +use Chubbyphp\Negotiation\Middleware\AcceptMiddleware; +use Chubbyphp\Negotiation\Middleware\ContentTypeMiddleware; use Chubbyphp\Negotiation\ServiceProvider\NegotiationServiceProvider; use PHPUnit\Framework\TestCase; use Pimple\Container; @@ -24,15 +27,21 @@ public function testRegister(): void $container->register(new NegotiationServiceProvider()); self::assertTrue(isset($container['negotiator.acceptNegotiator'])); + self::assertTrue(isset($container['negotiator.acceptMiddleware'])); self::assertTrue(isset($container['negotiator.acceptLanguageNegotiator'])); + self::assertTrue(isset($container['negotiator.acceptLanguageMiddleware'])); self::assertTrue(isset($container['negotiator.contentTypeNegotiator'])); + self::assertTrue(isset($container['negotiator.contentTypeMiddleware'])); self::assertTrue(isset($container['negotiator.acceptNegotiator.values'])); self::assertTrue(isset($container['negotiator.acceptLanguageNegotiator.values'])); self::assertTrue(isset($container['negotiator.contentTypeNegotiator.values'])); self::assertInstanceOf(AcceptNegotiator::class, $container['negotiator.acceptNegotiator']); + self::assertInstanceOf(AcceptMiddleware::class, $container['negotiator.acceptMiddleware']); self::assertInstanceOf(AcceptLanguageNegotiator::class, $container['negotiator.acceptLanguageNegotiator']); + self::assertInstanceOf(AcceptLanguageMiddleware::class, $container['negotiator.acceptLanguageMiddleware']); self::assertInstanceOf(ContentTypeNegotiator::class, $container['negotiator.contentTypeNegotiator']); + self::assertInstanceOf(ContentTypeMiddleware::class, $container['negotiator.contentTypeMiddleware']); self::assertEquals([], $container['negotiator.acceptNegotiator.values']); self::assertEquals([], $container['negotiator.acceptLanguageNegotiator.values']); self::assertEquals([], $container['negotiator.contentTypeNegotiator.values']); From 71c871f46d45c1386ad36050579fbaa87b583ee3 Mon Sep 17 00:00:00 2001 From: Dominik Zogg Date: Sun, 4 Feb 2024 20:12:56 +0100 Subject: [PATCH 2/5] fix issue loading itself --- src/ServiceFactory/NegotiationServiceFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceFactory/NegotiationServiceFactory.php b/src/ServiceFactory/NegotiationServiceFactory.php index c3046f2..347697a 100644 --- a/src/ServiceFactory/NegotiationServiceFactory.php +++ b/src/ServiceFactory/NegotiationServiceFactory.php @@ -19,7 +19,7 @@ final class NegotiationServiceFactory public function __invoke(): array { return [ - 'negotiator.acceptLanguageMiddleware' => static fn (ContainerInterface $container) => new AcceptLanguageNegotiator($container->get('negotiator.acceptLanguageMiddleware')), + 'negotiator.acceptLanguageMiddleware' => static fn (ContainerInterface $container) => new AcceptLanguageNegotiator($container->get('negotiator.acceptLanguageNegotiator')), 'negotiator.acceptLanguageNegotiator' => static fn (ContainerInterface $container) => new AcceptLanguageNegotiator($container->get('negotiator.acceptLanguageNegotiator.values')), 'negotiator.acceptMiddleware' => static fn (ContainerInterface $container) => new AcceptMiddleware($container->get('negotiator.acceptNegotiator')), 'negotiator.acceptNegotiator' => static fn (ContainerInterface $container) => new AcceptNegotiator($container->get('negotiator.acceptNegotiator.values')), From 302c820fc6dc4d9765c5ad330414da971307eaa5 Mon Sep 17 00:00:00 2001 From: Dominik Zogg Date: Sun, 4 Feb 2024 20:14:31 +0100 Subject: [PATCH 3/5] fix readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87379f6..19aebcb 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ $request = ...; $container->get('negotiator.acceptNegotiator') ->negotiate($request); -$container->get('negotiator.acceptNegotiator') +$container->get('negotiator.acceptLanguageNegotiator') ->negotiate($request); $container->get('negotiator.contentTypeNegotiator') @@ -135,7 +135,7 @@ $request = ...; $container['negotiator.acceptNegotiator'] ->negotiate($request); -$container['negotiator.acceptNegotiator'] +$container['negotiator.acceptLanguageNegotiator'] ->negotiate($request); $container['negotiator.contentTypeNegotiator'] From f009ff7091c911c7b78d8184788bdfd4d4dbeada Mon Sep 17 00:00:00 2001 From: Dominik Zogg Date: Sun, 4 Feb 2024 20:24:26 +0100 Subject: [PATCH 4/5] update readme --- README.md | 78 +++++++++++++++++-- .../AcceptLanguageMiddlewareFactory.md | 39 ++++++++++ .../AcceptLanguageNegotiatorFactory.md | 2 - doc/ServiceFactory/AcceptMiddlewareFactory.md | 39 ++++++++++ doc/ServiceFactory/AcceptNegotiatorFactory.md | 2 - .../ContentTypeMiddlewareFactory.md | 39 ++++++++++ .../ContentTypeNegotiatorFactory.md | 2 - 7 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 doc/ServiceFactory/AcceptLanguageMiddlewareFactory.md create mode 100644 doc/ServiceFactory/AcceptMiddlewareFactory.md create mode 100644 doc/ServiceFactory/ContentTypeMiddlewareFactory.md diff --git a/README.md b/README.md index 19aebcb..76e6d79 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,20 @@ $value->getValue(); // de $value->getAttributes(); // ['q' => '1.0'] ``` +### AcceptLanguageMiddleware + +```php +withHeader('Accept-Language', 'de,en-US;q=0.7,en;q=0.3'); + +$middleware = new AcceptLanguageMiddleware($acceptLanguageNegotiator); +$response = $negotiator->process($request, $handler); +``` + ### AcceptNegotiator ```php @@ -78,6 +92,20 @@ $value->getValue(); // application/xml $value->getAttributes(); // ['q' => '0.9'] ``` +### AcceptMiddleware + +```php +withHeader('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q =0.8'); + +$middleware = new AcceptMiddleware($acceptNegotiator); +$response = $negotiator->process($request, $handler); +``` + ### ContentTypeNegotiator ```php @@ -94,6 +122,20 @@ $value->getValue(); // application/xml $value->getAttributes(); // ['charset' => 'UTF-8'] ``` +### ContentTypeMiddleware + +```php +withHeader('Content-Type', 'application/xml; charset=UTF-8'); + +$middleware = new ContentTypeMiddleware($contentTypeNegotiator); +$response = $negotiator->process($request, $handler); +``` + ### NegotiationServiceFactory ```php @@ -111,11 +153,20 @@ $request = ...; $container->get('negotiator.acceptNegotiator') ->negotiate($request); +$container->get('negotiator.acceptMiddleware') + ->process($request, $handler); + $container->get('negotiator.acceptLanguageNegotiator') ->negotiate($request); +$container->get('negotiator.acceptLanguageMiddleware') + ->process($request, $handler); + $container->get('negotiator.contentTypeNegotiator') ->negotiate($request); + +$container->get('negotiator.contentTypeMiddleware') + ->process($request, $handler); ``` ### NegotiationServiceProvider @@ -135,18 +186,30 @@ $request = ...; $container['negotiator.acceptNegotiator'] ->negotiate($request); +$container['negotiator.acceptMiddleware'] + ->process($request, $handler); + $container['negotiator.acceptLanguageNegotiator'] ->negotiate($request); +$container['negotiator.acceptLanguageMiddleware'] + ->process($request, $handler); + $container['negotiator.contentTypeNegotiator'] ->negotiate($request); + +$container['negotiator.contentTypeMiddleware'] + ->process($request, $handler); ``` ### ServiceFactory - * [AcceptLanguageNegotiatorFactory][2] - * [AcceptNegotiatorFactory][3] - * [ContentTypeNegotiatorFactory][4] + * [AcceptLanguageMiddlewareFactory][2] + * [AcceptLanguageNegotiatorFactory][3] + * [AcceptMiddlewareFactory][4] + * [AcceptNegotiatorFactory][5] + * [ContentTypeMiddlewareFactory][6] + * [ContentTypeNegotiatorFactory][7] ## Copyright @@ -154,6 +217,9 @@ $container['negotiator.contentTypeNegotiator'] [1]: https://packagist.org/packages/chubbyphp/chubbyphp-negotiation -[2]: doc/ServiceFactory/AcceptLanguageNegotiatorFactory.md -[3]: doc/ServiceFactory/AcceptNegotiatorFactory.md -[4]: doc/ServiceFactory/ContentTypeNegotiatorFactory.md +[2]: doc/ServiceFactory/AcceptLanguageMiddlewareFactory.md +[3]: doc/ServiceFactory/AcceptLanguageNegotiatorFactory.md +[4]: doc/ServiceFactory/AcceptMiddlewareFactory.md +[5]: doc/ServiceFactory/AcceptNegotiatorFactory.md +[6]: doc/ServiceFactory/ContentTypeMiddlewareFactory.md +[7]: doc/ServiceFactory/ContentTypeNegotiatorFactory.md diff --git a/doc/ServiceFactory/AcceptLanguageMiddlewareFactory.md b/doc/ServiceFactory/AcceptLanguageMiddlewareFactory.md new file mode 100644 index 0000000..07c3b72 --- /dev/null +++ b/doc/ServiceFactory/AcceptLanguageMiddlewareFactory.md @@ -0,0 +1,39 @@ +# AcceptLanguageMiddlewareFactory + +## without name (default) + +```php +get(AcceptLanguageNegotiator::class) + +$factory = new AcceptLanguageMiddlewareFactory(); + +$acceptLanguageMiddleware = $factory($container); +``` + +## with name `default` + +```php +get(AcceptLanguageNegotiator::class.'default') + +$factory = [AcceptLanguageMiddlewareFactory::class, 'default']; + +$acceptLanguageMiddleware = $factory($container); +``` diff --git a/doc/ServiceFactory/AcceptLanguageNegotiatorFactory.md b/doc/ServiceFactory/AcceptLanguageNegotiatorFactory.md index 5b1d094..c8d1c28 100644 --- a/doc/ServiceFactory/AcceptLanguageNegotiatorFactory.md +++ b/doc/ServiceFactory/AcceptLanguageNegotiatorFactory.md @@ -13,7 +13,6 @@ use Psr\Container\ContainerInterface; $container = ...; // $container->get(AcceptLanguageNegotiatorInterface::class.'supportedLocales[]') -// ['de-CH', 'en-US'] $factory = new AcceptLanguageNegotiatorFactory(); @@ -33,7 +32,6 @@ use Psr\Container\ContainerInterface; $container = ...; // $container->get(AcceptLanguageNegotiatorInterface::class.'supportedLocales[]default') -// ['de-CH', 'en-US'] $factory = [AcceptLanguageNegotiatorFactory::class, 'default']; diff --git a/doc/ServiceFactory/AcceptMiddlewareFactory.md b/doc/ServiceFactory/AcceptMiddlewareFactory.md new file mode 100644 index 0000000..1502d80 --- /dev/null +++ b/doc/ServiceFactory/AcceptMiddlewareFactory.md @@ -0,0 +1,39 @@ +# AcceptMiddlewareFactory + +## without name (default) + +```php +get(AcceptNegotiator::class) + +$factory = new AcceptMiddlewareFactory(); + +$acceptMiddleware = $factory($container); +``` + +## with name `default` + +```php +get(AcceptNegotiator::class.'default') + +$factory = [AcceptMiddlewareFactory::class, 'default']; + +$acceptMiddleware = $factory($container); +``` diff --git a/doc/ServiceFactory/AcceptNegotiatorFactory.md b/doc/ServiceFactory/AcceptNegotiatorFactory.md index a127405..a569802 100644 --- a/doc/ServiceFactory/AcceptNegotiatorFactory.md +++ b/doc/ServiceFactory/AcceptNegotiatorFactory.md @@ -13,7 +13,6 @@ use Psr\Container\ContainerInterface; $container = ...; // $container->get(AcceptNegotiatorInterface::class.'supportedMediaTypes[]') -// ['application/json', 'application/xml'] $factory = new AcceptNegotiatorFactory(); @@ -33,7 +32,6 @@ use Psr\Container\ContainerInterface; $container = ...; // $container->get(AcceptNegotiatorInterface::class.'supportedMediaTypes[]default') -// ['application/json', 'application/xml'] $factory = [AcceptNegotiatorFactory::class, 'default']; diff --git a/doc/ServiceFactory/ContentTypeMiddlewareFactory.md b/doc/ServiceFactory/ContentTypeMiddlewareFactory.md new file mode 100644 index 0000000..80a4ad8 --- /dev/null +++ b/doc/ServiceFactory/ContentTypeMiddlewareFactory.md @@ -0,0 +1,39 @@ +# ContentTypeMiddlewareFactory + +## without name (default) + +```php +get(ContentTypeNegotiator::class) + +$factory = new ContentTypeMiddlewareFactory(); + +$contentTypeMiddleware = $factory($container); +``` + +## with name `default` + +```php +get(ContentTypeNegotiator::class.'default') + +$factory = [ContentTypeMiddlewareFactory::class, 'default']; + +$contentTypeMiddleware = $factory($container); +``` diff --git a/doc/ServiceFactory/ContentTypeNegotiatorFactory.md b/doc/ServiceFactory/ContentTypeNegotiatorFactory.md index d930dff..5fa3d37 100644 --- a/doc/ServiceFactory/ContentTypeNegotiatorFactory.md +++ b/doc/ServiceFactory/ContentTypeNegotiatorFactory.md @@ -13,7 +13,6 @@ use Psr\Container\ContainerInterface; $container = ...; // $container->get(ContentTypeNegotiatorInterface::class.'supportedMediaTypes[]') -// ['application/json', 'application/xml'] $factory = new ContentTypeNegotiatorFactory(); @@ -33,7 +32,6 @@ use Psr\Container\ContainerInterface; $container = ...; // $container->get(ContentTypeNegotiatorInterface::class.'supportedMediaTypes[]default') -// ['application/json', 'application/xml'] $factory = [ContentTypeNegotiatorFactory::class, 'default']; From 155f9fe881d2f2010d557c2ae8f5f6e47e88fa66 Mon Sep 17 00:00:00 2001 From: Dominik Zogg Date: Sun, 4 Feb 2024 20:27:25 +0100 Subject: [PATCH 5/5] fix service factory --- src/ServiceFactory/NegotiationServiceFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ServiceFactory/NegotiationServiceFactory.php b/src/ServiceFactory/NegotiationServiceFactory.php index 347697a..a090d94 100644 --- a/src/ServiceFactory/NegotiationServiceFactory.php +++ b/src/ServiceFactory/NegotiationServiceFactory.php @@ -7,6 +7,7 @@ use Chubbyphp\Negotiation\AcceptLanguageNegotiator; use Chubbyphp\Negotiation\AcceptNegotiator; use Chubbyphp\Negotiation\ContentTypeNegotiator; +use Chubbyphp\Negotiation\Middleware\AcceptLanguageMiddleware; use Chubbyphp\Negotiation\Middleware\AcceptMiddleware; use Chubbyphp\Negotiation\Middleware\ContentTypeMiddleware; use Psr\Container\ContainerInterface; @@ -19,7 +20,7 @@ final class NegotiationServiceFactory public function __invoke(): array { return [ - 'negotiator.acceptLanguageMiddleware' => static fn (ContainerInterface $container) => new AcceptLanguageNegotiator($container->get('negotiator.acceptLanguageNegotiator')), + 'negotiator.acceptLanguageMiddleware' => static fn (ContainerInterface $container) => new AcceptLanguageMiddleware($container->get('negotiator.acceptLanguageNegotiator')), 'negotiator.acceptLanguageNegotiator' => static fn (ContainerInterface $container) => new AcceptLanguageNegotiator($container->get('negotiator.acceptLanguageNegotiator.values')), 'negotiator.acceptMiddleware' => static fn (ContainerInterface $container) => new AcceptMiddleware($container->get('negotiator.acceptNegotiator')), 'negotiator.acceptNegotiator' => static fn (ContainerInterface $container) => new AcceptNegotiator($container->get('negotiator.acceptNegotiator.values')),