Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
191 / 191
100.00% covered (success)
100.00%
45 / 45
CRAP
100.00% covered (success)
100.00%
1 / 1
Application
100.00% covered (success)
100.00%
191 / 191
100.00% covered (success)
100.00%
45 / 45
105
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
12
 bootstrap
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
25
 init
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 autoloader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 router
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 services
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 events
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 modules
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 registerRouter
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 registerServices
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 registerEvents
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 registerModules
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 registerAutoloader
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 module
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 register
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 unregister
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isRegistered
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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
 setService
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getService
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeService
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 on
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 off
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 trigger
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 env
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 environment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isLocal
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDev
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isTesting
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isStaging
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isProduction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDown
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isUp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
7
 __set
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
7
 __get
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 __isset
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
7
 offsetSet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetUnset
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;
15
16use ReflectionException;
17
18/**
19 * Application class
20 *
21 * @category   Pop
22 * @package    Pop
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    4.2.0
27 * @property   $config mixed
28 */
29class Application extends AbstractApplication implements \ArrayAccess
30{
31
32    /**
33     * Application router
34     * @var ?Router\Router
35     */
36    protected ?Router\Router $router = null;
37
38    /**
39     * Service locator
40     * @var ?Service\Locator
41     */
42    protected ?Service\Locator$services = null;
43
44    /**
45     * Event manager
46     * @var ?Event\Manager
47     */
48    protected ?Event\Manager$events = null;
49
50    /**
51     * Module manager
52     * @var ?Module\Manager
53     */
54    protected ?Module\Manager $modules = null;
55
56    /**
57     * Autoloader
58     * @var mixed
59     */
60    protected mixed $autoloader = null;
61
62    /**
63     * Constructor
64     *
65     * Instantiate an application object
66     *
67     * Optional parameters are a service locator instance, a router instance,
68     * an event manager instance or a configuration object or array
69     */
70    public function __construct()
71    {
72        $args       = func_get_args();
73        $autoloader = null;
74        $config     = null;
75
76        foreach ($args as $arg) {
77            $class = (is_object($arg)) ? get_class($arg) : '';
78            if ((stripos($class, 'classload') !== false) || (stripos($class, 'autoload') !== false)) {
79                $autoloader = $arg;
80            } else if ($arg instanceof Router\Router) {
81                $this->registerRouter($arg);
82            } else if ($arg instanceof Service\Locator) {
83                $this->registerServices($arg);
84            } else if ($arg instanceof Event\Manager) {
85                $this->registerEvents($arg);
86            } else if ($arg instanceof Module\Manager) {
87                $this->registerModules($arg);
88            } else if (is_array($arg) || ($arg instanceof \ArrayAccess)) {
89                $config = $arg;
90            }
91        }
92
93        if ($config !== null) {
94            $this->registerConfig($config);
95        }
96
97        $this->bootstrap($autoloader);
98    }
99
100    /**
101     * Bootstrap the application, creating the required objects if they haven't been created yet
102     * and registering with the autoloader, adding routes, services and events
103     *
104     * @param  mixed $autoloader
105     * @throws Exception|Module\Exception|Service\Exception
106     * @return static
107     */
108    public function bootstrap(mixed $autoloader = null): static
109    {
110        if ($autoloader !== null) {
111            $this->registerAutoloader($autoloader);
112        }
113        if ($this->router === null) {
114            $this->registerRouter(new Router\Router());
115        }
116        if ($this->services === null) {
117            $this->registerServices(new Service\Locator());
118        }
119        if ($this->events === null) {
120            $this->registerEvents(new Event\Manager());
121        }
122        if ($this->modules === null) {
123            $this->registerModules(new Module\Manager());
124        }
125
126        // If the autoloader is set and the application config has a
127        // defined prefix and src, register with the autoloader
128        if (($this->autoloader !== null) && isset($this->config['prefix']) &&
129            isset($this->config['src']) && file_exists($this->config['src'])) {
130            // Register as PSR-0
131            if (isset($this->config['psr-0']) && ($this->config['psr-0'])) {
132                $this->autoloader->add($this->config['prefix'], $this->config['src']);
133            // Else, default to PSR-4
134            } else {
135                $this->autoloader->addPsr4($this->config['prefix'], $this->config['src']);
136            }
137        }
138
139        // Set the app name
140        if (!empty($this->config['name'])) {
141            $this->setName($this->config['name']);
142        } else if (!empty(App::name())) {
143            $this->setName(App::name());
144        }
145
146        // Set the app version
147        if (!empty($this->config['version'])) {
148            $this->setVersion($this->config['version']);
149        }
150
151        // If routes are set in the app config, register them with the application
152        if (isset($this->config['routes']) && ($this->router !== null)) {
153            $this->router->addRoutes($this->config['routes']);
154        }
155
156        // If services are set in the app config, register them with the application
157        if (isset($this->config['services']) && ($this->services !== null)) {
158            foreach ($this->config['services'] as $name => $service) {
159                $this->setService($name, $service);
160            }
161        }
162
163        // If events are set in the app config, register them with the application
164        if (isset($this->config['events']) && ($this->events !== null)) {
165            foreach ($this->config['events'] as $event) {
166                if (isset($event['name']) && isset($event['action'])) {
167                    $this->on($event['name'], $event['action'], ((int)$event['priority'] ?? 0));
168                }
169            }
170        }
171
172        // Register application object with App helper class
173        App::set($this);
174
175        return $this;
176    }
177
178    /**
179     * Initialize the application
180     *
181     * @return static
182     */
183    public function init(): static
184    {
185        $this->trigger('app.init');
186        return $this;
187    }
188
189    /**
190     * Get the autoloader object
191     *
192     * @return mixed
193     */
194    public function autoloader(): mixed
195    {
196        return $this->autoloader;
197    }
198
199    /**
200     * Access the application router
201     *
202     * @return Router\Router
203     */
204    public function router(): Router\Router
205    {
206        return $this->router;
207    }
208
209    /**
210     * Get the service locator
211     *
212     * @return Service\Locator
213     */
214    public function services(): Service\Locator
215    {
216        return $this->services;
217    }
218
219    /**
220     * Get the event manager
221     *
222     * @return Event\Manager
223     */
224    public function events(): Event\Manager
225    {
226        return $this->events;
227    }
228
229    /**
230     * Access all application module configs
231     *
232     * @return Module\Manager
233     */
234    public function modules(): Module\Manager
235    {
236        return $this->modules;
237    }
238
239    /**
240     * Register a new router object with the application
241     *
242     * @param  Router\Router $router
243     * @return static
244     */
245    public function registerRouter(Router\Router $router): static
246    {
247        $this->router = $router;
248        Router\Route::setRouter($router);
249        return $this;
250    }
251
252    /**
253     * Register a new service locator object with the application
254     *
255     * @param  Service\Locator $services
256     * @return static
257     */
258    public function registerServices(Service\Locator $services): static
259    {
260        $this->services = $services;
261        return $this;
262    }
263
264    /**
265     * Register a new event manager object with the application
266     *
267     * @param  Event\Manager $events
268     * @return static
269     */
270    public function registerEvents(Event\Manager $events): static
271    {
272        $this->events = $events;
273        return $this;
274    }
275
276    /**
277     * Register a new module manager object with the application
278     *
279     * @param  Module\Manager $modules
280     * @return static
281     */
282    public function registerModules(Module\Manager $modules): static
283    {
284        $this->modules = $modules;
285        return $this;
286    }
287
288    /**
289     * Register the autoloader object with the application
290     *
291     * @param  mixed $autoloader
292     * @throws Exception
293     * @return static
294     */
295    public function registerAutoloader(mixed $autoloader): static
296    {
297        if (!method_exists($autoloader, 'add') || !method_exists($autoloader, 'addPsr4')) {
298            throw new Exception(
299                'Error: The autoloader instance must contain the methods \'add\' and \'addPsr4\', ' .
300                'as with Composer\Autoload\ClassLoader.'
301            );
302        }
303        $this->autoloader = $autoloader;
304        return $this;
305    }
306
307    /**
308     * Access a module object
309     *
310     * @param  string $name
311     * @return ?Module\ModuleInterface
312     */
313    public function module(string $name): ?Module\ModuleInterface
314    {
315        return $this->modules[$name] ?? null;
316    }
317
318    /**
319     * Register a module with the module manager object
320     *
321     * @param  mixed   $module
322     * @param  ?string $name
323     * @throws Module\Exception|Service\Exception
324     * @return static
325     */
326    public function register(mixed $module, ?string $name = null): static
327    {
328        if (!($module instanceof Module\ModuleInterface)) {
329            $module = new Module\Module($module, $this);
330        }
331
332        if ($name !== null) {
333            $module->setName($name);
334        }
335
336        if (!$module->isRegistered()) {
337            $module->register($this);
338        }
339
340        return $this;
341    }
342
343    /**
344     * Unregister a module with the module manager object
345     *
346     * @param  string $name
347     * @return static
348     */
349    public function unregister(string $name): static
350    {
351        unset($this->modules[$name]);
352        return $this;
353    }
354
355    /**
356     * Determine whether a module is registered with the application object
357     *
358     * @param  string $name
359     * @return bool
360     */
361    public function isRegistered(string $name): bool
362    {
363        return $this->modules->isRegistered($name);
364    }
365
366    /**
367     * Add a route
368     *
369     * @param  string $route
370     * @param  mixed  $controller
371     * @return static
372     */
373    public function addRoute(string $route, mixed $controller): static
374    {
375        $this->router->addRoute($route, $controller);
376        return $this;
377    }
378
379    /**
380     * Add routes
381     *
382     * @param  array $routes
383     * @return static
384     */
385    public function addRoutes(array $routes): static
386    {
387        $this->router->addRoutes($routes);
388        return $this;
389    }
390
391    /**
392     * Set a service
393     *
394     * @param  string $name
395     * @param  mixed  $service
396     * @throws Service\Exception
397     * @return static
398     */
399    public function setService(string $name, mixed $service): static
400    {
401        $this->services->set($name, $service);
402        return $this;
403    }
404
405    /**
406     * Get a service
407     *
408     * @param  string $name
409     * @throws Service\Exception
410     * @return mixed
411     */
412    public function getService(string $name): mixed
413    {
414        return $this->services->get($name);
415    }
416
417    /**
418     * Remove a service
419     *
420     * @param  string $name
421     * @return static
422     */
423    public function removeService(string $name): static
424    {
425        $this->services->remove($name);
426        return $this;
427    }
428
429    /**
430     * Attach an event. Default hook-points are:
431     *
432     *   app.init
433     *   app.route.pre
434     *   app.dispatch.pre
435     *   app.dispatch.post
436     *   app.error
437     *
438     * @param  string $name
439     * @param  mixed  $action
440     * @param  int    $priority
441     * @return static
442     */
443    public function on(string $name, mixed $action, int $priority = 0): static
444    {
445        $this->events->on($name, $action, $priority);
446        return $this;
447    }
448
449    /**
450     * Detach an event. Default hook-points are:
451     *
452     *   app.init
453     *   app.route.pre
454     *   app.dispatch.pre
455     *   app.dispatch.post
456     *   app.error
457     *
458     * @param  string $name
459     * @param  mixed  $action
460     * @return static
461     */
462    public function off(string $name, mixed $action): static
463    {
464        $this->events->off($name, $action);
465        return $this;
466    }
467
468    /**
469     * Trigger an event
470     *
471     * @param  string $name
472     * @param  array $args
473     * @return static
474     */
475    public function trigger(string $name, array $args = []): static
476    {
477        if (count($args) == 0) {
478            $args = ['application' => $this];
479        } else if (!in_array($this, $args, true)) {
480            $args['application'] = $this;
481        }
482        $this->events->trigger($name, $args);
483        return $this;
484    }
485
486    /**
487     * Get environment value
488     *
489     * @param  string $key
490     * @param  mixed  $default
491     * @return mixed
492     */
493    public function env(string $key, mixed $default = null): mixed
494    {
495        return App::env($key, $default);
496    }
497
498    /**
499     * Get application environment
500     *
501     * @param  mixed $env
502     * @return string|null|bool
503     */
504    public function environment(mixed $env = null): string|null|bool
505    {
506        return App::environment($env);
507    }
508
509    /**
510     * Get application name (alias method)
511     *
512     * @return ?string
513     */
514    public function name(): ?string
515    {
516        return $this->name;
517    }
518
519    /**
520     * Get application URL
521     *
522     * @return ?string
523     */
524    public function url(): ?string
525    {
526        return App::url();
527    }
528
529    /**
530     * Check if application environment is local
531     *
532     * @return bool
533     */
534    public function isLocal(): bool
535    {
536        return App::isLocal();
537    }
538
539    /**
540     * Check if application environment is dev
541     *
542     * @return bool
543     */
544    public function isDev(): bool
545    {
546        return App::isDev();
547    }
548
549    /**
550     * Check if application environment is testing
551     *
552     * @return bool
553     */
554    public function isTesting(): bool
555    {
556        return App::isTesting();
557    }
558
559    /**
560     * Check if application environment is staging
561     *
562     * @return bool
563     */
564    public function isStaging(): bool
565    {
566        return App::isStaging();
567    }
568
569    /**
570     * Check if application environment is production
571     *
572     * @return bool
573     */
574    public function isProduction(): bool
575    {
576        return App::isProduction();
577    }
578
579    /**
580     * Check if application is in maintenance mode
581     *
582     * @return bool
583     */
584    public function isDown(): bool
585    {
586        return App::isDown();
587    }
588
589    /**
590     * Check if application is in not maintenance mode
591     *
592     * @return bool
593     */
594    public function isUp(): bool
595    {
596        return App::isUp();
597    }
598
599    /**
600     * Run the application
601     *
602     * @param  bool    $exit
603     * @param  ?string $forceRoute
604     * @throws Event\Exception|Router\Exception|ReflectionException
605     * @return void
606     */
607    public function run(bool $exit = true, ?string $forceRoute = null): void
608    {
609        try {
610            $this->init();
611
612            // Trigger any app.route.pre events
613            $this->trigger('app.route.pre');
614
615            if (($this->router !== null)) {
616                $this->router->route($forceRoute);
617
618                // Trigger any app.dispatch.post events
619                $this->trigger('app.dispatch.pre');
620
621                if ($this->router->hasController()) {
622                    $controller = $this->router->getController();
623                    if ($this->router->getControllerClass() == 'Closure') {
624                        if ($this->router->hasRouteParams()) {
625                            call_user_func_array($controller, array_values($this->router->getRouteParams()));
626                        } else {
627                            $controller();
628                        }
629                    } else {
630                        $params = ($this->router->hasRouteParams()) ? $this->router->getRouteParams() : null;
631                        $controller->dispatch($this->router->getAction(), $params);
632                    }
633                } else {
634                    $this->router->noRouteFound($exit);
635                }
636
637                // Trigger any app.dispatch.post events
638                $this->trigger('app.dispatch.post');
639            }
640        } catch (Exception $exception) {
641            // Trigger any app.error events
642            $this->trigger('app.error', ['exception' => $exception]);
643        }
644    }
645
646    /**
647     * Set a pre-designated value in the application object
648     *
649     * @param  string $name
650     * @param  mixed $value
651     * @throws Exception
652     * @return void
653     */
654    public function __set(string $name, mixed $value): void
655    {
656        switch ($name) {
657            case 'config':
658                $this->registerConfig($value);
659                break;
660            case 'router':
661                $this->registerRouter($value);
662                break;
663            case 'services':
664                $this->registerServices($value);
665                break;
666            case 'events':
667                $this->registerEvents($value);
668                break;
669            case 'modules':
670                $this->registerModules($value);
671                break;
672            case 'autoloader':
673                $this->registerAutoloader($value);
674                break;
675        }
676    }
677
678    /**
679     * Get a pre-designated value from the application object
680     *
681     * @param  string $name
682     * @return mixed
683     */
684    public function __get(string $name): mixed
685    {
686        return match ($name) {
687            'config'     => $this->config,
688            'router'     => $this->router,
689            'services'   => $this->services,
690            'events'     => $this->events,
691            'modules'    => $this->modules,
692            'autoloader' => $this->autoloader,
693            default      => null,
694        };
695    }
696
697    /**
698     * Determine if a pre-designated value in the application object exists
699     *
700     * @param  string $name
701     * @return bool
702     */
703    public function __isset(string $name): bool
704    {
705        return match ($name) {
706            'config'     => ($this->config !== null),
707            'router'     => ($this->router !== null),
708            'services'   => ($this->services !== null),
709            'events'     => ($this->events !== null),
710            'modules'    => ($this->modules !== null),
711            'autoloader' => ($this->autoloader !== null),
712            default      => false,
713        };
714    }
715
716    /**
717     * Unset a pre-designated value in the application object
718     *
719     * @param  string $name
720     * @return void
721     */
722    public function __unset(string $name): void
723    {
724        switch ($name) {
725            case 'config':
726                $this->config = null;
727                break;
728            case 'router':
729                $this->router = null;
730                break;
731            case 'services':
732                $this->services = null;
733                break;
734            case 'events':
735                $this->events = null;
736                break;
737            case 'modules':
738                $this->modules = null;
739                break;
740            case 'autoloader':
741                $this->autoloader = null;
742                break;
743        }
744    }
745
746    /**
747     * Set a pre-designated value in the application object
748     *
749     * @param  mixed $offset
750     * @param  mixed $value
751     * @throws Exception
752     * @return void
753     */
754    public function offsetSet(mixed $offset, mixed $value): void
755    {
756        $this->__set($offset, $value);
757    }
758
759    /**
760     * Get a pre-designated value from the application object
761     *
762     * @param  mixed $offset
763     * @return mixed
764     */
765    public function offsetGet(mixed $offset): mixed
766    {
767        return $this->__get($offset);
768    }
769
770    /**
771     * Determine if a pre-designated value in the application object exists
772     *
773     * @param  mixed $offset
774     * @return bool
775     */
776    public function offsetExists(mixed $offset): bool
777    {
778        return $this->__isset($offset);
779    }
780
781    /**
782     * Unset a pre-designated value in the application object
783     *
784     * @param  mixed $offset
785     * @return void
786     */
787    public function offsetUnset(mixed $offset): void
788    {
789        $this->__unset($offset);
790    }
791
792}