Processor   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 116
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 16
eloc 28
dl 0
loc 116
rs 10
c 0
b 0
f 0

4 Methods

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