Passed
Push — master ( 6215ec...1255dc )
by Darío
01:47
created

Router::addZendRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * DronePHP (http://www.dronephp.com)
4
 *
5
 * @link      http://github.com/Pleets/DronePHP
6
 * @copyright Copyright (c) 2016-2018 Pleets. (http://www.pleets.org)
7
 * @license   http://www.dronephp.com/license
8
 * @author    Darío Rivera <[email protected]>
9
 */
10
11
namespace Drone\Mvc;
12
13
use Drone\Mvc\Exception;
14
15
/**
16
 * Router class
17
 *
18
 * This class build the route and calls to specific application controller
19
 */
20
class Router
21
{
22
    /**
23
     * List of routes
24
     *
25
     * @var array
26
     */
27
    private $routes = [];
28
29
    /**
30
     * The Identifiers builds the route
31
     *
32
     * @var array
33
     */
34
    private $identifiers;
35
36
    /**
37
     * Default identifiers
38
     *
39
     * @var array
40
     */
41
    private $defaults;
42
43
    /**
44
     * Controller instance
45
     *
46
     * @var AbstractController
47
     */
48
    private $controller;
49
50
    /**
51
     * Indicates how the class name could be matched
52
     *
53
     * @var callable
54
     */
55
    private $classNameBuilder;
56
57
    /**
58
     * Zend\Router implementation
59
     *
60
     * @var \Zend\Router\SimpleRouteStack
61
     */
62
    private $zendRouter;
63
64
    /**
65
     * Returns all routes built
66
     *
67
     * @return array
68
     */
69
    public function getRoutes()
70
    {
71
        return $this->routes;
72
    }
73
74
    /**
75
     * Returns all identifiers
76
     *
77
     * @return array
78
     */
79
    public function getIdentifiers()
80
    {
81
        return $this->identifiers;
82
    }
83
84
    /**
85
     * Returns default identifiers
86
     *
87
     * @return array
88
     */
89
    public function getDefaults()
90
    {
91
        return $this->defaults;
92
    }
93
94
    /**
95
     * Returns the controller instance
96
     *
97
     * @throws \RuntimeException
98
     *
99
     * @return AbstractController
100
     */
101
    public function getController()
102
    {
103
        if (is_null($this->controller))
104
            throw new \RuntimeException("No controller matched, try to match first.");
105
106
        return $this->controller;
107
    }
108
109
    /**
110
     * Returns the class name builder function
111
     *
112
     * @return callable
113
     */
114
    public function getClassNameBuilder()
115
    {
116
        return $this->classNameBuilder;
117
    }
118
119
    /**
120
     * Returns the Zend\Router\SimpleRouteStack object
121
     *
122
     * @return \Zend\Router\SimpleRouteStack
123
     */
124
    public function getZendRouter()
125
    {
126
        return $this->zendRouter;
127
    }
128
129
    /**
130
     * Sets identifiers
131
     *
132
     * @param string $module
133
     * @param string $controller
134
     * @param string $view
135
     *
136
     * @return null
137
     */
138
    public function setIdentifiers($module, $controller, $view)
139
    {
140
        $identifiers = ["module" => $module, "controller" => $controller, "view" => $view];
141
142
        foreach ($identifiers as $key => $identifier)
143
        {
144
            if (!is_string($identifier))
145
                throw new \InvalidArgumentException("Invalid type given for '$key'. String expected.");
146
        }
147
148
        $this->identifiers = [
149
            "module"     => $module,
150
            "controller" => $controller,
151
            "view"       => $view
152
        ];
153
    }
154
155
    /**
156
     * Sets default identifiers
157
     *
158
     * @param string $module
159
     * @param string $controller
160
     * @param string $view
161
     *
162
     * @return null
163
     */
164
    public function setDefaults($module, $controller, $view)
165
    {
166
        $identifiers = ["module" => $module, "controller" => $controller, "view" => $view];
167
168
        foreach ($identifiers as $key => $identifier)
169
        {
170
            if (!is_string($identifier))
171
                throw new \InvalidArgumentException("Invalid type given for '$key'. String expected.");
172
        }
173
174
        $this->defaults = [
175
            "module"     => $module,
176
            "controller" => $controller,
177
            "view"       => $view
178
        ];
179
    }
180
181
    /**
182
     * Sets the class name builder function
183
     *
184
     * @param callable $builder
185
     *
186
     * @return null
187
     */
188
    public function setClassNameBuilder(callable $builder)
189
    {
190
        $this->classNameBuilder = $builder;
191
    }
192
193
    /**
194
     * Constructor
195
     *
196
     * @param array $routes
197
     */
198
    public function __construct(Array $routes = [])
199
    {
200
        if (count($routes))
201
        {
202
            foreach ($routes as $route)
203
            {
204
                $this->addRoute($route);
205
            }
206
        }
207
208
        # schema for identifiers
209
        $this->identifiers = [
210
            "module"     => '',
211
            "controller" => '',
212
            "view"       => ''
213
        ];
214
215
        $this->defaults = [
216
            "module"     => '',
217
            "controller" => '',
218
            "view"       => ''
219
        ];
220
221
        # default class name builder
222
        $this->setClassNameBuilder(function($module, $class) {
223
            return "\\$module\\$class";
224
        });
225
226
        $this->zendRouter = new \Zend\Router\SimpleRouteStack();
227
    }
228
229
    /**
230
     * Builds the current route and calls the controller
231
     *
232
     * @throws Exception\PageNotFoundException
233
     * @throws Exception\RouteNotFoundException
234
     * @throws \LogicException
235
     *
236
     * @return  null
237
     */
238
    public function match()
239
    {
240
        if (!is_callable($this->classNameBuilder))
241
            throw \LogicException("No class name builder found");
0 ignored issues
show
Bug introduced by
The function LogicException was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

241
            throw /** @scrutinizer ignore-call */ \LogicException("No class name builder found");
Loading history...
242
243
        /*
244
         *  Key value pairs builder:
245
         *  Searches for the pattern /var1/value1/var2/value2 and converts it to  var1 => value1, var2 => value2
246
         */
247
        if (array_key_exists('params', $_GET))
248
        {
249
            $keypairs = $this->parseRequestParameters($_GET["params"]);
0 ignored issues
show
Bug introduced by
The method parseRequestParameters() does not exist on Drone\Mvc\Router. ( Ignorable by Annotation )

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

249
            /** @scrutinizer ignore-call */ 
250
            $keypairs = $this->parseRequestParameters($_GET["params"]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
250
            unset($_GET["params"]);
251
            $_GET = array_merge($_GET, $keypairs);
252
        }
253
254
        /*
255
         *  Route builder:
256
         *  The route is built by default from the URL as follow
257
         *  www.example.com/module/controller/view
258
         */
259
260
        $match = false;
261
262
        if (count($this->routes))
263
        {
264
            foreach ($this->routes as $key => $route)
265
            {
266
                if
267
                (
268
                    $route["module"]     == $this->identifiers["module"]     &&
269
                    $route["controller"] == $this->identifiers["controller"] &&
270
                    $route["view"]       == $this->identifiers["view"]
271
                )
272
                {
273
                    $module     = $route["module"];
274
                    $controller = $route["controller"];
275
                    $view       = $route["view"];
276
277
                    $match = true;
278
                    break;
279
                }
280
            }
281
        }
282
283
        if (count($this->defaults) && !$match)
284
        {
285
            if
286
            (
287
                !empty($this->defaults["module"])     &&
288
                !empty($this->defaults["controller"]) &&
289
                !empty($this->defaults["view"])
290
            )
291
            {
292
                $module     = $this->defaults["module"];
293
                $controller = $this->defaults["controller"];
294
                $view       = $this->defaults["view"];
295
296
                $match = true;
297
            }
298
        }
299
300
        if (!$match)
301
            throw new Exception\RouteNotFoundException("The route has not been matched");
302
303
        $fqn_controller = call_user_func($this->classNameBuilder, $module, $controller);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $controller does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $module does not seem to be defined for all execution paths leading up to this point.
Loading history...
304
305
        if (class_exists($fqn_controller))
306
        {
307
            try {
308
                $this->controller = new $fqn_controller;
309
            }
310
            # change context, in terms of Router MethodNotFoundException or
311
            # PrivateMethodExecutionException is a PageNotfoundException
312
            catch (Exception\MethodNotFoundException $e)
313
            {
314
                throw new Exception\PageNotFoundException($e->getMessage(), $e->getCode(), $e);
315
            }
316
            catch (Exception\PrivateMethodExecutionException $e)
317
            {
318
                throw new Exception\PageNotFoundException($e->getMessage(), $e->getCode(), $e);
319
            }
320
321
            # in controller terms, a view is a method
322
            $this->controller->setMethod($view);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $view does not seem to be defined for all execution paths leading up to this point.
Loading history...
323
        }
324
        else
325
            throw new Exception\ControllerNotFoundException("The control class '$fqn_controller' does not exists!");
326
    }
327
328
    /**
329
     * Execute the method matched in the controller
330
     *
331
     * @return mixed
332
     */
333
    public function run()
334
    {
335
        return $this->controller->execute();
336
    }
337
338
    /**
339
     * Adds a new route to router
340
     *
341
     * @param Array $route
342
     *
343
     * @throws LogicException
344
     *
345
     * @return null
346
     */
347
    public function addRoute(array $route)
348
    {
349
        $key = array_keys($route);
350
351
        if (count($key) > 1)
352
            throw new \InvalidArgumentException("So many keys in a simple route");
353
354
        $key = array_shift($key);
355
356
        $identifiers = ["module", "controller", "view"];
357
358
        foreach ($identifiers as $identifier)
359
        {
360
            if (!array_key_exists($identifier, $route[$key]))
361
                throw new \InvalidArgumentException("The identifier '$identifier' does not exists in the route");
362
363
            if (!is_string($route[$key][$identifier]))
364
                throw new \InvalidArgumentException("Invalid type given for '$identifier'. String expected.");
365
        }
366
367
        if (array_key_exists($key, $this->routes))
368
            throw new \LogicException("The key '$key' was already defined as a route");
369
370
        $this->routes = array_merge($this->routes, $route);
371
    }
372
373
    /**
374
     * Adds a new route to router
375
     *
376
     * @param string $name
377
     * @param Zend\Router\Http\RouteInterface $route
0 ignored issues
show
Bug introduced by
The type Drone\Mvc\Zend\Router\Http\RouteInterface was not found. Did you mean Zend\Router\Http\RouteInterface? If so, make sure to prefix the type with \.
Loading history...
378
     *
379
     * @throws LogicException
380
     *
381
     * @return null
382
     */
383
    public function addZendRoute($name, \Zend\Router\Http\RouteInterface $route)
384
    {
385
        $this->zendRouter->addRoute($name, $route);
386
    }
387
388
    /**
389
     * Parse key value pairs from a string
390
     *
391
     * Searches for the pattern /var1/value1/var2/value2 and converts it to
392
     *
393
     * var1 => value1
394
     * var2 => value2
395
     *
396
     * @param string $unparsed
397
     *
398
     * @return array
399
     */
400
    private function parseKeyValuePairsFrom($unparsed)
0 ignored issues
show
Unused Code introduced by
The method parseKeyValuePairsFrom() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
401
    {
402
        $params = explode("/", $unparsed);
403
404
        $vars = $values = [];
405
406
        $i = 1;
407
        foreach ($params as $item)
408
        {
409
            if ($i % 2 != 0)
410
                $vars[] = $item;
411
            else
412
                $values[] = $item;
413
            $i++;
414
        }
415
416
        $vars_count = count($vars);
417
418
        $result = [];
419
420
        for ($i = 0; $i < $vars_count; $i++)
421
        {
422
            if (array_key_exists($i, $values))
423
                $result[$vars[$i]] = $values[$i];
424
            else
425
                $result[$vars[$i]] = '';
426
        }
427
428
        return $result;
429
    }
430
}