Skip to content

Commit

Permalink
Remove wrapper factory, move parameters resolver from request-model (
Browse files Browse the repository at this point in the history
  • Loading branch information
rustamwin committed Dec 21, 2022
1 parent 0cccd5d commit 5375ef9
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 172 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
@@ -1,8 +1,9 @@
# Yii Middleware Dispatcher Change Log

## 4.0.1 under development
## 5.0.0 under development

- no changes in this release.
- Chg #68: Remove wrapper factory (@rustamwin)
- New #68: Add `ParametersResolverInterface` to resolve parameters of middleware that are provided as callable (@rustamwin)

## 4.0.0 November 10, 2022

Expand Down
28 changes: 13 additions & 15 deletions README.md
Expand Up @@ -96,28 +96,26 @@ executed first. For each middleware
`\Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware` and `\Yiisoft\Middleware\Dispatcher\Event\AfterMiddleware`
events are triggered.

### Customizing callable wrapper
### Creating your own implementation of parameters resolver

Callable wrapper could be customized by providing your own `WrapperFactoryInterface` implementation:
Parameters resolver could be customized by providing your own `ParametersResolverInterface` implementation:

```php
use \Yiisoft\Middleware\Dispatcher\WrapperFactoryInterface;
use \Psr\Http\Message\ServerRequestInterface;
use \Yiisoft\Middleware\Dispatcher\ParametersResolverInterface;

class CoolWrapperFactory implements WrapperFactoryInterface
class CoolParametersResolver implements ParametersResolverInterface
{
private WrapperFactoryInterface $wrapperFactory;

public function __construct(WrapperFactoryInterface $wrapperFactory) {
$this->wrapperFactory = $wrapperFactory;
}

public function create($callable): MiddlewareInterface
public function resolve(array $parameters, ServerRequestInterface $request): MiddlewareInterface
{
if (is_array($callable)) {
return createMiddleware($callable);
$resolvedParameters = [];
foreach ($parameters as $parameter) {
if ($request->getAttribute($parameter->getName()) !== null) {
$resolvedParameters[$parameter->getName()] = $request->getAttribute($parameter->getName())
}
}

$this->wrapperFactory->create($callable);
return $resolvedParameters;
}
}
```
Expand All @@ -129,7 +127,7 @@ use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;

$dispatcher = new MiddlewareDispatcher(
new MiddlewareFactory($diContainer, new CoolWrapperFactory($wrapperFactory)),
new MiddlewareFactory($diContainer, new CoolParametersResolver()),
$eventDispatcher
);
```
Expand Down
10 changes: 1 addition & 9 deletions composer.json
Expand Up @@ -34,7 +34,7 @@
"rector/rector": "^0.14.3",
"roave/infection-static-analysis-plugin": "^1.18",
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.30|^5.2",
"vimeo/psalm": "^4.30|^5.3",
"yiisoft/test-support": "^1.3"
},
"autoload": {
Expand All @@ -47,14 +47,6 @@
"Yiisoft\\Middleware\\Dispatcher\\Tests\\": "tests"
}
},
"extra": {
"config-plugin-options": {
"source-directory": "config"
},
"config-plugin": {
"web": "web.php"
}
},
"config": {
"sort-packages": true,
"allow-plugins": {
Expand Down
10 changes: 0 additions & 10 deletions config/web.php

This file was deleted.

140 changes: 130 additions & 10 deletions src/MiddlewareFactory.php
Expand Up @@ -6,12 +6,16 @@

use Closure;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use ReflectionClass;
use ReflectionFunction;
use Yiisoft\Definitions\ArrayDefinition;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Helpers\DefinitionValidator;
use Yiisoft\Injector\Injector;

use function in_array;
use function is_array;
Expand All @@ -29,7 +33,7 @@ final class MiddlewareFactory
*/
public function __construct(
private ContainerInterface $container,
private WrapperFactoryInterface $wrapperFactory
private ?ParametersResolverInterface $parametersResolver = null
) {
}

Expand Down Expand Up @@ -59,15 +63,13 @@ public function create(array|callable|string $middlewareDefinition): MiddlewareI
}

if ($this->isCallableDefinition($middlewareDefinition)) {
/** @var array{0:class-string, 1:string}|Closure $middlewareDefinition */
return $this->wrapperFactory->create($middlewareDefinition);
return $this->wrapCallable($middlewareDefinition);
}

if ($this->isArrayDefinition($middlewareDefinition)) {
/**
* @psalm-var ArrayDefinitionConfig $middlewareDefinition
*
* @var MiddlewareInterface
* @psalm-suppress InvalidArgument Need for Psalm version 4.* only.
*/
return ArrayDefinition::fromConfig($middlewareDefinition)->resolve($this->container);
}
Expand All @@ -78,16 +80,16 @@ public function create(array|callable|string $middlewareDefinition): MiddlewareI
/**
* @psalm-assert-if-true class-string<MiddlewareInterface> $definition
*/
private function isMiddlewareClassDefinition(mixed $definition): bool
private function isMiddlewareClassDefinition(array|callable|string $definition): bool
{
return is_string($definition)
&& is_subclass_of($definition, MiddlewareInterface::class);
}

/**
* @psalm-assert-if-true array|Closure $definition
* @psalm-assert-if-true array{0:class-string, 1:non-empty-string}|Closure $definition
*/
private function isCallableDefinition(mixed $definition): bool
private function isCallableDefinition(array|callable|string $definition): bool
{
if ($definition instanceof Closure) {
return true;
Expand All @@ -107,7 +109,7 @@ class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
/**
* @psalm-assert-if-true ArrayDefinitionConfig $definition
*/
private function isArrayDefinition(mixed $definition): bool
private function isArrayDefinition(array|callable|string $definition): bool
{
if (!is_array($definition)) {
return false;
Expand All @@ -119,6 +121,124 @@ private function isArrayDefinition(mixed $definition): bool
return false;
}

return is_subclass_of((string) ($definition['class'] ?? ''), MiddlewareInterface::class);
return is_subclass_of((string)($definition['class'] ?? ''), MiddlewareInterface::class);
}

/**
* @param array{0:class-string, 1:non-empty-string}|Closure $callable
*/
private function wrapCallable(array|Closure $callable): MiddlewareInterface
{
if (is_array($callable)) {
return $this->createActionWrapper($callable[0], $callable[1]);
}

return $this->createCallableWrapper($callable);
}

private function createCallableWrapper(callable $callback): MiddlewareInterface
{
return new class ($callback, $this->container, $this->parametersResolver) implements MiddlewareInterface {
private $callback;

public function __construct(
callable $callback,
private ContainerInterface $container,
private ?ParametersResolverInterface $parametersResolver
) {
$this->callback = $callback;
}

public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$parameters = [$request, $handler];
if ($this->parametersResolver !== null) {
$parameters = array_merge(
$parameters,
$this->parametersResolver->resolve($this->getCallableParameters(), $request)
);
}
/** @var MiddlewareInterface|mixed|ResponseInterface $response */
$response = (new Injector($this->container))->invoke($this->callback, $parameters);
if ($response instanceof ResponseInterface) {
return $response;
}
if ($response instanceof MiddlewareInterface) {
return $response->process($request, $handler);
}
throw new InvalidMiddlewareDefinitionException($this->callback);
}

/**
* @return \ReflectionParameter[]
*/
private function getCallableParameters(): array
{
$callback = Closure::fromCallable($this->callback);

return (new ReflectionFunction($callback))->getParameters();
}
};
}

/**
* @param class-string $class
* @param non-empty-string $method
*/
private function createActionWrapper(string $class, string $method): MiddlewareInterface
{
return new class ($this->container, $this->parametersResolver, $class, $method) implements MiddlewareInterface {
public function __construct(
private ContainerInterface $container,
private ?ParametersResolverInterface $parametersResolver,
/** @var class-string */
private string $class,
/** @var non-empty-string */
private string $method
) {
}

public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
/** @var mixed $controller */
$controller = $this->container->get($this->class);
$parameters = [$request, $handler];
if ($this->parametersResolver !== null) {
$parameters = array_merge(
$parameters,
$this->parametersResolver->resolve($this->getActionParameters(), $request)
);
}

/** @var mixed|ResponseInterface $response */
$response = (new Injector($this->container))->invoke([$controller, $this->method], $parameters);
if ($response instanceof ResponseInterface) {
return $response;
}

throw new InvalidMiddlewareDefinitionException([$this->class, $this->method]);
}

/**
* @throws \ReflectionException
*
* @return \ReflectionParameter[]
*/
private function getActionParameters(): array
{
return (new ReflectionClass($this->class))->getMethod($this->method)->getParameters();
}

public function __debugInfo()
{
return [
'callback' => [$this->class, $this->method],
];
}
};
}
}
25 changes: 25 additions & 0 deletions src/ParametersResolverInterface.php
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher;

use Psr\Http\Message\ServerRequestInterface;
use ReflectionParameter;

/**
* Resolves parameters of PSR-15 middleware that are provided as callable.
* You may implement this interface if you want to introduce custom dependencies or inject additional data from
* the {@see ServerRequestInterface} (e.g. using attributes) to the middleware.
*/
interface ParametersResolverInterface
{
/**
* Resolve parameters of a PSR-15 middleware the provided as callable.
*
* @param ReflectionParameter[] $parameters
*
* @return array<array-key, mixed>
*/
public function resolve(array $parameters, ServerRequestInterface $request): array;
}

0 comments on commit 5375ef9

Please sign in to comment.