Skip to content

Commit f63fc6d

Browse files
committed
Issue #390: WIP
Signed-off-by: alexmerlin <[email protected]>
1 parent 2d67f51 commit f63fc6d

File tree

9 files changed

+169
-56
lines changed

9 files changed

+169
-56
lines changed

config/pipeline.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Api\App\Middleware\ContentNegotiationMiddleware;
99
use Api\App\Middleware\DeprecationMiddleware;
1010
use Api\App\Middleware\ResponseMiddleware;
11+
use Core\App\Middleware\ResourceProviderMiddleware;
1112
use Dot\ErrorHandler\ErrorHandlerInterface;
1213
use Dot\ResponseHeader\Middleware\ResponseHeaderMiddleware;
1314
use Mezzio\Application;
@@ -81,6 +82,7 @@
8182
// - etc.
8283

8384
$app->pipe(ResponseMiddleware::class);
85+
$app->pipe(ResourceProviderMiddleware::class);
8486

8587
// Register the dispatch middleware in the middleware pipeline
8688
$app->pipe(DispatchMiddleware::class);

src/Admin/src/Handler/Admin/GetAdminResourceHandler.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
namespace Api\Admin\Handler\Admin;
66

77
use Api\Admin\Service\AdminServiceInterface;
8-
use Api\App\Exception\NotFoundException;
98
use Api\App\Handler\AbstractHandler;
9+
use Core\Admin\Entity\Admin;
10+
use Core\App\Attribute\Resource;
1011
use Dot\DependencyInjection\Attribute\Inject;
1112
use Psr\Http\Message\ResponseInterface;
1213
use Psr\Http\Message\ServerRequestInterface;
@@ -21,13 +22,12 @@ public function __construct(
2122
) {
2223
}
2324

24-
/**
25-
* @throws NotFoundException
26-
*/
25+
#[Resource(class: Admin::class)]
2726
public function handle(ServerRequestInterface $request): ResponseInterface
2827
{
29-
$admin = $this->adminService->findOneBy(['uuid' => $request->getAttribute('uuid')]);
30-
31-
return $this->createResponse($request, $admin);
28+
return $this->createResponse(
29+
$request,
30+
$request->getAttribute(Resource::class)
31+
);
3232
}
3333
}

src/App/src/Middleware/DeprecationMiddleware.php

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,16 @@
77
use Api\App\Attribute\MethodDeprecation;
88
use Api\App\Attribute\ResourceDeprecation;
99
use Api\App\Exception\DeprecationConflictException;
10+
use Api\App\Service\HandlerService;
1011
use Core\App\Message;
1112
use Dot\DependencyInjection\Attribute\Inject;
12-
use Dot\Router\Middleware\LazyLoadingMiddleware;
13-
use Laminas\Stratigility\MiddlewarePipe;
14-
use Mezzio\Middleware\LazyLoadingMiddleware as MezzioLazyLoadingMiddleware;
15-
use Mezzio\Router\RouteResult;
1613
use Psr\Http\Message\ResponseInterface;
1714
use Psr\Http\Message\ServerRequestInterface;
1815
use Psr\Http\Server\MiddlewareInterface;
1916
use Psr\Http\Server\RequestHandlerInterface;
2017
use ReflectionClass;
2118
use ReflectionException;
2219
use ReflectionMethod;
23-
use RuntimeException;
2420

2521
use function array_column;
2622
use function array_filter;
@@ -57,19 +53,10 @@ public function process(
5753
ServerRequestInterface $request,
5854
RequestHandlerInterface $handler
5955
): ResponseInterface {
60-
$response = $handler->handle($request);
61-
$routeResult = $request->getAttribute(RouteResult::class);
62-
if (! $routeResult instanceof RouteResult || $routeResult->isFailure()) {
63-
return $response;
64-
}
65-
66-
$matchedRoute = $routeResult->getMatchedRoute();
67-
if (! $matchedRoute) {
68-
return $response;
69-
}
56+
$response = $handler->handle($request);
7057

71-
$reflectionHandler = $this->getHandler($matchedRoute->getMiddleware());
72-
if (empty($reflectionHandler)) {
58+
$reflectionHandler = HandlerService::fromRequest($request);
59+
if (! $reflectionHandler instanceof ReflectionClass) {
7360
return $response;
7461
}
7562

@@ -136,38 +123,6 @@ private function getReflectionAttributes(ReflectionClass $reflectionObject): arr
136123
return $attributes;
137124
}
138125

139-
/**
140-
* @throws ReflectionException
141-
*/
142-
private function getHandler(MiddlewareInterface $routeMiddleware): ?ReflectionClass
143-
{
144-
$reflectionHandler = null;
145-
if (
146-
$routeMiddleware instanceof MezzioLazyLoadingMiddleware
147-
|| $routeMiddleware instanceof LazyLoadingMiddleware
148-
) {
149-
/** @var class-string $routeMiddlewareName */
150-
$routeMiddlewareName = $routeMiddleware->middlewareName;
151-
$reflectionMiddlewareClass = new ReflectionClass($routeMiddlewareName);
152-
if ($reflectionMiddlewareClass->implementsInterface(RequestHandlerInterface::class)) {
153-
$reflectionHandler = $reflectionMiddlewareClass;
154-
}
155-
} elseif ($routeMiddleware instanceof MiddlewarePipe) {
156-
$reflectionClass = new ReflectionClass($routeMiddleware);
157-
$middlewarePipeline = $reflectionClass->getProperty('pipeline')->getValue($routeMiddleware);
158-
for ($middlewarePipeline->rewind(); $middlewarePipeline->valid(); $middlewarePipeline->next()) {
159-
$reflectionMiddlewareClass = new ReflectionClass($middlewarePipeline->current()->middlewareName);
160-
if ($reflectionMiddlewareClass->implementsInterface(RequestHandlerInterface::class)) {
161-
$reflectionHandler = $reflectionMiddlewareClass;
162-
}
163-
}
164-
} else {
165-
throw new RuntimeException('Invalid route middleware provided.');
166-
}
167-
168-
return $reflectionHandler;
169-
}
170-
171126
private function validateAttributes(array $attributes): void
172127
{
173128
$intersect = array_intersect(self::DEPRECATION_ATTRIBUTES, array_column($attributes, 'deprecationType'));
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Api\App\Service;
6+
7+
use Api\App\Exception\RuntimeException;
8+
use Laminas\Stratigility\MiddlewarePipe;
9+
use Mezzio\Middleware\LazyLoadingMiddleware as MezzioLazyLoadingMiddleware;
10+
use Mezzio\Router\Route;
11+
use Mezzio\Router\RouteResult;
12+
use Psr\Http\Message\ServerRequestInterface;
13+
use Psr\Http\Server\RequestHandlerInterface;
14+
use ReflectionClass;
15+
use ReflectionException;
16+
17+
class HandlerService
18+
{
19+
/**
20+
* @throws ReflectionException
21+
*/
22+
public static function fromRequest(ServerRequestInterface $request): ?ReflectionClass
23+
{
24+
$routeResult = $request->getAttribute(RouteResult::class);
25+
if (! $routeResult instanceof RouteResult || $routeResult->isFailure()) {
26+
return null;
27+
}
28+
29+
$matchedRoute = $routeResult->getMatchedRoute();
30+
if (! $matchedRoute instanceof Route) {
31+
return null;
32+
}
33+
34+
$routeMiddleware = $matchedRoute->getMiddleware();
35+
if (
36+
$routeMiddleware instanceof MezzioLazyLoadingMiddleware
37+
|| $routeMiddleware instanceof \Dot\Router\Middleware\LazyLoadingMiddleware
38+
) {
39+
/** @var class-string $routeMiddlewareName */
40+
$routeMiddlewareName = $routeMiddleware->middlewareName;
41+
$reflectionMiddlewareClass = new ReflectionClass($routeMiddlewareName);
42+
if ($reflectionMiddlewareClass->implementsInterface(RequestHandlerInterface::class)) {
43+
return $reflectionMiddlewareClass;
44+
}
45+
} elseif ($routeMiddleware instanceof MiddlewarePipe) {
46+
$reflectionClass = new ReflectionClass($routeMiddleware);
47+
$middlewarePipeline = $reflectionClass->getProperty('pipeline')->getValue($routeMiddleware);
48+
for ($middlewarePipeline->rewind(); $middlewarePipeline->valid(); $middlewarePipeline->next()) {
49+
$reflectionMiddlewareClass = new ReflectionClass($middlewarePipeline->current()->middlewareName);
50+
if ($reflectionMiddlewareClass->implementsInterface(RequestHandlerInterface::class)) {
51+
return $reflectionMiddlewareClass;
52+
}
53+
}
54+
} else {
55+
throw new RuntimeException('Invalid route middleware provided.');
56+
}
57+
58+
return null;
59+
}
60+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Core\App\Attribute;
6+
7+
use Attribute;
8+
9+
#[Attribute(Attribute::TARGET_METHOD)]
10+
readonly class Resource implements ResourceInterface
11+
{
12+
/** @param class-string $class */
13+
public function __construct(
14+
public string $class,
15+
public string $identifier = 'uuid',
16+
public string $placeholder = 'uuid',
17+
) {
18+
}
19+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Core\App\Attribute;
6+
7+
interface ResourceInterface
8+
{
9+
}

src/Core/src/App/src/ConfigProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66

77
use Api\App\Factory\EntityListenerResolverFactory;
88
use Core\App\Entity\EntityListenerResolver;
9+
use Core\App\Middleware\ResourceProviderMiddleware;
910
use Doctrine\ORM\EntityManager;
1011
use Doctrine\ORM\EntityManagerInterface;
1112
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
1213
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
1314
use Dot\Cache\Adapter\ArrayAdapter;
1415
use Dot\Cache\Adapter\FilesystemAdapter;
16+
use Dot\DependencyInjection\Factory\AttributedServiceFactory;
1517
use Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType;
1618
use Ramsey\Uuid\Doctrine\UuidBinaryType;
1719
use Ramsey\Uuid\Doctrine\UuidType;
@@ -36,6 +38,7 @@ public function getDependencies(): array
3638
'factories' => [
3739
'doctrine.entity_manager.orm_default' => EntityManagerFactory::class,
3840
EntityListenerResolver::class => EntityListenerResolverFactory::class,
41+
ResourceProviderMiddleware::class => AttributedServiceFactory::class,
3942
],
4043
'aliases' => [
4144
EntityManager::class => 'doctrine.entity_manager.orm_default',

src/Core/src/App/src/Message.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Message
3232
public const RESET_PASSWORD_USED = 'Password reset request for hash: \'%s\' is invalid (used).';
3333
public const RESET_PASSWORD_VALID = 'Password reset request for hash: \'%s\' is valid.';
3434
public const RESOURCE_NOT_ALLOWED = 'You are not allowed to access this resource.';
35+
public const RESOURCE_NOT_FOUND = '%s not found.';
3536
public const RESTRICTION_DEPRECATION = 'Cannot use both `%s` and `%s` attributes on the same object.';
3637
public const RESTRICTION_IMAGE = 'File must be an image (jpg, png).';
3738
public const RESTRICTION_ROLES = 'User accounts must have at least one role.';
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Core\App\Middleware;
6+
7+
use Api\App\Exception\NotFoundException;
8+
use Api\App\Service\HandlerService;
9+
use Core\App\Attribute\Resource;
10+
use Core\App\Message;
11+
use Doctrine\ORM\EntityManagerInterface;
12+
use Dot\DependencyInjection\Attribute\Inject;
13+
use Psr\Http\Message\ResponseInterface;
14+
use Psr\Http\Message\ServerRequestInterface;
15+
use Psr\Http\Server\MiddlewareInterface;
16+
use Psr\Http\Server\RequestHandlerInterface;
17+
use ReflectionClass;
18+
use ReflectionException;
19+
20+
use function array_pop;
21+
use function count;
22+
use function explode;
23+
use function sprintf;
24+
25+
readonly class ResourceProviderMiddleware implements MiddlewareInterface
26+
{
27+
#[Inject(EntityManagerInterface::class)]
28+
public function __construct(
29+
private EntityManagerInterface $entityManager,
30+
) {
31+
}
32+
33+
/**
34+
* @throws NotFoundException
35+
* @throws ReflectionException
36+
*/
37+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
38+
{
39+
$reflectionHandler = HandlerService::fromRequest($request);
40+
if (! $reflectionHandler instanceof ReflectionClass) {
41+
return $handler->handle($request);
42+
}
43+
44+
$reflectionAttributes = $reflectionHandler->getMethod('handle')->getAttributes(Resource::class);
45+
if (count($reflectionAttributes) === 0) {
46+
return $handler->handle($request);
47+
}
48+
49+
/** @var Resource $resource */
50+
$resource = $reflectionAttributes[0]->newInstance();
51+
52+
$entity = $this->entityManager->getRepository($resource->class)->findOneBy([
53+
$resource->identifier => $request->getAttribute($resource->placeholder),
54+
]);
55+
if ($entity === null) {
56+
$entity = explode('\\', $resource->class);
57+
throw new NotFoundException(
58+
sprintf(Message::RESOURCE_NOT_FOUND, array_pop($entity)),
59+
);
60+
}
61+
62+
return $handler->handle($request->withAttribute(Resource::class, $entity));
63+
}
64+
}

0 commit comments

Comments
 (0)