This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Vectorface\SnappyRouter\Handler; |
||
4 | |||
5 | use \Exception; |
||
6 | use FastRoute\Dispatcher; |
||
7 | use Vectorface\SnappyRouter\Controller\AbstractController; |
||
8 | use Vectorface\SnappyRouter\Encoder\EncoderInterface; |
||
9 | use Vectorface\SnappyRouter\Encoder\NullEncoder; |
||
10 | use Vectorface\SnappyRouter\Encoder\TwigViewEncoder; |
||
11 | use Vectorface\SnappyRouter\Exception\ResourceNotFoundException; |
||
12 | use Vectorface\SnappyRouter\Request\HttpRequest; |
||
13 | use Vectorface\SnappyRouter\Response\AbstractResponse; |
||
14 | use Vectorface\SnappyRouter\Response\Response; |
||
15 | |||
16 | /** |
||
17 | * Handles MVC requests mapping URIs like /controller/action/param1/param2/... |
||
18 | * to its corresponding controller action. |
||
19 | * @copyright Copyright (c) 2014, VectorFace, Inc. |
||
20 | * @author Dan Bruce <[email protected]> |
||
21 | */ |
||
22 | class ControllerHandler extends PatternMatchHandler |
||
23 | { |
||
24 | /** Options key for the base path */ |
||
25 | const KEY_BASE_PATH = 'basePath'; |
||
26 | /** Options key for the view config */ |
||
27 | const KEY_VIEWS = 'views'; |
||
28 | /** Options key for the view path config */ |
||
29 | const KEY_VIEWS_PATH = 'path'; |
||
30 | |||
31 | /** The current web request */ |
||
32 | protected $request; |
||
33 | /** The current encoder */ |
||
34 | protected $encoder; |
||
35 | /** The current route parameters */ |
||
36 | protected $routeParams; |
||
37 | |||
38 | /** Constants indicating the type of route */ |
||
39 | const MATCHES_NOTHING = 0; |
||
40 | const MATCHES_CONTROLLER = 1; |
||
41 | const MATCHES_ACTION = 2; |
||
42 | const MATCHES_CONTROLLER_AND_ACTION = 3; |
||
43 | const MATCHES_PARAMS = 4; |
||
44 | const MATCHES_CONTROLLER_ACTION_AND_PARAMS = 7; |
||
45 | |||
46 | /** controller route pattern */ |
||
47 | const ROUTE_PATTERN_CONTROLLER = '{controller:[a-zA-Z]\w*}'; |
||
48 | |||
49 | /** action route pattern */ |
||
50 | const ROUTE_PATTERN_ACTION = '{action:[a-zA-Z]\w*}'; |
||
51 | |||
52 | /** URL parameters route pattern */ |
||
53 | const ROUTE_PATTERN_PARAMS = '{params:.+}'; |
||
54 | |||
55 | /** |
||
56 | * Returns true if the handler determines it should handle this request and false otherwise. |
||
57 | * @param string $path The URL path for the request. |
||
58 | * @param array $query The query parameters. |
||
59 | * @param array $post The post data. |
||
60 | * @param string $verb The HTTP verb used in the request. |
||
61 | * @return boolean Returns true if this handler will handle the request and false otherwise. |
||
62 | */ |
||
63 | 29 | public function isAppropriate($path, $query, $post, $verb) |
|
64 | { |
||
65 | // remove the leading base path option if present |
||
66 | 29 | $options = $this->getOptions(); |
|
67 | 29 | $path = $this->extractPathFromBasePath($path, $options); |
|
68 | |||
69 | // extract the controller, action and route parameters if present |
||
70 | // and fall back to defaults when not present |
||
71 | 29 | $controller = 'index'; |
|
72 | 29 | $action = 'index'; |
|
73 | 29 | $this->routeParams = array(); |
|
74 | 29 | $routeInfo = $this->getRouteInfo($verb, $path); |
|
75 | // ensure the path matches at least one of the routes |
||
76 | 29 | if (Dispatcher::FOUND !== $routeInfo[0]) { |
|
77 | 2 | return false; |
|
78 | } |
||
79 | |||
80 | 27 | if ($routeInfo[1] & self::MATCHES_CONTROLLER) { |
|
81 | 27 | $controller = strtolower($routeInfo[2]['controller']); |
|
82 | 27 | if ($routeInfo[1] & self::MATCHES_ACTION) { |
|
83 | 14 | $action = strtolower($routeInfo[2]['action']); |
|
84 | 14 | if ($routeInfo[1] & self::MATCHES_PARAMS) { |
|
85 | 1 | $this->routeParams = explode('/', $routeInfo[2]['params']); |
|
86 | } |
||
87 | } |
||
88 | } |
||
89 | |||
90 | // configure the default view encoder |
||
91 | 27 | $this->configureViewEncoder($options, $controller, $action); |
|
92 | |||
93 | // configure the request object |
||
94 | 26 | $this->request = new HttpRequest( |
|
95 | 26 | ucfirst($controller).'Controller', |
|
96 | 26 | $action.'Action', |
|
97 | 26 | $verb, |
|
98 | 26 | 'php://input' |
|
99 | ); |
||
100 | |||
101 | 26 | $this->request->setQuery($query); |
|
102 | 26 | $this->request->setPost($post); |
|
103 | |||
104 | // return that we will handle this request |
||
105 | 26 | return true; |
|
106 | } |
||
107 | |||
108 | /** |
||
109 | * Performs the actual routing. |
||
110 | * @return string Returns the result of the route. |
||
111 | */ |
||
112 | 12 | public function performRoute() |
|
113 | { |
||
114 | 12 | $controller = null; |
|
115 | 12 | $action = null; |
|
116 | 12 | $this->determineControllerAndAction($controller, $action); |
|
117 | 10 | $response = $this->invokeControllerAction($controller, $action); |
|
118 | 8 | \Vectorface\SnappyRouter\http_response_code($response->getStatusCode()); |
|
119 | 8 | return $this->getEncoder()->encode($response); |
|
120 | } |
||
121 | |||
122 | /** |
||
123 | * Returns a request object extracted from the request details (path, query, etc). The method |
||
124 | * isAppropriate() must have returned true, otherwise this method should return null. |
||
125 | * @return \Vectorface\SnappyRouter\Request\HttpRequest|null Returns a |
||
126 | * Request object or null if this handler is not appropriate. |
||
127 | */ |
||
128 | 18 | public function getRequest() |
|
129 | { |
||
130 | 18 | return $this->request; |
|
131 | } |
||
132 | |||
133 | /** |
||
134 | * Returns the active response encoder. |
||
135 | * @return EncoderInterface Returns the response encoder. |
||
136 | */ |
||
137 | 11 | public function getEncoder() |
|
138 | { |
||
139 | 11 | return $this->encoder; |
|
140 | } |
||
141 | |||
142 | /** |
||
143 | * Sets the encoder to be used by this handler (overriding the default). |
||
144 | * @param EncoderInterface $encoder The encoder to be used. |
||
145 | * @return ControllerHandler Returns $this. |
||
146 | */ |
||
147 | 2 | public function setEncoder(EncoderInterface $encoder) |
|
148 | { |
||
149 | 2 | $this->encoder = $encoder; |
|
150 | 2 | return $this; |
|
151 | } |
||
152 | |||
153 | /** |
||
154 | * Returns the new path with the base path extracted. |
||
155 | * @param string $path The full path. |
||
156 | * @param array $options The array of options. |
||
157 | * @return string Returns the new path with the base path removed. |
||
158 | */ |
||
159 | 29 | protected function extractPathFromBasePath($path, $options) |
|
160 | { |
||
161 | 29 | if (isset($options[self::KEY_BASE_PATH])) { |
|
162 | 14 | $pos = strpos($path, $options[self::KEY_BASE_PATH]); |
|
163 | 14 | View Code Duplication | if (false !== $pos) { |
0 ignored issues
–
show
|
|||
164 | 14 | $path = substr($path, $pos + strlen($options[self::KEY_BASE_PATH])); |
|
165 | } |
||
166 | } |
||
167 | // ensure the path has a leading slash |
||
168 | 29 | if (empty($path) || $path[0] !== '/') { |
|
169 | 14 | $path = '/'.$path; |
|
170 | } |
||
171 | 29 | return $path; |
|
172 | } |
||
173 | |||
174 | /** |
||
175 | * Determines the exact controller instance and action name to be invoked |
||
176 | * by the request. |
||
177 | * @param mixed $controller The controller passed by reference. |
||
178 | * @param mixed $actionName The action name passed by reference. |
||
179 | */ |
||
180 | 12 | private function determineControllerAndAction(&$controller, &$actionName) |
|
181 | { |
||
182 | 12 | $request = $this->getRequest(); |
|
183 | 12 | $this->invokePluginsHook( |
|
184 | 12 | 'beforeControllerSelected', |
|
185 | 12 | array($this, $request) |
|
186 | ); |
||
187 | |||
188 | 12 | $controllerDiKey = $request->getController(); |
|
189 | try { |
||
190 | 12 | $controller = $this->getServiceProvider()->getServiceInstance($controllerDiKey); |
|
191 | 1 | } catch (Exception $e) { |
|
192 | 1 | throw new ResourceNotFoundException(sprintf( |
|
193 | 1 | 'No such controller found "%s".', |
|
194 | 1 | $this->getRequest()->getController() |
|
195 | )); |
||
196 | } |
||
197 | 11 | $actionName = $request->getAction(); |
|
198 | 11 | if (!method_exists($controller, $actionName)) { |
|
199 | 1 | throw new ResourceNotFoundException(sprintf( |
|
200 | 1 | '%s does not have method %s', |
|
201 | 1 | $controllerDiKey, |
|
202 | 1 | $actionName |
|
203 | )); |
||
204 | } |
||
205 | 10 | $this->invokePluginsHook( |
|
206 | 10 | 'afterControllerSelected', |
|
207 | 10 | array($this, $request, $controller, $actionName) |
|
208 | ); |
||
209 | 10 | $controller->initialize($request, $this); |
|
210 | 10 | } |
|
211 | |||
212 | /** |
||
213 | * Invokes the actual controller action and returns the response. |
||
214 | * @param AbstractController $controller The controller to use. |
||
215 | * @param string $action The action to invoke. |
||
216 | * @return AbstractResponse Returns the response from the action. |
||
217 | */ |
||
218 | 10 | protected function invokeControllerAction(AbstractController $controller, $action) |
|
219 | { |
||
220 | 10 | $this->invokePluginsHook( |
|
221 | 10 | 'beforeActionInvoked', |
|
222 | 10 | array($this, $this->getRequest(), $controller, $action) |
|
223 | ); |
||
224 | 10 | $response = $controller->$action($this->routeParams); |
|
225 | 8 | if (null === $response) { |
|
226 | // if the action returns null, we simply render the default view |
||
227 | 2 | $response = array(); |
|
228 | 6 | } elseif (!is_string($response)) { |
|
229 | // if they don't return a string, try to use whatever is returned |
||
230 | // as variables to the view renderer |
||
231 | 1 | $response = (array)$response; |
|
232 | } |
||
233 | |||
234 | // merge the response variables with the existing view context |
||
235 | 8 | if (is_array($response)) { |
|
236 | 3 | $response = array_merge($controller->getViewContext(), $response); |
|
237 | } |
||
238 | |||
239 | // whatever we have as a response needs to be encapsulated in an |
||
240 | // AbstractResponse object |
||
241 | 8 | if (!($response instanceof AbstractResponse)) { |
|
242 | 8 | $response = new Response($response); |
|
243 | } |
||
244 | 8 | $this->invokePluginsHook( |
|
245 | 8 | 'afterActionInvoked', |
|
246 | 8 | array($this, $this->getRequest(), $controller, $action, $response) |
|
247 | ); |
||
248 | 8 | return $response; |
|
249 | } |
||
250 | |||
251 | /** |
||
252 | * Configures the view encoder based on the current options. |
||
253 | * @param array $options The current options. |
||
254 | * @param string $controller The controller to use for the default view. |
||
255 | * @param string $action The action to use for the default view. |
||
256 | */ |
||
257 | 27 | private function configureViewEncoder($options, $controller, $action) |
|
258 | { |
||
259 | // configure the view encoder if they specify a view option |
||
260 | 27 | if (isset($options[self::KEY_VIEWS])) { |
|
261 | 8 | $this->encoder = new TwigViewEncoder( |
|
262 | 8 | $options[self::KEY_VIEWS], |
|
263 | 8 | sprintf('%s/%s.twig', $controller, $action) |
|
264 | ); |
||
265 | } else { |
||
266 | 19 | $this->encoder = new NullEncoder(); |
|
267 | } |
||
268 | 26 | } |
|
269 | |||
270 | /** |
||
271 | * Returns the array of routes. |
||
272 | * @return array The array of routes. |
||
273 | */ |
||
274 | 19 | protected function getRoutes() |
|
275 | { |
||
276 | 19 | $c = self::ROUTE_PATTERN_CONTROLLER; |
|
277 | 19 | $a = self::ROUTE_PATTERN_ACTION; |
|
278 | 19 | $p = self::ROUTE_PATTERN_PARAMS; |
|
279 | return array( |
||
280 | 19 | "/" => self::MATCHES_NOTHING, |
|
281 | 19 | "/$c" => self::MATCHES_CONTROLLER, |
|
282 | 19 | "/$c/" => self::MATCHES_CONTROLLER, |
|
283 | 19 | "/$c/$a" => self::MATCHES_CONTROLLER_AND_ACTION, |
|
284 | 19 | "/$c/$a/" => self::MATCHES_CONTROLLER_AND_ACTION, |
|
285 | 19 | "/$c/$a/$p" => self::MATCHES_CONTROLLER_ACTION_AND_PARAMS |
|
286 | ); |
||
287 | } |
||
288 | } |
||
289 |
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.