SplashUrlNode::walk()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
namespace Mouf\Mvc\Splash\Store;
4
5
use Mouf\Mvc\Splash\Services\SplashRouteInterface;
6
use Mouf\Mvc\Splash\Utils\SplashException;
7
/*
8
 * A SplashUrlNode is a datastructure optimised to navigate all possible URLs known to the application.
9
 * A SplashUrlNode represents all possible routes starting at the current position (just after a / in a URL)
10
 *
11
 * @author David Negrier
12
 */
13
use Mouf\Mvc\Splash\Services\SplashRoute;
14
use Psr\Http\Message\ServerRequestInterface;
15
16
class SplashUrlNode
17
{
18
    /**
19
     * An array of subnodes
20
     * The key is the string to be appended to the URL.
21
     *
22
     * @var array<string, SplashUrlNode>
23
     */
24
    private $children = array();
25
26
    /**
27
     * An array of parameterized subnodes.
28
     *
29
     * @var array<string, SplashUrlNode>
30
     */
31
    private $parameterizedChildren = array();
32
33
    /**
34
     * A list of callbacks (assicated to there HTTP method).
35
     *
36
     * @var array<string, SplashRoute>
37
     */
38
    private $callbacks = array();
39
40
    /**
41
     * A list of callbacks (assicated to there HTTP method) finishing with "*".
42
     *
43
     * @var array<string, SplashRoute>
44
     */
45
    private $wildcardCallbacks = array();
46
47
    public function registerCallback(SplashRouteInterface $callback)
48
    {
49
        $this->addUrl(explode('/', $callback->getUrl()), $callback);
50
    }
51
52
    /**
53
     * Registers a new URL.
54
     * The URL is passed as an array of strings (exploded on /).
55
     *
56
     * @param array<string> $urlParts
57
     */
58
    protected function addUrl(array $urlParts, SplashRouteInterface $callback)
59
    {
60
        if (!empty($urlParts)) {
61
            $key = array_shift($urlParts);
62
63
            if ($key == '*') {
64
                // Wildcard URL
65
                if (!empty($urlParts)) {
66
                    throw new SplashException('Sorry, the URL pattern /foo/*/bar is not supported. The wildcard (*) must be at the end of an URL');
67
                }
68
69
                $httpMethods = $callback->getHttpMethods();
70 View Code Duplication
                if (empty($httpMethods)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
71
                    if (isset($this->wildcardCallbacks[''])) {
72
                        throw new SplashException("An error occured while looking at the list URL managed in Splash. The URL '".$callback->getUrl()."' is associated "
73
                                ."to 2 methods: \$".$callback->getControllerInstanceName().'->'.$callback->getMethodName()." and \$".$this->wildcardCallbacks['']->getControllerInstanceName().'->'.$this->wildcardCallbacks['']->getMethodName());
74
                    }
75
                    $this->wildcardCallbacks[''] = $callback;
76
                } else {
77
                    foreach ($httpMethods as $httpMethod) {
78
                        if (isset($this->wildcardCallbacks[$httpMethod])) {
79
                            throw new SplashException("An error occured while looking at the list URL managed in Splash. The URL '".$callback->getUrl()."' for HTTP method '".$httpMethod."' is associated "
80
                                    ."to 2 methods: \$".$callback->getControllerInstanceName().'->'.$callback->getMethodName()." and \$".$this->wildcardCallbacks[$httpMethod]->getControllerInstanceName().'->'.$this->wildcardCallbacks[$httpMethod]->getMethodName());
81
                        }
82
                        $this->wildcardCallbacks[$httpMethod] = $callback;
83
                    }
84
                }
85
            } elseif (strpos($key, '{') === 0 && strpos($key, '}') === strlen($key) - 1) {
86
                // Parameterized URL element
87
                $varName = substr($key, 1, strlen($key) - 2);
88
89
                if (!isset($this->parameterizedChildren[$varName])) {
90
                    $this->parameterizedChildren[$varName] = new self();
91
                }
92
                $this->parameterizedChildren[$varName]->addUrl($urlParts, $callback);
93
            } else {
94
                // Usual URL element
95
                if (!isset($this->children[$key])) {
96
                    $this->children[$key] = new self();
97
                }
98
                $this->children[$key]->addUrl($urlParts, $callback);
99
            }
100 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101
            $httpMethods = $callback->getHttpMethods();
102
            if (empty($httpMethods)) {
103
                if (isset($this->callbacks[''])) {
104
                    throw new SplashException("An error occured while looking at the list URL managed in Splash. The URL '".$callback->getUrl()."' is associated "
105
                        ."to 2 methods: \$".$callback->getControllerInstanceName().'->'.$callback->getMethodName()." and \$".$this->callbacks['']->getControllerInstanceName().'->'.$this->callbacks['']->getMethodName());
106
                }
107
                $this->callbacks[''] = $callback;
108
            } else {
109
                foreach ($httpMethods as $httpMethod) {
110
                    if (isset($this->callbacks[$httpMethod])) {
111
                        throw new SplashException("An error occured while looking at the list URL managed in Splash. The URL '".$callback->getUrl()."' for HTTP method '".$httpMethod."' is associated "
112
                            ."to 2 methods: \$".$callback->getControllerInstanceName().'->'.$callback->getMethodName()." and \$".$this->callbacks[$httpMethod]->getControllerInstanceName().'->'.$this->callbacks[$httpMethod]->getMethodName());
113
                    }
114
                    $this->callbacks[$httpMethod] = $callback;
115
                }
116
            }
117
        }
118
    }
119
120
    /**
121
     * Walks through the nodes to find the callback associated to the URL.
122
     *
123
     * @param string                 $url
124
     * @param ServerRequestInterface $request
125
     *
126
     * @return SplashRoute
127
     */
128
    public function walk($url, ServerRequestInterface $request)
129
    {
130
        return $this->walkArray(explode('/', $url), $request, array());
131
    }
132
133
    /**
134
     * Walks through the nodes to find the callback associated to the URL.
135
     *
136
     * @param array                  $urlParts
137
     * @param ServerRequestInterface $request
138
     * @param array                  $parameters
139
     * @param SplashRoute            $closestWildcardRoute The last wildcard (*) route encountered while navigating the tree.
140
     *
141
     * @return SplashRoute
142
     *
143
     * @throws SplashException
144
     */
145
    private function walkArray(array $urlParts, ServerRequestInterface  $request, array $parameters, $closestWildcardRoute = null)
146
    {
147
        $httpMethod = $request->getMethod();
148
149
        if (isset($this->wildcardCallbacks[$httpMethod])) {
150
            $closestWildcardRoute = $this->wildcardCallbacks[$httpMethod];
151
            $closestWildcardRoute->setFilledParameters($parameters);
152
        } elseif (isset($this->wildcardCallbacks[''])) {
153
            $closestWildcardRoute = $this->wildcardCallbacks[''];
154
            $closestWildcardRoute->setFilledParameters($parameters);
155
        }
156
157
        if (!empty($urlParts)) {
158
            $key = array_shift($urlParts);
159
            if (isset($this->children[$key])) {
160
                return $this->children[$key]->walkArray($urlParts, $request, $parameters, $closestWildcardRoute);
161
            }
162
163
            foreach ($this->parameterizedChildren as $varName => $splashUrlNode) {
164
                if (isset($parameters[$varName])) {
165
                    throw new SplashException("An error occured while looking at the list URL managed in Splash. In a @URL annotation, the parameter '{$parameters[$varName]}' appears twice. That should never happen");
166
                }
167
                $newParams = $parameters;
168
                $newParams[$varName] = $key;
169
                $result = $this->parameterizedChildren[$varName]->walkArray($urlParts, $request, $newParams, $closestWildcardRoute);
170
                if ($result !== null) {
171
                    return $result;
172
                }
173
            }
174
175
            // If we arrive here, there was no parametrized URL matching our objective
176
            return $closestWildcardRoute;
177
        } else {
178
            if (isset($this->callbacks[$httpMethod])) {
179
                $route = $this->callbacks[$httpMethod];
180
                $route->setFilledParameters($parameters);
181
182
                return $route;
183
            } elseif (isset($this->callbacks[''])) {
184
                $route = $this->callbacks[''];
185
                $route->setFilledParameters($parameters);
186
187
                return $route;
188
            } else {
189
                return $closestWildcardRoute;
190
            }
191
        }
192
    }
193
}
194