Processor::processParameterInRegex()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 9
c 0
b 0
f 0
rs 10
cc 3
nc 2
nop 2
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\Http\Routing\Processor;
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\Http\Routing\Constant\Regex;
0 ignored issues
show
Bug introduced by
The type Valkyrja\Http\Routing\Constant\Regex 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...
18
use Valkyrja\Http\Routing\Data\Contract\ParameterContract;
19
use Valkyrja\Http\Routing\Data\Contract\RouteContract;
20
use Valkyrja\Http\Routing\Processor\Contract\ProcessorContract as Contract;
21
use Valkyrja\Http\Routing\Support\Helpers;
22
use Valkyrja\Http\Routing\Throwable\Exception\InvalidRoutePathException;
23
24
class Processor implements Contract
25
{
26
    /**
27
     * Process a route.
28
     *
29
     * @param RouteContract $route The route
30
     *
31
     * @throws InvalidRoutePathException
32
     */
33
    #[Override]
34
    public function route(RouteContract $route): RouteContract
35
    {
36
        // Set the path to the validated cleaned path (/some/path)
37
        $route = $route->withPath(Helpers::trimPath($route->getPath()));
38
39
        // If this is a dynamic route
40
        if (str_contains($route->getPath(), '{')) {
41
            $route = $this->modifyRegex($route);
42
        }
43
44
        return $route;
45
    }
46
47
    /**
48
     * Create the regex for a route.
49
     *
50
     * @param RouteContract $route The route
51
     *
52
     * @throws InvalidRoutePathException
53
     */
54
    protected function modifyRegex(RouteContract $route): RouteContract
55
    {
56
        // If the regex has already been set then don't do anything
57
        if ($route->getRegex() !== null) {
58
            return $route;
59
        }
60
61
        // Replace all slashes with \/
62
        $regex = str_replace('/', Regex::PATH, $route->getPath());
63
64
        // Iterate through the route's parameters
65
        foreach ($route->getParameters() as $parameter) {
66
            // Validate the parameter
67
            $parameter = $this->processParameterInRegex($parameter, $regex);
68
69
            $regex = $this->replaceParameterNameInRegex($route, $parameter, $regex);
70
        }
71
72
        $regex = Regex::START . $regex . Regex::END;
73
74
        return $route->withRegex($regex);
75
    }
76
77
    /**
78
     * Validate the parameter name exists in the regex.
79
     *
80
     * @param ParameterContract $parameter The parameter
81
     * @param string            $regex     The regex
82
     */
83
    protected function processParameterInRegex(ParameterContract $parameter, string $regex): ParameterContract
84
    {
85
        // If the parameter is optional or the name has a ? affixed to it
86
        if ($parameter->isOptional() || str_contains($regex, $parameter->getName() . '?')) {
87
            // Ensure the parameter is set to optional
88
            return $parameter->withIsOptional(true);
89
        }
90
91
        return $parameter;
92
    }
93
94
    /**
95
     * Replace the parameter name in the route's regex.
96
     *
97
     * @param RouteContract     $route     The route
98
     * @param ParameterContract $parameter The parameter
99
     * @param string            $regex     The regex
100
     *
101
     * @throws InvalidRoutePathException
102
     */
103
    protected function replaceParameterNameInRegex(RouteContract $route, ParameterContract $parameter, string $regex): string
104
    {
105
        // Get whether this parameter is optional
106
        $isOptional = $parameter->isOptional();
107
108
        // Get the replacement for this parameter's name (something like {name} or {name?}
109
        // Prepend \/ if it optional so we can replace the path slash and set it in the
110
        // regex below as a non-capture-optional group
111
        $nameReplacement = ($isOptional ? Regex::PATH : '')
112
            . '{' . $parameter->getName() . ($isOptional ? '?' : '') . '}';
113
114
        // Check if the path doesn't contain the parameter's name replacement
115
        if (! str_contains($regex, $nameReplacement)) {
116
            throw new InvalidRoutePathException("{$route->getPath()} is missing $nameReplacement");
117
        }
118
119
        // If optional we don't want to capture the / before the value
120
        $parameterRegex = ($isOptional ? Regex::START_OPTIONAL_CAPTURE_GROUP : '')
121
            // Start the actual value's capture group
122
            . (! $parameter->shouldCapture() ? Regex::START_NON_CAPTURE_GROUP : Regex::START_CAPTURE_GROUP)
123
            // Add the parameter name only for a capture group (non capture groups won't be captured so we don't need them to be attributed to a param name)
124
            . ($parameter->shouldCapture() ? Regex::START_CAPTURE_GROUP_NAME . $parameter->getName() . Regex::END_CAPTURE_GROUP_NAME : '')
125
            // Set the parameter's regex to match the value
126
            . $parameter->getRegex()
127
            // End the capture group
128
            . ($isOptional ? Regex::END_OPTIONAL_CAPTURE_GROUP : Regex::END_CAPTURE_GROUP);
129
130
        // Replace the {name} or \/{name?} with the finished regex
131
        return str_replace($nameReplacement, $parameterRegex, $regex);
132
    }
133
}
134