Skip to content

Commit

Permalink
Merge pull request #11 from kawanamiyuu/request-mapper
Browse files Browse the repository at this point in the history
Request Mapping
  • Loading branch information
kawanamiyuu authored Jul 21, 2020
2 parents 97917f6 + 9afe331 commit 6f1250f
Show file tree
Hide file tree
Showing 14 changed files with 312 additions and 95 deletions.
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"minimum-stability": "stable",
"require": {
"php": "^7.4",
"ext-json": "*",
"k9u/request-mapper": "^0.11.0",
"laminas/laminas-diactoros": "^2.3",
"laminas/laminas-httphandlerrunner": "^1.2",
"psr/http-factory": "^1.0",
Expand Down
10 changes: 8 additions & 2 deletions demo/src/AppModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@
namespace K9u\Framework\Demo;

use K9u\Framework\FrameworkModule;
use K9u\RequestMapper\Annotation\AbstractMapping;
use Ray\Di\AbstractModule;

class AppModule extends AbstractModule
{
protected function configure()
{
$this->bindInterceptor(
$this->matcher->any(),
$this->matcher->annotatedWith(AbstractMapping::class),
[FakeInterceptor::class]
);

$middlewares = [
FakeMiddleware::class,
FakeRequestHandler::class
FakeMiddleware::class
];

$this->install(new FrameworkModule($middlewares));
Expand Down
47 changes: 47 additions & 0 deletions demo/src/FakeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace K9u\Framework\Demo;

use K9u\RequestMapper\Annotation\GetMapping;
use K9u\RequestMapper\PathParams;
use Psr\Http\Message\ServerRequestInterface;

class FakeController
{
/**
* @GetMapping("/")
*
* @return array<string, mixed>
*/
public function index(): array
{
return ['path' => '/'];
}

/**
* @GetMapping("/blogs")
*
* @return array<string, mixed>
*/
public function getBlogs(): array
{
return ['path' => '/blogs'];
}

/**
* @GetMapping("/blogs/{id}")
*
* @param ServerRequestInterface $request
*
* @return array<string, mixed>
*/
public function getBlogById(ServerRequestInterface $request): array
{
$pathParams = $request->getAttribute(PathParams::class);
assert($pathParams instanceof PathParams);

return ['path' => "/blogs/{$pathParams['id']}"];
}
}
22 changes: 22 additions & 0 deletions demo/src/FakeInterceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace K9u\Framework\Demo;

use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;

class FakeInterceptor implements MethodInterceptor
{
public function invoke(MethodInvocation $invocation)
{
$result = $invocation->proceed();
assert(is_array($result));

$method = $invocation->getMethod();
$class = $method->getDeclaringClass();

return $result + ['handler' => sprintf('%s::%s', $class->getName(), $method->getName())];
}
}
8 changes: 7 additions & 1 deletion demo/src/FakeMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ class FakeMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
return $handler->handle($request);
$response = $handler->handle($request);

foreach ($request->getHeaders() as $name => $values) {
$response = $response->withHeader("X-Request-{$name}", $values);
}

return $response;
}
}
33 changes: 0 additions & 33 deletions demo/src/FakeRequestHandler.php

This file was deleted.

15 changes: 14 additions & 1 deletion src/ExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace K9u\Framework;

use K9u\RequestMapper\Exception\HandlerNotFoundException;
use K9u\RequestMapper\Exception\MethodNotAllowedException;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand All @@ -24,6 +26,17 @@ public function __invoke(Throwable $throwable, ServerRequestInterface $request):

error_log((string) $throwable);

return $this->responseFactory->createResponse(505);
return $this->responseFactory->createResponse(self::getStatusCode($throwable));
}

private static function getStatusCode(Throwable $throwable): int
{
if ($throwable instanceof HandlerNotFoundException) {
return 404;
} elseif ($throwable instanceof MethodNotAllowedException) {
return 405;
}

return 500;
}
}
27 changes: 0 additions & 27 deletions src/FallbackHandler.php

This file was deleted.

87 changes: 74 additions & 13 deletions src/FrameworkModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

namespace K9u\Framework;

use K9u\RequestMapper\HandlerClassFactoryInterface;
use K9u\RequestMapper\HandlerCollectorInterface;
use K9u\RequestMapper\HandlerInvoker;
use K9u\RequestMapper\HandlerInvokerInterface;
use K9u\RequestMapper\HandlerMethodArgumentsResolverInterface;
use K9u\RequestMapper\HandlerResolver;
use K9u\RequestMapper\HandlerResolverInterface;
use K9u\RequestMapper\OnDemandHandlerCollector;
use Laminas\Diactoros\ResponseFactory;
use Laminas\Diactoros\StreamFactory;
use Psr\Http\Message\ResponseFactoryInterface;
Expand All @@ -22,41 +30,94 @@ class FrameworkModule extends AbstractModule
*/
private array $middlewares;

private string $handlerDir;

/**
* @param array<class-string> $middlewares
* @param string|null $handlerDir
* @param AbstractModule|null $module
*/
public function __construct(array $middlewares = [], AbstractModule $module = null)
public function __construct(array $middlewares = [], string $handlerDir = null, AbstractModule $module = null)
{
$this->middlewares = array_merge($middlewares, [FallbackHandler::class]);
$this->middlewares = array_merge($middlewares, [RequestMappingHandler::class]);

$this->handlerDir = $handlerDir ?? dirname(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 0)[0]['file']);
assert(is_dir($this->handlerDir));

parent::__construct($module);
}

protected function configure(): void
{
foreach ($this->middlewares as $middleware) {
$this->bind($middleware)->in(Scope::SINGLETON);
}
self::bindHttpFactory($this);

$this->bind()->annotatedWith(MiddlewareCollection::class)
->toInstance($this->middlewares);
self::bindRequestMapper($this, $this->handlerDir);

$this->bind(RequestHandlerInterface::class)
->toProvider(RequestHandlerProvider::class)->in(Scope::SINGLETON);
self::bindRequestHandler($this, $this->middlewares);

$this->bind(ExceptionHandlerInterface::class)
->to(ExceptionHandler::class)->in(Scope::SINGLETON);

$this->bind(ResponseEmitterInterface::class)
->to(ResponseEmitter::class)->in(Scope::SINGLETON);

$this->bind(ResponseFactoryInterface::class)
$this->bind(ApplicationInterface::class)
->to(Application::class)->in(Scope::SINGLETON);
}

/**
* @param AbstractModule $module
*/
private static function bindHttpFactory(AbstractModule $module): void
{
$module->bind(ResponseFactoryInterface::class)
->to(ResponseFactory::class)->in(Scope::SINGLETON);

$this->bind(StreamFactoryInterface::class)
$module->bind(StreamFactoryInterface::class)
->to(StreamFactory::class)->in(Scope::SINGLETON);
}

$this->bind(ApplicationInterface::class)
->to(Application::class)->in(Scope::SINGLETON);
/**
* @param AbstractModule $module
* @param string $handlerDir
*/
private static function bindRequestMapper(AbstractModule $module, string $handlerDir): void
{
$module->bind()->annotatedWith('handler_dir')
->toInstance($handlerDir);

// TODO: bind `CachedHandlerCollector`
$module->bind(HandlerCollectorInterface::class)
->toConstructor(OnDemandHandlerCollector::class, ['baseDir' => 'handler_dir'])
->in(Scope::SINGLETON);

$module->bind(HandlerResolverInterface::class)
->to(HandlerResolver::class)->in(Scope::SINGLETON);

$module->bind(HandlerClassFactoryInterface::class)
->to(HandlerClassFactory::class)->in(Scope::SINGLETON);

$module->bind(HandlerMethodArgumentsResolverInterface::class)
->to(HandlerMethodArgumentsResolver::class)->in(Scope::SINGLETON);

$module->bind(HandlerInvokerInterface::class)
->to(HandlerInvoker::class)->in(Scope::SINGLETON);
}

/**
* @param AbstractModule $module
* @param array<class-string> $middlewares
*/
private static function bindRequestHandler(AbstractModule $module, array $middlewares): void
{
foreach ($middlewares as $middleware) {
$module->bind($middleware)->in(Scope::SINGLETON);
}

$module->bind()->annotatedWith('middleware_collection')
->toInstance($middlewares);

$module->bind(RequestHandlerInterface::class)
->toProvider(RequestHandlerProvider::class)->in(Scope::SINGLETON);
}
}
23 changes: 23 additions & 0 deletions src/HandlerClassFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace K9u\Framework;

use K9u\RequestMapper\HandlerClassFactoryInterface;
use Ray\Di\InjectorInterface;

class HandlerClassFactory implements HandlerClassFactoryInterface
{
private InjectorInterface $injector;

public function __construct(InjectorInterface $injector)
{
$this->injector = $injector;
}

public function __invoke(string $class): object
{
return $this->injector->getInstance($class);
}
}
42 changes: 42 additions & 0 deletions src/HandlerMethodArgumentsResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace K9u\Framework;

use K9u\RequestMapper\HandlerMethodArgumentsResolverInterface;
use K9u\RequestMapper\NamedArguments;
use K9u\RequestMapper\PathParams;
use Psr\Http\Message\ServerRequestInterface;
use ReflectionMethod;
use ReflectionNamedType;

class HandlerMethodArgumentsResolver implements HandlerMethodArgumentsResolverInterface
{
public function __invoke(
ReflectionMethod $method,
ServerRequestInterface $request,
PathParams $pathParams
): NamedArguments {

$request = $request->withAttribute(PathParams::class, $pathParams);

$args = [];
foreach ($method->getParameters() as $param) {
if ($param->hasType()) {
$type = $param->getType();
assert($type instanceof ReflectionNamedType);

if (is_a($type->getName(), ServerRequestInterface::class, true)) {
$args[$param->getName()] = $request;
continue;
}
}

// TODO: assign Path params, Query params, Parsed body, ...
$args[$param->getName()] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
}

return new NamedArguments($args);
}
}
Loading

0 comments on commit 6f1250f

Please sign in to comment.