Skip to content

Commit

Permalink
Add composite parameters resolver (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed Apr 25, 2023
1 parent aead5ca commit b6ecaa3
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 16 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Expand Up @@ -10,6 +10,10 @@ indent_style = space
indent_size = 4
trim_trailing_whitespace = true

[*.php]
ij_php_space_before_short_closure_left_parenthesis = false
ij_php_space_after_type_cast = true

[*.md]
trim_trailing_whitespace = false

Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
@@ -1,8 +1,9 @@
# Yii Middleware Dispatcher Change Log

## 5.0.1 under development
## 5.1.0 under development

- Enh #75: Optimize `MiddlewareFactory` performance (@random-rage)
- New #76: Add composite parameters resolver (@vjik)

## 5.0.0 January 09, 2023

Expand Down
24 changes: 21 additions & 3 deletions README.md
Expand Up @@ -110,9 +110,9 @@ class CoolParametersResolver implements ParametersResolverInterface
public function resolve(array $parameters, ServerRequestInterface $request): MiddlewareInterface
{
$resolvedParameters = [];
foreach ($parameters as $parameter) {
if ($request->getAttribute($parameter->getName()) !== null) {
$resolvedParameters[$parameter->getName()] = $request->getAttribute($parameter->getName())
foreach ($parameters as $name => $parameter) {
if ($request->getAttribute($name) !== null) {
$resolvedParameters[$name] = $request->getAttribute($name)
}
}

Expand All @@ -133,6 +133,24 @@ $dispatcher = new MiddlewareDispatcher(
);
```

To combine several parameters' resolvers use `CompositeParametersResolver`:

```php
use Yiisoft\Middleware\Dispatcher\CompositeParametersResolver;
use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;

$dispatcher = new MiddlewareDispatcher(
new MiddlewareFactory(
$diContainer, new CompositeParametersResolver(
new CoolParametersResolver(),
new YetAnotherParametersResolver(),
)
),
$eventDispatcher
);
```

## Testing

### Unit testing
Expand Down
40 changes: 40 additions & 0 deletions src/CompositeParametersResolver.php
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher;

use Psr\Http\Message\ServerRequestInterface;

use function in_array;

final class CompositeParametersResolver implements ParametersResolverInterface
{
/**
* @var ParametersResolverInterface[]
*/
private array $resolvers;

public function __construct(ParametersResolverInterface ...$resolvers)
{
$this->resolvers = $resolvers;
}

public function resolve(array $parameters, ServerRequestInterface $request): array
{
$results = [];
foreach ($this->resolvers as $resolver) {
$result = $resolver->resolve($parameters, $request);
$results[] = $result;

$resultKeys = array_keys($result);
$parameters = array_filter(
$parameters,
static fn($key) => !in_array($key, $resultKeys, true),
ARRAY_FILTER_USE_KEY
);
}

return array_merge(...$results);
}
}
29 changes: 21 additions & 8 deletions src/MiddlewareFactory.php
Expand Up @@ -144,8 +144,11 @@ private function createCallableWrapper(callable $callback): MiddlewareInterface
return new class ($callback, $this->container, $this->parametersResolver) implements MiddlewareInterface {
/** @var callable */
private $callback;
/** @var ReflectionParameter[] */
private array $callableParameters;
/**
* @var ReflectionParameter[]
* @psalm-var array<string,ReflectionParameter>
*/
private array $callableParameters = [];

public function __construct(
callable $callback,
Expand All @@ -154,7 +157,11 @@ public function __construct(
) {
$this->callback = $callback;
$callback = Closure::fromCallable($callback);
$this->callableParameters = (new ReflectionFunction($callback))->getParameters();

$callableParameters = (new ReflectionFunction($callback))->getParameters();
foreach ($callableParameters as $parameter) {
$this->callableParameters[$parameter->getName()] = $parameter;
}
}

public function process(
Expand All @@ -168,6 +175,7 @@ public function process(
$this->parametersResolver->resolve($this->callableParameters, $request)
);
}

/** @var MiddlewareInterface|mixed|ResponseInterface $response */
$response = (new Injector($this->container))->invoke($this->callback, $parameters);
if ($response instanceof ResponseInterface) {
Expand All @@ -176,6 +184,7 @@ public function process(
if ($response instanceof MiddlewareInterface) {
return $response->process($request, $handler);
}

throw new InvalidMiddlewareDefinitionException($this->callback);
}

Expand All @@ -193,8 +202,11 @@ public function __debugInfo(): array
private function createActionWrapper(string $class, string $method): MiddlewareInterface
{
return new class ($this->container, $this->parametersResolver, $class, $method) implements MiddlewareInterface {
/** @var ReflectionParameter[] */
private array $actionParameters;
/**
* @var ReflectionParameter[]
* @psalm-var array<string,ReflectionParameter>
*/
private array $actionParameters = [];

public function __construct(
private ContainerInterface $container,
Expand All @@ -204,9 +216,10 @@ public function __construct(
/** @var non-empty-string */
private string $method
) {
$this->actionParameters = (new ReflectionClass($this->class))
->getMethod($this->method)
->getParameters();
$actionParameters = (new ReflectionClass($this->class))->getMethod($this->method)->getParameters();
foreach ($actionParameters as $parameter) {
$this->actionParameters[$parameter->getName()] = $parameter;
}
}

public function process(
Expand Down
4 changes: 3 additions & 1 deletion src/ParametersResolverInterface.php
Expand Up @@ -19,7 +19,9 @@ interface ParametersResolverInterface
*
* @param ReflectionParameter[] $parameters
*
* @return array<array-key, mixed>
* @psalm-param array<string,ReflectionParameter> $parameters
*
* @psalm-return array<string,mixed>
*/
public function resolve(array $parameters, ServerRequestInterface $request): array;
}
39 changes: 39 additions & 0 deletions tests/CompositeParametersResolverTest.php
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher\Tests;

use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Yiisoft\Middleware\Dispatcher\CompositeParametersResolver;
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;
use Yiisoft\Middleware\Dispatcher\Tests\Support\ParametersResolver\NameParametersResolver;
use Yiisoft\Middleware\Dispatcher\Tests\Support\TestController;
use Yiisoft\Test\Support\Container\SimpleContainer;

final class CompositeParametersResolverTest extends TestCase
{
public function testBase(): void
{
$resolver = new CompositeParametersResolver(
new NameParametersResolver(['a' => 1, 'b' => 2]),
new NameParametersResolver(['a' => 10, 'b' => 11, 'c' => 12, 'd' => 13]),
);

$container = new SimpleContainer([TestController::class => new TestController()]);
$middleware = (new MiddlewareFactory($container, $resolver))
->create([TestController::class, 'compositeResolver']);

$response = $middleware->process(
$this->createMock(ServerRequestInterface::class),
$this->createMock(RequestHandlerInterface::class)
);

$this->assertSame(
'1-2-12-13',
$response->getReasonPhrase(),
);
}
}
2 changes: 1 addition & 1 deletion tests/MiddlewareFactoryTest.php
Expand Up @@ -16,7 +16,7 @@
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;
use Yiisoft\Middleware\Dispatcher\ParametersResolverInterface;
use Yiisoft\Middleware\Dispatcher\Tests\Support\InvokeableAction;
use Yiisoft\Middleware\Dispatcher\Tests\Support\SimpleParametersResolver;
use Yiisoft\Middleware\Dispatcher\Tests\Support\ParametersResolver\SimpleParametersResolver;
use Yiisoft\Middleware\Dispatcher\Tests\Support\UseParamsController;
use Yiisoft\Middleware\Dispatcher\Tests\Support\UseParamsMiddleware;
use Yiisoft\Middleware\Dispatcher\Tests\Support\InvalidController;
Expand Down
29 changes: 29 additions & 0 deletions tests/Support/ParametersResolver/NameParametersResolver.php
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher\Tests\Support\ParametersResolver;

use Psr\Http\Message\ServerRequestInterface;
use Yiisoft\Middleware\Dispatcher\ParametersResolverInterface;

use function array_key_exists;

final class NameParametersResolver implements ParametersResolverInterface
{
public function __construct(private array $data)
{
}

public function resolve(array $parameters, ServerRequestInterface $request): array
{
$result = [];
foreach ($parameters as $name => $_parameter) {
if (array_key_exists($name, $this->data)) {
$result[$name] = $this->data[$name];
}
}

return $result;
}
}
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher\Tests\Support;
namespace Yiisoft\Middleware\Dispatcher\Tests\Support\ParametersResolver;

use Psr\Http\Message\ServerRequestInterface;
use Yiisoft\Middleware\Dispatcher\ParametersResolverInterface;
Expand Down
9 changes: 8 additions & 1 deletion tests/Support/TestController.php
Expand Up @@ -14,8 +14,15 @@ public function index(): ResponseInterface
return new Response(200, ['test' => 'yii']);
}

public function indexWithParams(string $test = '')
public function indexWithParams(string $test = ''): ResponseInterface
{
return new Response(200, ['test' => $test]);
}

public function compositeResolver(int $a = 0, int $b = 0, int $c = 0, int $d = 0): ResponseInterface
{
return new Response(
reason: $a . '-' . $b . '-' . $c . '-' . $d,
);
}
}

0 comments on commit b6ecaa3

Please sign in to comment.