Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
60 / 60
100.00% covered (success)
100.00%
26 / 26
CRAP
100.00% covered (success)
100.00%
1 / 1
Router
100.00% covered (success)
100.00%
60 / 60
100.00% covered (success)
100.00%
26 / 26
41
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 addRoute
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addRoutes
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 name
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUrl
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addControllerParams
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 appendControllerParams
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getControllerParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasControllerParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeControllerParams
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getRoutes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRouteMatch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasRoute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRouteParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasRouteParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getController
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasController
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getControllerClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCli
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isHttp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 route
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
11
 noRouteFound
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Router;
15
16use Closure;
17use ReflectionException;
18
19/**
20 * Pop router class
21 *
22 * @category   Pop
23 * @package    Pop\Router
24 * @author     Nick Sagona, III <dev@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    4.2.0
28 */
29class Router
30{
31
32    /**
33     * Route match object
34     * @var ?Match\MatchInterface
35     */
36    protected ?Match\MatchInterface $routeMatch = null;
37
38    /**
39     * Controller object
40     * @var mixed
41     */
42    protected mixed $controller = null;
43
44    /**
45     * Action
46     * @var mixed
47     */
48    protected mixed $action = null;
49
50    /**
51     * Controller class
52     * @var ?string
53     */
54    protected ?string $controllerClass = null;
55
56    /**
57     * Constructor
58     *
59     * Instantiate the router object
60     *
61     * @param  ?array               $routes
62     * @param  ?Match\AbstractMatch $match
63     */
64    public function __construct(?array $routes = null, ?Match\AbstractMatch $match = null)
65    {
66        if ($match !== null) {
67            $this->routeMatch = $match;
68        } else {
69            $this->routeMatch = ((stripos(php_sapi_name(), 'cli') !== false) &&
70                (stripos(php_sapi_name(), 'server') === false)) ?
71                new Match\Cli() : new Match\Http();
72        }
73
74        if ($routes !== null) {
75            $this->addRoutes($routes);
76        }
77    }
78
79    /**
80     * Add a route
81     *
82     * @param  string $route
83     * @param  mixed  $controller
84     * @return static
85     */
86    public function addRoute(string $route, mixed $controller): static
87    {
88        $this->routeMatch->addRoute($route, $controller);
89        return $this;
90    }
91
92    /**
93     * Add multiple controller routes
94     *
95     * @param  array $routes
96     * @return static
97     */
98    public function addRoutes(array $routes): static
99    {
100        $this->routeMatch->addRoutes($routes);
101        return $this;
102    }
103
104    /**
105     * Add a route name
106     *
107     * @param  string $routeName
108     * @return Router
109     */
110    public function name(string $routeName): static
111    {
112        $this->routeMatch->name($routeName);
113        return $this;
114    }
115
116    /**
117     * Has a route name
118     *
119     * @param  string $routeName
120     * @return bool
121     */
122    public function hasName(string $routeName): bool
123    {
124        return $this->routeMatch->hasName($routeName);
125    }
126
127    /**
128     * Get URL for the named route
129     *
130     * @param  string $routeName
131     * @param  mixed  $params
132     * @param  bool   $fqdn
133     * @throws Exception
134     * @return string
135     */
136    public function getUrl(string $routeName, mixed $params = null, bool $fqdn = false): string
137    {
138        if (!$this->isHttp()) {
139            throw new Exception('Error: The route is not HTTP.');
140        }
141        return $this->routeMatch->getUrl($routeName, $params, $fqdn);
142    }
143
144    /**
145     * Add controller params to be passed into a new controller instance
146     *
147     * @param  string $controller
148     * @param  mixed  $params
149     * @return static
150     */
151    public function addControllerParams(string $controller, mixed $params): static
152    {
153        $this->routeMatch->addControllerParams($controller, $params);
154        return $this;
155    }
156
157    /**
158     * Append controller params to be passed into a new controller instance
159     *
160     * @param  string $controller
161     * @param  mixed  $params
162     * @return static
163     */
164    public function appendControllerParams(string $controller, mixed $params): static
165    {
166        $this->routeMatch->appendControllerParams($controller, $params);
167        return $this;
168    }
169
170    /**
171     * Get the params assigned to the controller
172     *
173     * @param  string $controller
174     * @return mixed
175     */
176    public function getControllerParams(string $controller): mixed
177    {
178        return $this->routeMatch->getControllerParams($controller);
179    }
180
181    /**
182     * Determine if the controller has params
183     *
184     * @param  string $controller
185     * @return bool
186     */
187    public function hasControllerParams(string $controller): bool
188    {
189        return $this->routeMatch->hasControllerParams($controller);
190    }
191
192    /**
193     * Remove controller params
194     *
195     * @param  string $controller
196     * @return static
197     */
198    public function removeControllerParams(string $controller): static
199    {
200        $this->routeMatch->removeControllerParams($controller);
201        return $this;
202    }
203
204    /**
205     * Get routes
206     *
207     * @return array
208     */
209    public function getRoutes(): array
210    {
211        return $this->routeMatch->getRoutes();
212    }
213
214    /**
215     * Get route match object
216     *
217     * @return Match\MatchInterface
218     */
219    public function getRouteMatch(): Match\MatchInterface
220    {
221        return $this->routeMatch;
222    }
223
224    /**
225     * Determine if there is a route match
226     *
227     * @return bool
228     */
229    public function hasRoute(): bool
230    {
231        return $this->routeMatch->hasRoute();
232    }
233
234    /**
235     * Get the params discovered from the route
236     *
237     * @return array
238     */
239    public function getRouteParams(): array
240    {
241        return $this->routeMatch->getRouteParams();
242    }
243
244    /**
245     * Determine if the route has params
246     *
247     * @return bool
248     */
249    public function hasRouteParams(): bool
250    {
251        return $this->routeMatch->hasRouteParams();
252    }
253
254    /**
255     * Get the current controller object
256     *
257     * @return mixed
258     */
259    public function getController(): mixed
260    {
261        return $this->controller;
262    }
263
264    /**
265     * Determine if the router has a controller
266     *
267     * @return bool
268     */
269    public function hasController(): bool
270    {
271        return ($this->controller !== null);
272    }
273
274    /**
275     * Get the action
276     *
277     * @return mixed
278     */
279    public function getAction(): mixed
280    {
281        return $this->action;
282    }
283
284    /**
285     * Determine if the router has an action
286     *
287     * @return bool
288     */
289    public function hasAction(): bool
290    {
291        return ($this->action !== null);
292    }
293
294    /**
295     * Get the current controller class name
296     *
297     * @return string
298     */
299    public function getControllerClass(): string
300    {
301        return $this->controllerClass;
302    }
303
304    /**
305     * Determine if the route is CLI
306     *
307     * @return bool
308     */
309    public function isCli(): bool
310    {
311        return ($this->routeMatch instanceof Match\Cli);
312    }
313
314    /**
315     * Determine if the route is HTTP
316     *
317     * @return bool
318     */
319    public function isHttp(): bool
320    {
321        return ($this->routeMatch instanceof Match\Http);
322    }
323
324    /**
325     * Prepare routes
326     *
327     * @return static
328     */
329    public function prepare(): static
330    {
331        $this->routeMatch->prepare();
332        return $this;
333    }
334
335    /**
336     * Route to the correct controller
337     *
338     * @param  ?string $forceRoute
339     * @throws Exception|ReflectionException
340     * @return void
341     */
342    public function route(?string $forceRoute = null): void
343    {
344        if ($this->routeMatch->match($forceRoute)) {
345            if ($this->routeMatch->hasController()) {
346                $controller = $this->routeMatch->getController();
347
348                if ($controller instanceof Closure) {
349                    $this->controllerClass = 'Closure';
350                    $this->controller      = $controller;
351                } else if (class_exists($controller)) {
352                    $this->controllerClass = $controller;
353                    $controllerParams      = null;
354
355                    if ($this->routeMatch->hasControllerParams($controller)) {
356                        $controllerParams = $this->routeMatch->getControllerParams($controller);
357                    } else if ($this->routeMatch->hasControllerParams('*')) {
358                        $controllerParams = $this->routeMatch->getControllerParams('*');
359                    }
360
361                    if ($controllerParams !== null) {
362                        $this->controller = (new \ReflectionClass($controller))->newInstanceArgs($controllerParams);
363                    } else {
364                        $this->controller = new $controller();
365                    }
366
367                    if (!($this->controller instanceof \Pop\Controller\ControllerInterface)) {
368                        throw new Exception('Error: The controller must be an instance of Pop\Controller\Interface');
369                    }
370
371                    $action       = $this->routeMatch->getAction();
372                    $this->action = (($action === null) && ($this->routeMatch->isDynamicRoute())) ? 'index' : $action;
373                }
374            }
375        }
376    }
377
378    /**
379     * Method to process if a route was not found
380     *
381     * @param  bool $exit
382     * @return void
383     */
384    public function noRouteFound(bool $exit = true): void
385    {
386        $this->routeMatch->noRouteFound($exit);
387    }
388
389}