diff --git a/psalm.xml.dist b/psalm.xml.dist
index c32cf45c..f928c781 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -25,6 +25,8 @@
+
+
diff --git a/resources/definitions/middlewares/base64_decoding_middleware.php b/resources/definitions/middlewares/base64_decoding_middleware.php
new file mode 100644
index 00000000..27c7853c
--- /dev/null
+++ b/resources/definitions/middlewares/base64_decoding_middleware.php
@@ -0,0 +1,19 @@
+ add([
+ create(Base64DecodingMiddleware::class)
+ ->constructor(
+ streamFactory: get(StreamFactoryInterface::class),
+ )
+ ]),
+];
diff --git a/src/Middleware/Base64DecodingMiddleware.php b/src/Middleware/Base64DecodingMiddleware.php
new file mode 100644
index 00000000..8b1b1ffa
--- /dev/null
+++ b/src/Middleware/Base64DecodingMiddleware.php
@@ -0,0 +1,50 @@
+
+ * @copyright Copyright (c) 2018, Anatoly Nekhay
+ * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
+ * @link https://github.com/sunrise-php/http-router
+ */
+
+declare(strict_types=1);
+
+namespace Sunrise\Http\Router\Middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\StreamFactoryInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+/**
+ * @since 3.2.0
+ */
+final class Base64DecodingMiddleware implements MiddlewareInterface
+{
+ public function __construct(
+ private readonly StreamFactoryInterface $streamFactory,
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+ {
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Encoding
+ if ($request->getHeaderLine('Content-Encoding') === 'base64') {
+ /** @var resource $resource */
+ $resource = $request->getBody()->detach();
+ // https://www.php.net/manual/en/filters.convert.php
+ \stream_filter_append($resource, 'convert.base64-decode');
+
+ $body = $this->streamFactory->createStreamFromResource($resource);
+ $request = $request->withBody($body);
+ }
+
+ return $handler->handle($request);
+ }
+}
diff --git a/src/OpenApi/Annotation/SchemaName.php b/src/OpenApi/Annotation/SchemaName.php
new file mode 100644
index 00000000..819d9022
--- /dev/null
+++ b/src/OpenApi/Annotation/SchemaName.php
@@ -0,0 +1,28 @@
+
+ * @copyright Copyright (c) 2018, Anatoly Nekhay
+ * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
+ * @link https://github.com/sunrise-php/http-router
+ */
+
+declare(strict_types=1);
+
+namespace Sunrise\Http\Router\OpenApi\Annotation;
+
+use Attribute;
+
+/**
+ * @since 3.2.0
+ */
+#[Attribute(Attribute::TARGET_CLASS)]
+final class SchemaName
+{
+ public function __construct(
+ public readonly string $value,
+ ) {
+ }
+}
diff --git a/src/OpenApi/PhpTypeSchemaResolver/ArrayAccessPhpTypeSchemaResolver.php b/src/OpenApi/PhpTypeSchemaResolver/ArrayAccessPhpTypeSchemaResolver.php
index 0c4aad8f..46457a7c 100644
--- a/src/OpenApi/PhpTypeSchemaResolver/ArrayAccessPhpTypeSchemaResolver.php
+++ b/src/OpenApi/PhpTypeSchemaResolver/ArrayAccessPhpTypeSchemaResolver.php
@@ -63,7 +63,6 @@ public function resolvePhpTypeSchema(Type $phpType, Reflector $phpTypeHolder): a
$phpTypeName = $phpType->name;
$arrayPhpType = new Type(Type::PHP_TYPE_NAME_ARRAY, $phpType->allowsNull);
- /** @var array{oneOf: array{0: array{type: 'array'}, 1: array{type: 'object'}}} $phpTypeSchema */
$phpTypeSchema = $this->openApiPhpTypeSchemaResolverManager
->resolvePhpTypeSchema($arrayPhpType, $phpTypeHolder);
@@ -76,8 +75,7 @@ public function resolvePhpTypeSchema(Type $phpType, Reflector $phpTypeHolder): a
$collectionElementPhpTypeSchema = $this->openApiPhpTypeSchemaResolverManager
->resolvePhpTypeSchema($collectionElementPhpType, $phpTypeHolder);
- $phpTypeSchema['oneOf'][0]['items'] = $collectionElementPhpTypeSchema;
- $phpTypeSchema['oneOf'][1]['additionalProperties'] = $collectionElementPhpTypeSchema;
+ $phpTypeSchema['items'] = $collectionElementPhpTypeSchema;
}
return $phpTypeSchema;
diff --git a/src/OpenApi/PhpTypeSchemaResolver/ArrayPhpTypeSchemaResolver.php b/src/OpenApi/PhpTypeSchemaResolver/ArrayPhpTypeSchemaResolver.php
index d07412d4..a775ee70 100644
--- a/src/OpenApi/PhpTypeSchemaResolver/ArrayPhpTypeSchemaResolver.php
+++ b/src/OpenApi/PhpTypeSchemaResolver/ArrayPhpTypeSchemaResolver.php
@@ -52,14 +52,7 @@ public function resolvePhpTypeSchema(Type $phpType, Reflector $phpTypeHolder): a
$this->supportsPhpType($phpType, $phpTypeHolder) or throw new UnsupportedPhpTypeException();
$phpTypeSchema = [
- 'oneOf' => [
- [
- 'type' => Type::OAS_TYPE_NAME_ARRAY,
- ],
- [
- 'type' => Type::OAS_TYPE_NAME_OBJECT,
- ],
- ],
+ 'type' => Type::OAS_TYPE_NAME_ARRAY,
];
if (
@@ -75,12 +68,9 @@ public function resolvePhpTypeSchema(Type $phpType, Reflector $phpTypeHolder): a
$arrayElementPhpTypeSchema = $this->openApiPhpTypeSchemaResolverManager
->resolvePhpTypeSchema($arrayElementPhpType, $phpTypeHolder);
- $phpTypeSchema['oneOf'][0]['items'] = $arrayElementPhpTypeSchema;
- $phpTypeSchema['oneOf'][1]['additionalProperties'] = $arrayElementPhpTypeSchema;
-
+ $phpTypeSchema['items'] = $arrayElementPhpTypeSchema;
if ($annotation->limit !== null) {
- $phpTypeSchema['oneOf'][0]['maxItems'] = $annotation->limit;
- $phpTypeSchema['oneOf'][1]['maxProperties'] = $annotation->limit;
+ $phpTypeSchema['maxItems'] = $annotation->limit;
}
}
}
diff --git a/src/OpenApi/PhpTypeSchemaResolver/BackedEnumPhpTypeSchemaResolver.php b/src/OpenApi/PhpTypeSchemaResolver/BackedEnumPhpTypeSchemaResolver.php
index 6409869d..b01e619c 100644
--- a/src/OpenApi/PhpTypeSchemaResolver/BackedEnumPhpTypeSchemaResolver.php
+++ b/src/OpenApi/PhpTypeSchemaResolver/BackedEnumPhpTypeSchemaResolver.php
@@ -14,9 +14,12 @@
namespace Sunrise\Http\Router\OpenApi\PhpTypeSchemaResolver;
use BackedEnum;
+use ReflectionAttribute;
+use ReflectionClass;
use ReflectionEnum;
use ReflectionException;
use Reflector;
+use Sunrise\Http\Router\OpenApi\Annotation\SchemaName;
use Sunrise\Http\Router\OpenApi\Exception\UnsupportedPhpTypeException;
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaNameResolverInterface;
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaResolverInterface;
@@ -26,7 +29,6 @@
use Sunrise\Http\Router\OpenApi\TypeFactory;
use function is_subclass_of;
-use function strtr;
/**
* @since 3.0.0
@@ -79,6 +81,17 @@ public function getWeight(): int
public function resolvePhpTypeSchemaName(Type $phpType, Reflector $phpTypeHolder): string
{
- return strtr($phpType->name, '\\', '.');
+ /** @var class-string $className */
+ $className = $phpType->name;
+ $classReflection = new ReflectionClass($className);
+
+ /** @var list> $annotations */
+ $annotations = $classReflection->getAttributes(SchemaName::class);
+ if (isset($annotations[0])) {
+ $annotation = $annotations[0]->newInstance();
+ return $annotation->value;
+ }
+
+ return $classReflection->getShortName();
}
}
diff --git a/src/OpenApi/PhpTypeSchemaResolver/FloatPhpTypeSchemaResolver.php b/src/OpenApi/PhpTypeSchemaResolver/FloatPhpTypeSchemaResolver.php
index 99bd2492..9b1340ea 100644
--- a/src/OpenApi/PhpTypeSchemaResolver/FloatPhpTypeSchemaResolver.php
+++ b/src/OpenApi/PhpTypeSchemaResolver/FloatPhpTypeSchemaResolver.php
@@ -38,6 +38,7 @@ public function resolvePhpTypeSchema(Type $phpType, Reflector $phpTypeHolder): a
return [
'type' => Type::OAS_TYPE_NAME_NUMBER,
'format' => 'double',
+ 'example' => 0.0,
];
}
diff --git a/src/OpenApi/PhpTypeSchemaResolver/IntPhpTypeSchemaResolver.php b/src/OpenApi/PhpTypeSchemaResolver/IntPhpTypeSchemaResolver.php
index e5903c45..5dd032a0 100644
--- a/src/OpenApi/PhpTypeSchemaResolver/IntPhpTypeSchemaResolver.php
+++ b/src/OpenApi/PhpTypeSchemaResolver/IntPhpTypeSchemaResolver.php
@@ -40,6 +40,7 @@ public function resolvePhpTypeSchema(Type $phpType, Reflector $phpTypeHolder): a
return [
'type' => Type::OAS_TYPE_NAME_INTEGER,
'format' => PHP_INT_SIZE === 4 ? 'int32' : 'int64',
+ 'example' => 0,
];
}
diff --git a/src/OpenApi/PhpTypeSchemaResolver/ObjectPhpTypeSchemaResolver.php b/src/OpenApi/PhpTypeSchemaResolver/ObjectPhpTypeSchemaResolver.php
index b2a24654..6599bd5b 100644
--- a/src/OpenApi/PhpTypeSchemaResolver/ObjectPhpTypeSchemaResolver.php
+++ b/src/OpenApi/PhpTypeSchemaResolver/ObjectPhpTypeSchemaResolver.php
@@ -19,6 +19,7 @@
use ReflectionException;
use ReflectionProperty;
use Reflector;
+use Sunrise\Http\Router\OpenApi\Annotation\SchemaName;
use Sunrise\Http\Router\OpenApi\Exception\UnsupportedPhpTypeException;
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaNameResolverInterface;
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaResolverInterface;
@@ -33,7 +34,6 @@
use function class_exists;
use function is_scalar;
-use function strtr;
/**
* @since 3.0.0
@@ -126,7 +126,18 @@ public function getWeight(): int
public function resolvePhpTypeSchemaName(Type $phpType, Reflector $phpTypeHolder): string
{
- return strtr($phpType->name, '\\', '.');
+ /** @var class-string $className */
+ $className = $phpType->name;
+ $classReflection = new ReflectionClass($className);
+
+ /** @var list> $annotations */
+ $annotations = $classReflection->getAttributes(SchemaName::class);
+ if (isset($annotations[0])) {
+ $annotation = $annotations[0]->newInstance();
+ return $annotation->value;
+ }
+
+ return $classReflection->getShortName();
}
private static function isIgnoredProperty(ReflectionProperty $property): bool
diff --git a/src/Router.php b/src/Router.php
index 0ac93062..650924a0 100644
--- a/src/Router.php
+++ b/src/Router.php
@@ -173,8 +173,12 @@ public function match(ServerRequestInterface $request): RouteInterface
*
* @throws InvalidArgumentException
*/
- public function runRoute(RouteInterface $route, ServerRequestInterface $request): ResponseInterface
+ public function runRoute(RouteInterface|string $route, ServerRequestInterface $request): ResponseInterface
{
+ if (! $route instanceof RouteInterface) {
+ $route = $this->getRoute($route);
+ }
+
foreach ($route->getAttributes() as $name => $value) {
$request = $request->withAttribute($name, $value);
}
@@ -203,8 +207,12 @@ public function runRoute(RouteInterface $route, ServerRequestInterface $request)
*
* @throws InvalidArgumentException
*/
- public function buildRoute(RouteInterface $route, array $values = [], bool $strictly = false): string
+ public function buildRoute(RouteInterface|string $route, array $values = [], bool $strictly = false): string
{
+ if (! $route instanceof RouteInterface) {
+ $route = $this->getRoute($route);
+ }
+
$result = RouteBuilder::buildRoute($route->getPath(), $values + $route->getAttributes());
if ($strictly && !RouteMatcher::matchRoute($this->compileRoute($route), $result)) {
diff --git a/src/RouterInterface.php b/src/RouterInterface.php
index 62ea59b7..3b225d2c 100644
--- a/src/RouterInterface.php
+++ b/src/RouterInterface.php
@@ -48,7 +48,7 @@ public function hasRoute(string $name): bool;
*
* @since 3.0.0
*/
- public function runRoute(RouteInterface $route, ServerRequestInterface $request): ResponseInterface;
+ public function runRoute(RouteInterface|string $route, ServerRequestInterface $request): ResponseInterface;
/**
* @param array $values
@@ -57,7 +57,7 @@ public function runRoute(RouteInterface $route, ServerRequestInterface $request)
*
* @since 3.0.0
*/
- public function buildRoute(RouteInterface $route, array $values = [], bool $strictly = false): string;
+ public function buildRoute(RouteInterface|string $route, array $values = [], bool $strictly = false): string;
/**
* @throws HttpException
diff --git a/tests/Fixture/App/View/PageView.php b/tests/Fixture/App/View/PageView.php
index b6b8854b..8fcbd9dd 100644
--- a/tests/Fixture/App/View/PageView.php
+++ b/tests/Fixture/App/View/PageView.php
@@ -4,6 +4,9 @@
namespace Sunrise\Http\Router\Tests\Fixture\App\View;
+use Sunrise\Http\Router\OpenApi\Annotation\SchemaName;
+
+#[SchemaName('Page')]
final class PageView
{
public function __construct(
diff --git a/tests/Fixture/App/openapi.json b/tests/Fixture/App/openapi.json
index b4e3f57d..5d0cbaa0 100644
--- a/tests/Fixture/App/openapi.json
+++ b/tests/Fixture/App/openapi.json
@@ -50,7 +50,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Auth.SignInRequest"
+ "$ref": "#/components/schemas/SignInRequest"
}
}
},
@@ -86,7 +86,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Product.ProductCreateRequest"
+ "$ref": "#/components/schemas/ProductCreateRequest"
}
}
},
@@ -206,7 +206,7 @@
"in": "query",
"name": "Query",
"schema": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Page.PageListRequest"
+ "$ref": "#/components/schemas/PageListRequest"
},
"required": true
}
@@ -217,20 +217,10 @@
"content": {
"application/json": {
"schema": {
- "oneOf": [
- {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.View.PageView"
- }
- },
- {
- "type": "object",
- "additionalProperties": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.View.PageView"
- }
- }
- ]
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Page"
+ }
}
}
}
@@ -263,7 +253,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.View.PageView"
+ "$ref": "#/components/schemas/Page"
}
}
}
@@ -273,7 +263,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Page.PageCreateRequest"
+ "$ref": "#/components/schemas/PageCreateRequest"
}
}
},
@@ -317,7 +307,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.View.PageView"
+ "$ref": "#/components/schemas/Page"
}
}
}
@@ -327,7 +317,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Page.PageUpdateRequest"
+ "$ref": "#/components/schemas/PageUpdateRequest"
}
}
},
@@ -344,7 +334,7 @@
},
"components": {
"schemas": {
- "Sunrise.Http.Router.Tests.Fixture.App.Dto.Auth.SignInRequest": {
+ "SignInRequest": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -362,7 +352,7 @@
"password"
]
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dto.Product.ProductTagDto": {
+ "ProductTagDto": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -375,7 +365,7 @@
"id"
]
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dictionary.ProductFeature": {
+ "ProductFeature": {
"type": "string",
"enum": [
"fast-delivery",
@@ -805,9 +795,10 @@
"UTC"
]
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dictionary.ProductStatus": {
+ "ProductStatus": {
"type": "integer",
"format": "int64",
+ "example": 0,
"enum": [
0,
1,
@@ -815,7 +806,7 @@
3
]
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dto.Product.ProductCreateRequest": {
+ "ProductCreateRequest": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -826,52 +817,33 @@
"dKQLn8yyMsYG": [],
"publicId": {
"type": "integer",
- "format": "int64"
+ "format": "int64",
+ "example": 0
},
"name": {
"type": "string"
},
"price": {
"type": "number",
- "format": "double"
+ "format": "double",
+ "example": 0
},
"categoryId": {
"type": "string",
"format": "uuid"
},
"tags": {
- "oneOf": [
- {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Product.ProductTagDto"
- }
- },
- {
- "type": "object",
- "additionalProperties": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Product.ProductTagDto"
- }
- }
- ]
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ProductTagDto"
+ }
},
"features": {
- "oneOf": [
- {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dictionary.ProductFeature"
- },
- "maxItems": 2
- },
- {
- "type": "object",
- "additionalProperties": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dictionary.ProductFeature"
- },
- "maxProperties": 2
- }
- ]
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ProductFeature"
+ },
+ "maxItems": 2
},
"isModerated": {
"type": "boolean"
@@ -887,7 +859,7 @@
"status": {
"allOf": [
{
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dictionary.ProductStatus"
+ "$ref": "#/components/schemas/ProductStatus"
}
],
"default": 0
@@ -908,7 +880,7 @@
"timezone"
]
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dto.Page.PageFilterRequest": {
+ "PageFilterRequest": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -924,7 +896,7 @@
}
}
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dto.Common.PaginationDto": {
+ "PaginationDto": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -932,7 +904,8 @@
"allOf": [
{
"type": "integer",
- "format": "int64"
+ "format": "int64",
+ "example": 0
}
],
"default": 20
@@ -941,26 +914,27 @@
"allOf": [
{
"type": "integer",
- "format": "int64"
+ "format": "int64",
+ "example": 0
}
],
"default": 0
}
}
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dto.Page.PageListRequest": {
+ "PageListRequest": {
"type": "object",
"additionalProperties": false,
"properties": {
"filter": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Page.PageFilterRequest"
+ "$ref": "#/components/schemas/PageFilterRequest"
},
"pagination": {
- "$ref": "#/components/schemas/Sunrise.Http.Router.Tests.Fixture.App.Dto.Common.PaginationDto"
+ "$ref": "#/components/schemas/PaginationDto"
}
}
},
- "Sunrise.Http.Router.Tests.Fixture.App.View.PageView": {
+ "Page": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -972,7 +946,7 @@
"name"
]
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dto.Page.PageCreateRequest": {
+ "PageCreateRequest": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -984,7 +958,7 @@
"name"
]
},
- "Sunrise.Http.Router.Tests.Fixture.App.Dto.Page.PageUpdateRequest": {
+ "PageUpdateRequest": {
"type": "object",
"additionalProperties": false,
"properties": {
diff --git a/tests/Middleware/Base64DecodingMiddlewareTest.php b/tests/Middleware/Base64DecodingMiddlewareTest.php
new file mode 100644
index 00000000..d3d06ed5
--- /dev/null
+++ b/tests/Middleware/Base64DecodingMiddlewareTest.php
@@ -0,0 +1,58 @@
+mockedRequest = $this->createMock(ServerRequestInterface::class);
+ $this->mockedRequestHandler = $this->createMock(RequestHandlerInterface::class);
+ $this->mockedResponse = $this->createMock(ResponseInterface::class);
+ $this->mockedStream = $this->createMock(StreamInterface::class);
+ $this->mockedStreamFactory = $this->createMock(StreamFactoryInterface::class);
+ $this->resource = \fopen('php://temp', 'r+b');
+ }
+
+ protected function tearDown(): void
+ {
+ if (\is_resource($this->resource)) {
+ \fclose($this->resource);
+ }
+ }
+
+ public function testProcess(): void
+ {
+ $middleware = new Base64DecodingMiddleware($this->mockedStreamFactory);
+ $this->mockedRequest->expects(self::once())->method('getHeaderLine')->with('Content-Encoding')->willReturn('base64');
+ $this->mockedRequest->expects(self::once())->method('getBody')->willReturn($this->mockedStream);
+ $this->mockedStream->expects(self::once())->method('detach')->willReturn($this->resource);
+ $this->mockedStreamFactory->expects(self::once())->method('createStreamFromResource')->with($this->resource)->willReturn($this->mockedStream);
+ $this->mockedRequest->expects(self::once())->method('withBody')->with($this->mockedStream)->willReturnSelf();
+ $this->mockedRequestHandler->expects(self::once())->method('handle')->with($this->mockedRequest)->willReturn($this->mockedResponse);
+ \fwrite($this->resource, 'Zm9v');
+ \rewind($this->resource);
+ $actualResponse = $middleware->process($this->mockedRequest, $this->mockedRequestHandler);
+ self::assertSame($this->mockedResponse, $actualResponse);
+ self::assertSame('foo', \fread($this->resource, 4));
+ }
+}
diff --git a/tests/RouterTest.php b/tests/RouterTest.php
index 7518d3d6..086d254a 100644
--- a/tests/RouterTest.php
+++ b/tests/RouterTest.php
@@ -236,6 +236,28 @@ public function testRunRoute(): void
self::assertSame($this->mockedResponse, $this->createRouter([])->runRoute($route, $request));
}
+ public function testRunRouteByName(): void
+ {
+ $request = $this->mockServerRequest(path: '/test');
+ $request->expects(self::any())->method('withAttribute')->withAnyParameters()->willReturnSelf();
+ $this->mockedReferenceResolver->expects(self::once())->method('resolveRequestHandler')->with('@test')->willReturn($this->mockedRequestHandler);
+ $this->mockedRequestHandler->expects(self::once())->method('handle')->with($request)->willReturn($this->mockedResponse);
+ self::assertSame($this->mockedResponse, $this->createRouter([
+ $this->mockLoader([
+ $this->mockRoute('test', path: '/test', requestHandler: '@test'),
+ ], calls: 1),
+ ])->runRoute('test', $request));
+ }
+
+ public function testRunRouteByUnknownName(): void
+ {
+ $router = $this->createRouter([]);
+ $request = $this->mockServerRequest(path: '/test');
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('The route "test" does not exist.');
+ $router->runRoute('test', $request);
+ }
+
public function testRunRouteAndPassAttributesToRequest(): void
{
$route = $this->mockRoute('test', path: '/test', requestHandler: $this->mockedRequestHandler);
@@ -245,7 +267,7 @@ public function testRunRouteAndPassAttributesToRequest(): void
static function ($name, $value) use ($route, $request) {
self::assertContains([$name, $value], [[RouteInterface::class, $route], ['foo', 'bar']]);
return $request;
- }
+ },
);
$this->mockedReferenceResolver->expects(self::once())->method('resolveRequestHandler')->withAnyParameters()->willReturnArgument(0);
$this->mockedRequestHandler->expects(self::once())->method('handle')->with($request)->willReturn($this->mockedResponse);
@@ -264,7 +286,7 @@ static function (object $event) use ($request, $overriddenRequest) {
self::assertSame($request, $event->request);
$event->request = $overriddenRequest;
}
- }
+ },
);
$this->mockedReferenceResolver->expects(self::once())->method('resolveRequestHandler')->withAnyParameters()->willReturnArgument(0);
$this->mockedRequestHandler->expects(self::once())->method('handle')->with($overriddenRequest)->willReturn($this->mockedResponse);
@@ -285,7 +307,7 @@ function (object $event) use ($overriddenResponse) {
self::assertSame($this->mockedResponse, $event->response);
$event->response = $overriddenResponse;
}
- }
+ },
);
self::assertSame($overriddenResponse, $this->createRouter([])->runRoute($route, $request));
}
@@ -311,6 +333,24 @@ public function testBuildRoute(string $routePath, string $expectedPath, array $v
self::assertSame($expectedPath, $this->createRouter([])->buildRoute($route, $variables, $strictly));
}
+ #[DataProvider('buildRouteDataProvider')]
+ public function testBuildRouteByName(string $routePath, string $expectedPath, array $variables = [], bool $strictly = false): void
+ {
+ self::assertSame($expectedPath, $this->createRouter([
+ $this->mockLoader([
+ $this->mockRoute('test', path: $routePath),
+ ], calls: 1),
+ ])->buildRoute('test', $variables, $strictly));
+ }
+
+ public function testBuildRouteByUnknownName(): void
+ {
+ $router = $this->createRouter([]);
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('The route "test" does not exist.');
+ $router->buildRoute('test');
+ }
+
public static function buildRouteDataProvider(): Generator
{
yield ['/test', '/test'];
@@ -374,7 +414,7 @@ public function testHandle(): void
static function ($name, $value) use ($route, $request) {
self::assertContains([$name, $value], [['foo', 'bar'], ['bar', 'baz'], [RouteInterface::class, $route]]);
return $request;
- }
+ },
);
$this->mockedRequestHandler->expects(self::once())->method('handle')->with($request)->willReturn($this->mockedResponse);
@@ -392,7 +432,7 @@ static function (RoutePreRunEvent|RoutePostRunEvent $event) use ($eventOverridde
if ($event instanceof RoutePostRunEvent) {
$event->response = $eventOverriddenResponse;
}
- }
+ },
);
$router = $this->createRouter(