checkParameterForEntity()   B
last analyzed

Complexity

Conditions 7
Paths 3

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
dl 0
loc 17
rs 8.8333
c 1
b 0
f 0
cc 7
nc 3
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Orm\Middleware;
15
16
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use Valkyrja\Container\Contract\Container;
18
use Valkyrja\Http\Message\Enum\StatusCode;
19
use Valkyrja\Http\Message\Request\Contract\ServerRequest;
20
use Valkyrja\Http\Message\Response\Contract\Response;
21
use Valkyrja\Http\Middleware\Contract\RouteMatchedMiddleware;
22
use Valkyrja\Http\Middleware\Handler\Contract\RouteMatchedHandler;
23
use Valkyrja\Http\Routing\Data\Contract\Parameter;
24
use Valkyrja\Http\Routing\Data\Contract\Route;
25
use Valkyrja\Orm\Contract\Manager;
26
use Valkyrja\Orm\Data\EntityCast;
27
use Valkyrja\Orm\Data\Value;
28
use Valkyrja\Orm\Data\Where;
29
use Valkyrja\Orm\Entity\Contract\Entity;
30
use Valkyrja\View\Factory\Contract\ResponseFactory;
31
32
use function is_a;
33
use function is_int;
34
use function is_string;
35
36
/**
37
 * Class EntityRouteMatchedMiddleware.
38
 *
39
 * @author Melech Mizrachi
40
 */
41
class EntityRouteMatchedMiddleware implements RouteMatchedMiddleware
42
{
43
    /**
44
     * The errors template directory.
45
     *
46
     * @var string
47
     */
48
    protected string $errorsTemplateDir = 'errors';
49
50
    public function __construct(
51
        protected Container $container,
52
        protected Manager $orm,
53
        protected ResponseFactory $responseFactory,
54
    ) {
55
    }
56
57
    /**
58
     * @inheritDoc
59
     */
60
    #[Override]
61
    public function routeMatched(ServerRequest $request, Route $route, RouteMatchedHandler $handler): Route|Response
62
    {
63
        $routeOrResponse = $this->checkRouteForEntities($route);
64
65
        if ($routeOrResponse instanceof Response) {
66
            return $routeOrResponse;
67
        }
68
69
        return $handler->routeMatched($request, $routeOrResponse);
70
    }
71
72
    /**
73
     * Check route for entities.
74
     *
75
     * @param Route $route The route
76
     *
77
     * @return Response|Route
78
     */
79
    protected function checkRouteForEntities(Route $route): Response|Route
80
    {
81
        $parameters = $route->getParameters();
82
        $dispatch   = $route->getDispatch();
83
84
        if ($parameters !== []) {
85
            $arguments    = $dispatch->getArguments() ?? [];
86
            $dependencies = $dispatch->getDependencies() ?? [];
87
88
            // Iterate through the params
89
            foreach ($parameters as $index => $parameter) {
90
                $response = $this->checkParameterForEntity((int) $index, $parameter, $dependencies, $arguments);
91
92
                if ($response !== null) {
93
                    return $response;
94
                }
95
            }
96
97
            $route = $route->withDispatch($dispatch->withArguments($arguments));
98
            $route = $route->withDispatch($dispatch->withDependencies($dependencies));
99
        }
100
101
        return $route;
102
    }
103
104
    /**
105
     * Check a route's parameters for an entity.
106
     *
107
     * @param int                     $index        The index
108
     * @param Parameter               $parameter    The parameter
109
     * @param class-string[]          $dependencies The route dependencies
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string[] at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string[].
Loading history...
110
     * @param array<array-key, mixed> $arguments    The arguments
111
     *
112
     * @return Response|null
113
     */
114
    protected function checkParameterForEntity(int $index, Parameter $parameter, array &$dependencies, array &$arguments): Response|null
115
    {
116
        $type = $parameter->getCast()->type ?? null;
117
118
        if ($type !== null && is_a($type, Entity::class, true)) {
119
            /** @var mixed $match */
120
            $match = $arguments[$index];
121
122
            if ((is_string($match) && $match !== '') || is_int($match) || $match instanceof Entity) {
123
                /** @var Entity|non-empty-string|int $match */
124
                return $this->findAndSetEntityFromParameter($parameter, $type, $dependencies, $match);
125
            }
126
127
            return $this->getBadRequestResponse($type, $match);
128
        }
129
130
        return null;
131
    }
132
133
    /**
134
     * Try to find and set a route's entity dependency.
135
     *
136
     * @param Parameter                   $parameter    The parameter
137
     * @param class-string<Entity>        $entityName   The entity class name
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Entity> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Entity>.
Loading history...
138
     * @param class-string[]              $dependencies The dependencies
139
     * @param Entity|non-empty-string|int $value        The value
140
     *
141
     * @return Response|null
142
     */
143
    protected function findAndSetEntityFromParameter(
144
        Parameter $parameter,
145
        string $entityName,
146
        array &$dependencies,
147
        Entity|string|int &$value
148
    ): Response|null {
149
        if ($value instanceof Entity) {
150
            return null;
151
        }
152
153
        // Attempt to get the entity from the ORM repository
154
        $entity = $this->findEntityFromParameter($parameter, $entityName, $value);
155
156
        if ($entity === null) {
157
            return $this->getNotFoundResponse($entityName, $value);
158
        }
159
160
        // Replace the route match with this entity
161
        /** @param-out Entity $value */
162
        $value = $entity;
163
164
        $updatedDependencies = [];
165
166
        foreach ($dependencies as $dependency) {
167
            if ($dependency !== $entityName) {
168
                $updatedDependencies[] = $dependency;
169
            }
170
        }
171
172
        $dependencies = $updatedDependencies;
173
174
        return null;
175
    }
176
177
    /**
178
     * Try to find a route's entity dependency.
179
     *
180
     * @param Parameter            $parameter  The parameter
181
     * @param class-string<Entity> $entityName The entity class name
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Entity> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Entity>.
Loading history...
182
     * @param non-empty-string|int $value      The value
183
     *
184
     * @return Entity|null
185
     */
186
    protected function findEntityFromParameter(
187
        Parameter $parameter,
188
        string $entityName,
189
        string|int $value
190
    ): Entity|null {
191
        $cast          = $parameter->getCast();
192
        $repository    = $this->orm->createRepository($entityName);
193
        $field         = null;
194
        $relationships = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $relationships is dead and can be removed.
Loading history...
195
196
        if ($cast instanceof EntityCast) {
197
            $relationships = $cast->relationships ?? [];
198
            $field         = $cast->column;
199
        }
200
201
        // If there is a field specified to use
202
        if ($field !== null && $field !== '') {
203
            return $repository->findBy(new Where(new Value(name: $field, value: $value)));
204
        }
205
206
        return $repository->find($value);
207
    }
208
209
    /**
210
     * Response for when the entity was not found with the given value.
211
     *
212
     * @param class-string<Entity> $entity The entity not found
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Entity> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Entity>.
Loading history...
213
     * @param mixed                $value  [optional] The value used to check for the entity
214
     *
215
     * @return Response
216
     */
217
    protected function getNotFoundResponse(string $entity, mixed $value): Response
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

217
    protected function getNotFoundResponse(string $entity, /** @scrutinizer ignore-unused */ mixed $value): Response

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $entity is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

217
    protected function getNotFoundResponse(/** @scrutinizer ignore-unused */ string $entity, mixed $value): Response

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
218
    {
219
        return $this->responseFactory
220
            ->createResponseFromView(
221
                template: "$this->errorsTemplateDir/" . ((string) StatusCode::NOT_FOUND->value),
222
                statusCode: StatusCode::NOT_FOUND,
223
            );
224
    }
225
226
    /**
227
     * Response for when bad data has been provided to match for the entity.
228
     *
229
     * @param class-string<Entity> $entity The entity with bad data
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Entity> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Entity>.
Loading history...
230
     * @param mixed                $value  [optional] The bad data value
231
     *
232
     * @return Response
233
     */
234
    protected function getBadRequestResponse(string $entity, mixed $value): Response
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

234
    protected function getBadRequestResponse(string $entity, /** @scrutinizer ignore-unused */ mixed $value): Response

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $entity is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

234
    protected function getBadRequestResponse(/** @scrutinizer ignore-unused */ string $entity, mixed $value): Response

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235
    {
236
        return $this->responseFactory
237
            ->createResponseFromView(
238
                template: "$this->errorsTemplateDir/" . ((string) StatusCode::BAD_REQUEST->value),
239
                statusCode: StatusCode::BAD_REQUEST,
240
            );
241
    }
242
}
243