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