• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aplus-framework / mvc / 15792038249

01 Mar 2025 01:17AM UTC coverage: 100.0%. Remained the same
15792038249

push

github

natanfelles
Update section order in the user guide

1829 of 1829 relevant lines covered (100.0%)

10.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/App.php
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of Aplus Framework MVC Library.
4
 *
5
 * (c) Natan Felles <natanfelles@gmail.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Framework\MVC;
11

12
use Framework\Autoload\Autoloader;
13
use Framework\Autoload\Debug\AutoloadCollection;
14
use Framework\Autoload\Locator;
15
use Framework\Cache\Cache;
16
use Framework\Cache\Debug\CacheCollection;
17
use Framework\Cache\Debug\CacheCollector;
18
use Framework\Cache\Serializer;
19
use Framework\CLI\Command;
20
use Framework\CLI\Console;
21
use Framework\Config\Config;
22
use Framework\Config\Debug\ConfigCollection;
23
use Framework\Config\Debug\ConfigCollector;
24
use Framework\Database\Database;
25
use Framework\Database\Debug\DatabaseCollection;
26
use Framework\Database\Debug\DatabaseCollector;
27
use Framework\Database\Extra\Migrator;
28
use Framework\Debug\Debugger;
29
use Framework\Debug\ExceptionHandler;
30
use Framework\Email\Debug\EmailCollection;
31
use Framework\Email\Debug\EmailCollector;
32
use Framework\Email\Mailer;
33
use Framework\Helpers\Isolation;
34
use Framework\HTTP\AntiCSRF;
35
use Framework\HTTP\CSP;
36
use Framework\HTTP\Debug\HTTPCollection;
37
use Framework\HTTP\Debug\HTTPCollector;
38
use Framework\HTTP\Request;
39
use Framework\HTTP\Response;
40
use Framework\Language\Debug\LanguageCollection;
41
use Framework\Language\Debug\LanguageCollector;
42
use Framework\Language\FallbackLevel;
43
use Framework\Language\Language;
44
use Framework\Log\Debug\LogCollection;
45
use Framework\Log\Debug\LogCollector;
46
use Framework\Log\Logger;
47
use Framework\Log\Loggers\MultiFileLogger;
48
use Framework\Log\LogLevel;
49
use Framework\MVC\Debug\AppCollection;
50
use Framework\MVC\Debug\AppCollector;
51
use Framework\MVC\Debug\ViewsCollection;
52
use Framework\MVC\Debug\ViewsCollector;
53
use Framework\Routing\Debug\RoutingCollection;
54
use Framework\Routing\Debug\RoutingCollector;
55
use Framework\Routing\Router;
56
use Framework\Session\Debug\SessionCollection;
57
use Framework\Session\Debug\SessionCollector;
58
use Framework\Session\SaveHandlers\DatabaseHandler;
59
use Framework\Session\Session;
60
use Framework\Validation\Debug\ValidationCollection;
61
use Framework\Validation\Debug\ValidationCollector;
62
use Framework\Validation\FilesValidator;
63
use Framework\Validation\Validation;
64
use LogicException;
65
use ReflectionClass;
66
use ReflectionException;
67
use SensitiveParameter;
68

69
/**
70
 * Class App.
71
 *
72
 * @package mvc
73
 */
74
class App
75
{
76
    /**
77
     * Array with keys with names of services and their values have arrays where
78
     * the keys are the names of the instances and the values are the objects.
79
     *
80
     * @var array<string,array<string,object>>
81
     */
82
    protected static array $services = [];
83
    /**
84
     * Tells if the App is running.
85
     *
86
     * @var bool
87
     */
88
    protected static bool $isRunning = false;
89
    /**
90
     * The Config instance.
91
     *
92
     * @var Config|null
93
     */
94
    protected static ?Config $config;
95
    /**
96
     * Tells if the request is by command line. Updating directly makes it
97
     * possible to run tests simulating HTTP or CLI.
98
     *
99
     * @var bool|null
100
     */
101
    protected static ?bool $isCli = null;
102
    /**
103
     * The App collector instance that is set when in debug mode.
104
     *
105
     * @var AppCollector
106
     */
107
    protected static AppCollector $debugCollector;
108
    /**
109
     * Variables set in the $_SERVER super-global in command-line requests.
110
     *
111
     * @var array<string,mixed>
112
     */
113
    protected static array $defaultServerVars = [
114
        'REMOTE_ADDR' => '127.0.0.1',
115
        'REQUEST_METHOD' => 'GET',
116
        'REQUEST_URI' => '/',
117
        'SERVER_PROTOCOL' => 'HTTP/1.1',
118
        'HTTP_HOST' => 'localhost',
119
    ];
120

121
    /**
122
     * Initialize the App.
123
     *
124
     * @param Config|array<string,mixed>|string|null $config The config
125
     * @param bool $debug Set true to enable debug mode. False to disable.
126
     */
127
    public function __construct(
128
        #[SensitiveParameter]
129
        Config | array | string | null $config = null,
130
        bool $debug = false
131
    ) {
132
        if ($debug) {
116✔
133
            $this->debugStart();
11✔
134
        }
135
        if (isset(static::$config)) {
116✔
136
            throw new LogicException('App already initialized');
1✔
137
        }
138
        if (!$config instanceof Config) {
116✔
139
            $config = new Config($config);
12✔
140
        }
141
        static::$config = $config;
116✔
142
        if ($debug) {
116✔
143
            $collection = new AppCollection('App');
11✔
144
            $collection->addCollector(static::$debugCollector);
11✔
145
            static::debugger()->addCollection($collection);
11✔
146
            $configCollector = new ConfigCollector();
11✔
147
            static::$config->setDebugCollector($configCollector);
11✔
148
            $configCollection = new ConfigCollection('Config');
11✔
149
            $configCollection->addCollector($configCollector);
11✔
150
            static::debugger()->addCollection($configCollection);
11✔
151
        }
152
    }
153

154
    /**
155
     * Start debugging the App.
156
     */
157
    protected function debugStart() : void
158
    {
159
        static::$debugCollector = new AppCollector();
11✔
160
        static::$debugCollector->setStartTime()->setStartMemory();
11✔
161
        static::$debugCollector->setApp($this);
11✔
162
    }
163

164
    /**
165
     * Load service configs catching exceptions.
166
     *
167
     * @param string $name The service name
168
     *
169
     * @return array<string,array<string,mixed>>|null The service configs or null
170
     */
171
    protected function loadConfigs(string $name) : ?array
172
    {
173
        $config = static::config();
10✔
174
        try {
175
            $config->load($name);
10✔
176
        } catch (LogicException) {
1✔
177
        }
178
        return $config->getInstances($name);
10✔
179
    }
180

181
    /**
182
     * Make sure to load the autoloader service if its default config is set.
183
     */
184
    protected function loadAutoloader() : void
185
    {
186
        $config = static::config();
10✔
187
        $autoloaderConfigs = $config->getInstances('autoloader');
10✔
188
        if ($config->getDir() !== null) {
10✔
189
            $autoloaderConfigs ??= $this->loadConfigs('autoloader');
9✔
190
        }
191
        if (isset($autoloaderConfigs['default'])) {
10✔
192
            static::autoloader();
10✔
193
        }
194
    }
195

196
    /**
197
     * Make sure to load the exceptionHandler service if its default config is set.
198
     */
199
    protected function loadExceptionHandler() : void
200
    {
201
        $config = static::config();
10✔
202
        $exceptionHandlerConfigs = $config->getInstances('exceptionHandler');
10✔
203
        if ($config->getDir() !== null) {
10✔
204
            $exceptionHandlerConfigs ??= $this->loadConfigs('exceptionHandler');
9✔
205
        }
206
        if (!isset($exceptionHandlerConfigs['default'])) {
10✔
207
            $environment = static::isDebugging()
3✔
208
                ? ExceptionHandler::DEVELOPMENT
2✔
209
                : ExceptionHandler::PRODUCTION;
1✔
210
            $config->set('exceptionHandler', [
3✔
211
                'environment' => $environment,
3✔
212
            ]);
3✔
213
            $exceptionHandlerConfigs = $config->getInstances('exceptionHandler');
3✔
214
        }
215
        if (isset($exceptionHandlerConfigs['default'])) {
10✔
216
            static::exceptionHandler();
10✔
217
        }
218
    }
219

220
    /**
221
     * Prepare the App to run via CLI or HTTP.
222
     */
223
    protected function prepareToRun() : void
224
    {
225
        if (static::$isRunning) {
10✔
226
            throw new LogicException('App is already running');
1✔
227
        }
228
        static::$isRunning = true;
10✔
229
        $this->loadAutoloader();
10✔
230
        $this->loadExceptionHandler();
10✔
231
    }
232

233
    /**
234
     * Run the App on HTTP requests.
235
     */
236
    public function runHttp() : void
237
    {
238
        $this->prepareToRun();
8✔
239
        $router = static::router();
8✔
240
        $response = $router->getResponse();
7✔
241
        $router->match()
7✔
242
            ->run($response->getRequest(), $response)
7✔
243
            ->send();
7✔
244
        if (static::isDebugging()) {
7✔
245
            $this->debugEnd();
3✔
246
        }
247
    }
248

249
    /**
250
     * Ends the debugging of the App and prints the debugbar if there is no
251
     * download file, if the request is not via AJAX and the Content-Type is
252
     * text/html.
253
     */
254
    protected function debugEnd() : void
255
    {
256
        static::$debugCollector->setEndTime()->setEndMemory();
3✔
257
        $response = static::router()->getResponse();
3✔
258
        if (!$response->hasDownload()
3✔
259
            && !$response->getRequest()->isAjax()
3✔
260
            && \str_contains(
3✔
261
                (string) $response->getHeader('Content-Type'),
3✔
262
                'text/html'
3✔
263
            )
3✔
264
        ) {
265
            echo static::debugger()->renderDebugbar();
3✔
266
        }
267
    }
268

269
    /**
270
     * Detects if the request is via command-line and runs as a CLI request,
271
     * otherwise runs as HTTP.
272
     */
273
    public function run() : void
274
    {
275
        static::isCli() ? $this->runCli() : $this->runHttp();
2✔
276
    }
277

278
    /**
279
     * Run the App on CLI requests.
280
     */
281
    public function runCli() : void
282
    {
283
        $this->prepareToRun();
3✔
284
        static::console()->run();
2✔
285
    }
286

287
    /**
288
     * Get the Config instance.
289
     *
290
     * @return Config
291
     */
292
    public static function config() : Config
293
    {
294
        return static::$config;
111✔
295
    }
296

297
    /**
298
     * Get a service.
299
     *
300
     * @param string $name Service name
301
     * @param string $instance Service instance name
302
     *
303
     * @return object|null The service object or null
304
     */
305
    public static function getService(string $name, string $instance = 'default') : ?object
306
    {
307
        return static::$services[$name][$instance] ?? null;
110✔
308
    }
309

310
    /**
311
     * Set a service.
312
     *
313
     * @template T of object
314
     *
315
     * @param string $name Service name
316
     * @param T $service Service object
317
     * @param string $instance Service instance name
318
     *
319
     * @return T The service object
320
     */
321
    public static function setService(
322
        string $name,
323
        object $service,
324
        string $instance = 'default'
325
    ) : object {
326
        return static::$services[$name][$instance] = $service;
110✔
327
    }
328

329
    /**
330
     * Remove services.
331
     *
332
     * @param string $name Service name
333
     * @param string|null $instance Service instance name or null to remove all instances
334
     */
335
    public static function removeService(string $name, ?string $instance = 'default') : void
336
    {
337
        if ($instance === null) {
3✔
338
            unset(static::$services[$name]);
1✔
339
            return;
1✔
340
        }
341
        unset(static::$services[$name][$instance]);
3✔
342
    }
343

344
    /**
345
     * Get a autoloader service.
346
     *
347
     * @param string $instance The autoloader instance name
348
     *
349
     * @return Autoloader
350
     */
351
    public static function autoloader(string $instance = 'default') : Autoloader
352
    {
353
        $service = static::getService('autoloader', $instance);
74✔
354
        if ($service) {
74✔
355
            return $service; // @phpstan-ignore-line
11✔
356
        }
357
        if (static::isDebugging()) {
74✔
358
            $start = \microtime(true);
7✔
359
            $service = static::setAutoloader($instance);
7✔
360
            $end = \microtime(true);
7✔
361
            $service->setDebugCollector(name: $instance);
7✔
362
            $collection = static::debugger()->getCollection('Autoload')
7✔
363
                ?? new AutoloadCollection('Autoload');
7✔
364
            $collection->addCollector($service->getDebugCollector());
7✔
365
            static::debugger()->addCollection($collection);
7✔
366
            static::addDebugData('autoloader', $instance, $start, $end);
7✔
367
            return $service;
7✔
368
        }
369
        return static::setAutoloader($instance);
67✔
370
    }
371

372
    /**
373
     * Set a autoloader service.
374
     *
375
     * @param string $instance The autoloader instance name
376
     *
377
     * @return Autoloader
378
     */
379
    protected static function setAutoloader(string $instance) : Autoloader
380
    {
381
        $config = static::config()->get('autoloader', $instance);
74✔
382
        $service = new Autoloader($config['register'] ?? true, $config['extensions'] ?? '.php');
74✔
383
        if (isset($config['namespaces'])) {
74✔
384
            $service->setNamespaces($config['namespaces']);
69✔
385
        }
386
        if (isset($config['classes'])) {
74✔
387
            $service->setClasses($config['classes']);
67✔
388
        }
389
        return static::setService('autoloader', $service, $instance);
74✔
390
    }
391

392
    /**
393
     * Get a cache service.
394
     *
395
     * @param string $instance The cache instance name
396
     *
397
     * @return Cache
398
     */
399
    public static function cache(string $instance = 'default') : Cache
400
    {
401
        $service = static::getService('cache', $instance);
13✔
402
        if ($service) {
13✔
403
            return $service; // @phpstan-ignore-line
12✔
404
        }
405
        if (static::isDebugging()) {
13✔
406
            $start = \microtime(true);
1✔
407
            $service = static::setCache($instance);
1✔
408
            $end = \microtime(true);
1✔
409
            $collector = new CacheCollector($instance);
1✔
410
            $service->setDebugCollector($collector);
1✔
411
            $collection = static::debugger()->getCollection('Cache')
1✔
412
                ?? new CacheCollection('Cache');
1✔
413
            $collection->addCollector($collector);
1✔
414
            static::debugger()->addCollection($collection);
1✔
415
            static::addDebugData('cache', $instance, $start, $end);
1✔
416
            return $service;
1✔
417
        }
418
        return static::setCache($instance);
12✔
419
    }
420

421
    /**
422
     * Set a cache service.
423
     *
424
     * @param string $instance The cache instance name
425
     *
426
     * @return Cache
427
     */
428
    protected static function setCache(string $instance) : Cache
429
    {
430
        $config = static::config()->get('cache', $instance);
13✔
431
        $logger = null;
13✔
432
        if (isset($config['logger_instance'])) {
13✔
433
            $logger = static::logger($config['logger_instance']);
13✔
434
        }
435
        $config['serializer'] ??= Serializer::PHP;
13✔
436
        if (\is_string($config['serializer'])) {
13✔
437
            $config['serializer'] = Serializer::from($config['serializer']);
13✔
438
        }
439
        /**
440
         * @var Cache $service
441
         */
442
        $service = new $config['class'](
13✔
443
            $config['configs'] ?? [],
13✔
444
            $config['prefix'] ?? null,
13✔
445
            $config['serializer'],
13✔
446
            $logger
13✔
447
        );
13✔
448
        if (isset($config['default_ttl'])) {
13✔
449
            $service->setDefaultTtl($config['default_ttl']);
13✔
450
        }
451
        return static::setService('cache', $service, $instance);
13✔
452
    }
453

454
    /**
455
     * Get a console service.
456
     *
457
     * @param string $instance The console instance name
458
     *
459
     * @throws ReflectionException
460
     *
461
     * @return Console
462
     */
463
    public static function console(string $instance = 'default') : Console
464
    {
465
        $service = static::getService('console', $instance);
4✔
466
        if ($service) {
4✔
467
            return $service; // @phpstan-ignore-line
1✔
468
        }
469
        if (static::isDebugging()) {
4✔
470
            $start = \microtime(true);
1✔
471
            $service = static::setConsole($instance);
1✔
472
            $end = \microtime(true);
1✔
473
            static::addDebugData('console', $instance, $start, $end);
1✔
474
            return $service;
1✔
475
        }
476
        return static::setConsole($instance);
3✔
477
    }
478

479
    /**
480
     * Set a console service.
481
     *
482
     * @param string $instance The console instance name
483
     *
484
     * @throws ReflectionException
485
     *
486
     * @return Console
487
     */
488
    protected static function setConsole(string $instance) : Console
489
    {
490
        $config = static::config()->get('console', $instance);
4✔
491
        $language = null;
4✔
492
        if (isset($config['language_instance'])) {
4✔
493
            $language = static::language($config['language_instance']);
4✔
494
        }
495
        $service = new Console($language);
4✔
496
        $locator = static::locator($config['locator_instance'] ?? 'default');
4✔
497
        if (isset($config['find_in_namespaces']) && $config['find_in_namespaces'] === true) {
4✔
498
            foreach ($locator->getFiles('Commands') as $file) {
4✔
499
                static::addCommand($file, $service, $locator);
4✔
500
            }
501
        }
502
        if (isset($config['directories'])) {
4✔
503
            foreach ($config['directories'] as $dir) {
4✔
504
                foreach ((array) $locator->listFiles($dir) as $file) {
4✔
505
                    static::addCommand($file, $service, $locator);
4✔
506
                }
507
            }
508
        }
509
        return static::setService('console', $service, $instance);
4✔
510
    }
511

512
    /**
513
     * Detects if the file has a command and adds it to the console.
514
     *
515
     * @param string $file The file to get the command class
516
     * @param Console $console The console to add the class
517
     * @param Locator $locator The locator to get the class name in the file
518
     *
519
     * @throws ReflectionException
520
     *
521
     * @return bool True if the command was added. If not, it's false.
522
     */
523
    protected static function addCommand(string $file, Console $console, Locator $locator) : bool
524
    {
525
        $className = $locator->getClassName($file);
4✔
526
        if ($className === null) {
4✔
527
            return false;
4✔
528
        }
529
        if (!\class_exists($className)) {
4✔
530
            Isolation::require($file);
4✔
531
        }
532
        $class = new ReflectionClass($className); // @phpstan-ignore-line
4✔
533
        if ($class->isInstantiable() && $class->isSubclassOf(Command::class)) {
4✔
534
            $console->addCommand($className); // @phpstan-ignore-line
4✔
535
            return true;
4✔
536
        }
537
        return false;
4✔
538
    }
539

540
    /**
541
     * Get a debugger service.
542
     *
543
     * @param string $instance The debugger instance name
544
     *
545
     * @return Debugger
546
     */
547
    public static function debugger(string $instance = 'default') : Debugger
548
    {
549
        $service = static::getService('debugger', $instance);
12✔
550
        if ($service) {
12✔
551
            return $service; // @phpstan-ignore-line
12✔
552
        }
553
        if (static::isDebugging()) {
12✔
554
            $start = \microtime(true);
11✔
555
            $service = static::setDebugger($instance);
11✔
556
            $end = \microtime(true);
11✔
557
            static::addDebugData('debugger', $instance, $start, $end);
11✔
558
            return $service;
11✔
559
        }
560
        return static::setDebugger($instance);
1✔
561
    }
562

563
    /**
564
     * Set a debugger service.
565
     *
566
     * @param string $instance The debugger instance name
567
     *
568
     * @return Debugger
569
     */
570
    protected static function setDebugger(string $instance) : Debugger
571
    {
572
        $config = static::config()->get('debugger', $instance);
12✔
573
        $service = new Debugger();
12✔
574
        if (isset($config['debugbar_view'])) {
12✔
575
            $service->setDebugbarView($config['debugbar_view']);
3✔
576
        }
577
        if (isset($config['options'])) {
12✔
578
            $service->setOptions($config['options']);
3✔
579
        }
580
        return static::setService('debugger', $service, $instance);
12✔
581
    }
582

583
    /**
584
     * Get a exceptionHandler service.
585
     *
586
     * @param string $instance The exceptionHandler instance name
587
     *
588
     * @return ExceptionHandler
589
     */
590
    public static function exceptionHandler(string $instance = 'default') : ExceptionHandler
591
    {
592
        $service = static::getService('exceptionHandler', $instance);
11✔
593
        if ($service) {
11✔
594
            return $service; // @phpstan-ignore-line
1✔
595
        }
596
        if (static::isDebugging()) {
11✔
597
            $start = \microtime(true);
3✔
598
            $service = static::setExceptionHandler($instance);
3✔
599
            $end = \microtime(true);
3✔
600
            static::addDebugData('exceptionHandler', $instance, $start, $end);
3✔
601
            return $service;
3✔
602
        }
603
        return static::setExceptionHandler($instance);
8✔
604
    }
605

606
    /**
607
     * Set a exceptionHandler service.
608
     *
609
     * @param string $instance The exceptionHandler instance name
610
     *
611
     * @return ExceptionHandler
612
     */
613
    protected static function setExceptionHandler(string $instance) : ExceptionHandler
614
    {
615
        $config = static::config()->get('exceptionHandler', $instance);
11✔
616
        $environment = $config['environment'] ?? ExceptionHandler::PRODUCTION;
11✔
617
        $logger = null;
11✔
618
        if (isset($config['logger_instance'])) {
11✔
619
            $logger = static::logger($config['logger_instance']);
8✔
620
        }
621
        $language = null;
11✔
622
        if (isset($config['language_instance'])) {
11✔
623
            $language = static::language($config['language_instance']);
8✔
624
        }
625
        $service = new ExceptionHandler($environment, $logger, $language);
11✔
626
        if (isset($config['development_view'])) {
11✔
627
            $service->setDevelopmentView($config['development_view']);
8✔
628
        }
629
        if (isset($config['production_view'])) {
11✔
630
            $service->setProductionView($config['production_view']);
8✔
631
        }
632
        $config['initialize'] ??= true;
11✔
633
        if ($config['initialize'] === true) {
11✔
634
            $service->initialize($config['handle_errors'] ?? true);
11✔
635
        }
636
        if (isset($config['search_engine'])) {
11✔
637
            $service->getSearchEngines()->setCurrent($config['search_engine']);
8✔
638
        }
639
        if (isset($config['show_log_id'])) {
11✔
640
            $service->setShowLogId($config['show_log_id']);
8✔
641
        }
642
        return static::setService('exceptionHandler', $service, $instance);
11✔
643
    }
644

645
    /**
646
     * Get a antiCsrf service.
647
     *
648
     * @param string $instance The antiCsrf instance name
649
     *
650
     * @return AntiCSRF
651
     */
652
    public static function antiCsrf(string $instance = 'default') : AntiCSRF
653
    {
654
        $service = static::getService('antiCsrf', $instance);
2✔
655
        if ($service) {
2✔
656
            return $service; // @phpstan-ignore-line
1✔
657
        }
658
        if (static::isDebugging()) {
2✔
659
            $start = \microtime(true);
1✔
660
            $service = static::setAntiCsrf($instance);
1✔
661
            $end = \microtime(true);
1✔
662
            static::addDebugData('antiCsrf', $instance, $start, $end);
1✔
663
            return $service;
1✔
664
        }
665
        return static::setAntiCsrf($instance);
1✔
666
    }
667

668
    /**
669
     * Set a antiCsrf service.
670
     *
671
     * @param string $instance The antiCsrf instance name
672
     *
673
     * @return AntiCSRF
674
     */
675
    protected static function setAntiCsrf(string $instance) : AntiCSRF
676
    {
677
        $config = static::config()->get('antiCsrf', $instance);
2✔
678
        static::session($config['session_instance'] ?? 'default');
2✔
679
        $service = new AntiCSRF(
2✔
680
            static::request($config['request_instance'] ?? 'default'),
2✔
681
            $config['token_bytes_length'] ?? null,
2✔
682
            $config['generate_token_function'] ?? null,
2✔
683
        );
2✔
684
        if (isset($config['token_name'])) {
2✔
685
            $service->setTokenName($config['token_name']);
2✔
686
        }
687
        if (isset($config['enabled']) && $config['enabled'] === false) {
2✔
688
            $service->disable();
2✔
689
        }
690
        return static::setService('antiCsrf', $service, $instance);
2✔
691
    }
692

693
    /**
694
     * Get a database service.
695
     *
696
     * @param string $instance The database instance name
697
     *
698
     * @return Database
699
     */
700
    public static function database(string $instance = 'default') : Database
701
    {
702
        $service = static::getService('database', $instance);
83✔
703
        if ($service) {
83✔
704
            return $service; // @phpstan-ignore-line
83✔
705
        }
706
        if (static::isDebugging()) {
83✔
707
            $collector = new DatabaseCollector($instance);
1✔
708
            $start = \microtime(true);
1✔
709
            $service = static::setDatabase($instance, $collector);
1✔
710
            $end = \microtime(true);
1✔
711
            $collection = static::debugger()->getCollection('Database')
1✔
712
                ?? new DatabaseCollection('Database');
1✔
713
            $collection->addCollector($collector);
1✔
714
            static::debugger()->addCollection($collection);
1✔
715
            static::addDebugData('database', $instance, $start, $end);
1✔
716
            return $service;
1✔
717
        }
718
        return static::setDatabase($instance);
82✔
719
    }
720

721
    /**
722
     * Set a database service.
723
     *
724
     * @param string $instance The database instance name
725
     *
726
     * @return Database
727
     */
728
    protected static function setDatabase(
729
        string $instance,
730
        ?DatabaseCollector $collector = null
731
    ) : Database {
732
        $config = static::config()->get('database', $instance);
83✔
733
        $logger = null;
83✔
734
        if (isset($config['logger_instance'])) {
83✔
735
            $logger = static::logger($config['logger_instance']);
83✔
736
        }
737
        return static::setService(
83✔
738
            'database',
83✔
739
            new Database(
83✔
740
                $config['config'],
83✔
741
                logger: $logger,
83✔
742
                collector: $collector
83✔
743
            ),
83✔
744
            $instance
83✔
745
        );
83✔
746
    }
747

748
    /**
749
     * Get a mailer service.
750
     *
751
     * @param string $instance The mailer instance name
752
     *
753
     * @return Mailer
754
     */
755
    public static function mailer(string $instance = 'default') : Mailer
756
    {
757
        $service = static::getService('mailer', $instance);
2✔
758
        if ($service) {
2✔
759
            return $service; // @phpstan-ignore-line
1✔
760
        }
761
        if (static::isDebugging()) {
2✔
762
            $start = \microtime(true);
1✔
763
            $service = static::setMailer($instance);
1✔
764
            $end = \microtime(true);
1✔
765
            $collector = new EmailCollector($instance);
1✔
766
            $service->setDebugCollector($collector);
1✔
767
            $collection = static::debugger()->getCollection('Email')
1✔
768
                ?? new EmailCollection('Email');
1✔
769
            $collection->addCollector($collector);
1✔
770
            static::debugger()->addCollection($collection);
1✔
771
            static::addDebugData('mailer', $instance, $start, $end);
1✔
772
            return $service;
1✔
773
        }
774
        return static::setMailer($instance);
1✔
775
    }
776

777
    /**
778
     * Set a mailer service.
779
     *
780
     * @param string $instance The mailer instance name
781
     *
782
     * @return Mailer
783
     */
784
    protected static function setMailer(string $instance) : Mailer
785
    {
786
        $config = static::config()->get('mailer', $instance);
2✔
787
        return static::setService(
2✔
788
            'mailer',
2✔
789
            new Mailer($config),
2✔
790
            $instance
2✔
791
        );
2✔
792
    }
793

794
    /**
795
     * Get a migrator service.
796
     *
797
     * @param string $instance The migrator instance name
798
     *
799
     * @return Migrator
800
     */
801
    public static function migrator(string $instance = 'default') : Migrator
802
    {
803
        $service = static::getService('migrator', $instance);
2✔
804
        if ($service) {
2✔
805
            return $service; // @phpstan-ignore-line
1✔
806
        }
807
        if (static::isDebugging()) {
2✔
808
            $start = \microtime(true);
1✔
809
            $service = static::setMigrator($instance);
1✔
810
            $end = \microtime(true);
1✔
811
            static::addDebugData('migrator', $instance, $start, $end);
1✔
812
            return $service;
1✔
813
        }
814
        return static::setMigrator($instance);
1✔
815
    }
816

817
    /**
818
     * Set a migrator service.
819
     *
820
     * @param string $instance The migrator instance name
821
     *
822
     * @return Migrator
823
     */
824
    protected static function setMigrator(string $instance) : Migrator
825
    {
826
        $config = static::config()->get('migrator', $instance);
2✔
827
        return static::setService(
2✔
828
            'migrator',
2✔
829
            new Migrator(
2✔
830
                static::database($config['database_instance'] ?? 'default'),
2✔
831
                $config['directories'],
2✔
832
                $config['table'] ?? 'Migrations',
2✔
833
            ),
2✔
834
            $instance
2✔
835
        );
2✔
836
    }
837

838
    /**
839
     * Get a language service.
840
     *
841
     * @param string $instance The language instance name
842
     *
843
     * @return Language
844
     */
845
    public static function language(string $instance = 'default') : Language
846
    {
847
        $service = static::getService('language', $instance);
68✔
848
        if ($service) {
68✔
849
            return $service; // @phpstan-ignore-line
15✔
850
        }
851
        if (static::isDebugging()) {
68✔
852
            $start = \microtime(true);
4✔
853
            $service = static::setLanguage($instance);
4✔
854
            $end = \microtime(true);
4✔
855
            $collector = new LanguageCollector($instance);
4✔
856
            $service->setDebugCollector($collector);
4✔
857
            $collection = static::debugger()->getCollection('Language')
4✔
858
                ?? new LanguageCollection('Language');
4✔
859
            $collection->addCollector($collector);
4✔
860
            static::debugger()->addCollection($collection);
4✔
861
            static::addDebugData('language', $instance, $start, $end);
4✔
862
            return $service;
4✔
863
        }
864
        return static::setLanguage($instance);
64✔
865
    }
866

867
    /**
868
     * Set a language service.
869
     *
870
     * @param string $instance The language instance name
871
     *
872
     * @return Language
873
     */
874
    protected static function setLanguage(string $instance) : Language
875
    {
876
        $config = static::config()->get('language', $instance);
68✔
877
        $service = new Language($config['default'] ?? 'en');
68✔
878
        if (isset($config['current'])) {
68✔
879
            $service->setCurrentLocale($config['current']);
66✔
880
        }
881
        if (isset($config['supported'])) {
68✔
882
            $service->setSupportedLocales($config['supported']);
66✔
883
        }
884
        if (isset($config['negotiate']) && $config['negotiate'] === true) {
68✔
885
            $service->setCurrentLocale(
66✔
886
                static::negotiateLanguage($service, $config['request_instance'] ?? 'default')
66✔
887
            );
66✔
888
        }
889
        if (isset($config['fallback_level'])) {
68✔
890
            if (\is_int($config['fallback_level'])) {
66✔
891
                $config['fallback_level'] = FallbackLevel::from($config['fallback_level']);
66✔
892
            }
893
            $service->setFallbackLevel($config['fallback_level']);
66✔
894
        }
895
        $config['directories'] ??= [];
68✔
896
        if (isset($config['find_in_namespaces']) && $config['find_in_namespaces'] === true) {
68✔
897
            foreach (static::autoloader($config['autoloader_instance'] ?? 'default')
66✔
898
                ->getNamespaces() as $directories) {
66✔
899
                foreach ($directories as $directory) {
66✔
900
                    $directory .= 'Languages';
66✔
901
                    if (\is_dir($directory)) {
66✔
902
                        $config['directories'][] = $directory;
66✔
903
                    }
904
                }
905
            }
906
        }
907
        if ($config['directories']) {
68✔
908
            $service->setDirectories($config['directories']);
66✔
909
        }
910
        $service->addDirectory(__DIR__ . '/Languages');
68✔
911
        return static::setService('language', $service, $instance);
68✔
912
    }
913

914
    /**
915
     * Negotiates the language either via the command line or over HTTP.
916
     *
917
     * @param Language $language The current Language instance
918
     * @param string $requestInstance The name of the Request instance to be used
919
     *
920
     * @return string The negotiated language
921
     */
922
    protected static function negotiateLanguage(Language $language, string $requestInstance = 'default') : string
923
    {
924
        if (static::isCli()) {
67✔
925
            $supported = \array_map('\strtolower', $language->getSupportedLocales());
61✔
926
            $lang = \getenv('LANG');
61✔
927
            if ($lang) {
61✔
928
                $lang = \explode('.', $lang, 2);
61✔
929
                $lang = \strtolower($lang[0]);
61✔
930
                $lang = \strtr($lang, ['_' => '-']);
61✔
931
                if (\in_array($lang, $supported, true)) {
61✔
932
                    return $lang;
1✔
933
                }
934
            }
935
            return $language->getDefaultLocale();
61✔
936
        }
937
        return static::request($requestInstance)->negotiateLanguage(
7✔
938
            $language->getSupportedLocales()
7✔
939
        );
7✔
940
    }
941

942
    /**
943
     * Get a locator service.
944
     *
945
     * @param string $instance The locator instance name
946
     *
947
     * @return Locator
948
     */
949
    public static function locator(string $instance = 'default') : Locator
950
    {
951
        $service = static::getService('locator', $instance);
6✔
952
        if ($service) {
6✔
953
            return $service; // @phpstan-ignore-line
4✔
954
        }
955
        if (static::isDebugging()) {
6✔
956
            $start = \microtime(true);
1✔
957
            $service = static::setLocator($instance);
1✔
958
            $end = \microtime(true);
1✔
959
            static::addDebugData('locator', $instance, $start, $end);
1✔
960
            return $service;
1✔
961
        }
962
        return static::setLocator($instance);
5✔
963
    }
964

965
    /**
966
     * Set a locator service.
967
     *
968
     * @param string $instance The locator instance name
969
     *
970
     * @return Locator
971
     */
972
    protected static function setLocator(string $instance) : Locator
973
    {
974
        $config = static::config()->get('locator', $instance);
6✔
975
        return static::setService(
6✔
976
            'locator',
6✔
977
            new Locator(static::autoloader($config['autoloader_instance'] ?? 'default')),
6✔
978
            $instance
6✔
979
        );
6✔
980
    }
981

982
    /**
983
     * Get a logger service.
984
     *
985
     * @param string $instance The logger instance name
986
     *
987
     * @return Logger
988
     */
989
    public static function logger(string $instance = 'default') : Logger
990
    {
991
        $service = static::getService('logger', $instance);
90✔
992
        if ($service) {
90✔
993
            return $service; // @phpstan-ignore-line
14✔
994
        }
995
        if (static::isDebugging()) {
90✔
996
            $start = \microtime(true);
1✔
997
            $service = static::setLogger($instance);
1✔
998
            $end = \microtime(true);
1✔
999
            $collector = new LogCollector($instance);
1✔
1000
            $service->setDebugCollector($collector);
1✔
1001
            $collection = static::debugger()->getCollection('Log')
1✔
1002
                ?? new LogCollection('Log');
1✔
1003
            $collection->addCollector($collector);
1✔
1004
            static::debugger()->addCollection($collection);
1✔
1005
            static::addDebugData('logger', $instance, $start, $end);
1✔
1006
            return $service;
1✔
1007
        }
1008
        return static::setLogger($instance);
89✔
1009
    }
1010

1011
    /**
1012
     * Set a logger service.
1013
     *
1014
     * @param string $instance The logger instance name
1015
     *
1016
     * @return Logger
1017
     */
1018
    protected static function setLogger(string $instance) : Logger
1019
    {
1020
        $config = static::config()->get('logger', $instance);
90✔
1021
        /**
1022
         * @var class-string<Logger> $class
1023
         */
1024
        $class = $config['class'] ?? MultiFileLogger::class;
90✔
1025
        $config['level'] ??= LogLevel::DEBUG;
90✔
1026
        if (\is_int($config['level'])) {
90✔
1027
            $config['level'] = LogLevel::from($config['level']);
90✔
1028
        }
1029
        return static::setService(
90✔
1030
            'logger',
90✔
1031
            new $class(
90✔
1032
                $config['destination'],
90✔
1033
                $config['level'],
90✔
1034
                $config['config'] ?? [],
90✔
1035
            ),
90✔
1036
            $instance
90✔
1037
        );
90✔
1038
    }
1039

1040
    /**
1041
     * Get a router service.
1042
     *
1043
     * @param string $instance The router instance name
1044
     *
1045
     * @return Router
1046
     */
1047
    public static function router(string $instance = 'default') : Router
1048
    {
1049
        $service = static::getService('router', $instance);
9✔
1050
        if ($service) {
9✔
1051
            return $service; // @phpstan-ignore-line
8✔
1052
        }
1053
        if (static::isDebugging()) {
9✔
1054
            $start = \microtime(true);
3✔
1055
            $config = (array) static::config()->get('router', $instance);
3✔
1056
            $service = static::setRouter($instance, $config);
3✔
1057
            $collector = new RoutingCollector($instance);
3✔
1058
            $service->setDebugCollector($collector);
3✔
1059
            if (isset($config['files'])) {
3✔
1060
                static::requireRouterFiles($config['files'], $service);
2✔
1061
            }
1062
            $end = \microtime(true);
3✔
1063
            $collection = static::debugger()->getCollection('Routing')
3✔
1064
                ?? new RoutingCollection('Routing');
3✔
1065
            $collection->addCollector($collector);
3✔
1066
            static::debugger()->addCollection($collection);
3✔
1067
            static::addDebugData('router', $instance, $start, $end);
3✔
1068
            return $service;
3✔
1069
        }
1070
        return static::setRouter($instance);
6✔
1071
    }
1072

1073
    /**
1074
     * Set a router service.
1075
     *
1076
     * @param string $instance The router instance name
1077
     * @param array<mixed>|null $config The router instance configs or null
1078
     *
1079
     * @return Router
1080
     */
1081
    protected static function setRouter(string $instance, ?array $config = null) : Router
1082
    {
1083
        $requireFiles = $config === null;
9✔
1084
        $config ??= static::config()->get('router', $instance);
9✔
1085
        $language = null;
9✔
1086
        if (isset($config['language_instance'])) {
9✔
1087
            $language = static::language($config['language_instance']);
7✔
1088
        }
1089
        $service = static::setService('router', new Router(
9✔
1090
            static::response($config['response_instance'] ?? 'default'),
9✔
1091
            $language
9✔
1092
        ), $instance);
9✔
1093
        if (isset($config['auto_options']) && $config['auto_options'] === true) {
9✔
1094
            $service->setAutoOptions();
7✔
1095
        }
1096
        if (isset($config['auto_methods']) && $config['auto_methods'] === true) {
9✔
1097
            $service->setAutoMethods();
7✔
1098
        }
1099
        if (!empty($config['placeholders'])) {
9✔
1100
            $service->addPlaceholder($config['placeholders']);
7✔
1101
        }
1102
        if ($requireFiles && isset($config['files'])) {
9✔
1103
            static::requireRouterFiles($config['files'], $service);
6✔
1104
        }
1105
        if (isset($config['callback'])) {
8✔
1106
            $config['callback']($service);
7✔
1107
        }
1108
        return $service;
8✔
1109
    }
1110

1111
    /**
1112
     * Load files that set the routes.
1113
     *
1114
     * @param array<string> $files The path of the router files
1115
     * @param Router $router
1116
     */
1117
    protected static function requireRouterFiles(array $files, Router $router) : void
1118
    {
1119
        foreach ($files as $file) {
8✔
1120
            if (!\is_file($file)) {
8✔
1121
                throw new LogicException('Invalid router file: ' . $file);
1✔
1122
            }
1123
            Isolation::require($file, ['router' => $router]);
7✔
1124
        }
1125
    }
1126

1127
    /**
1128
     * Get a request service.
1129
     *
1130
     * @param string $instance The request instance name
1131
     *
1132
     * @return Request
1133
     */
1134
    public static function request(string $instance = 'default') : Request
1135
    {
1136
        $service = static::getService('request', $instance);
14✔
1137
        if ($service) {
14✔
1138
            return $service; // @phpstan-ignore-line
9✔
1139
        }
1140
        if (static::isDebugging()) {
14✔
1141
            $start = \microtime(true);
3✔
1142
            $service = static::setRequest($instance);
3✔
1143
            $end = \microtime(true);
3✔
1144
            $collector = new HTTPCollector($instance);
3✔
1145
            $collector->setRequest($service);
3✔
1146
            $collection = static::debugger()->getCollection('HTTP')
3✔
1147
                ?? new HTTPCollection('HTTP');
3✔
1148
            $collection->addCollector($collector);
3✔
1149
            static::debugger()->addCollection($collection);
3✔
1150
            static::addDebugData('request', $instance, $start, $end);
3✔
1151
            return $service;
3✔
1152
        }
1153
        return static::setRequest($instance);
11✔
1154
    }
1155

1156
    /**
1157
     * Overrides variables to be set in the $_SERVER super-global when the
1158
     * request is made via the command line.
1159
     *
1160
     * @param array<string,mixed> $vars
1161
     */
1162
    protected static function setServerVars(array $vars = []) : void
1163
    {
1164
        $vars = \array_replace(static::$defaultServerVars, $vars);
14✔
1165
        foreach ($vars as $key => $value) {
14✔
1166
            $_SERVER[$key] ??= $value;
14✔
1167
        }
1168
    }
1169

1170
    /**
1171
     * Set a request service.
1172
     *
1173
     * @param string $instance The request instance name
1174
     *
1175
     * @return Request
1176
     */
1177
    protected static function setRequest(string $instance) : Request
1178
    {
1179
        $config = static::config()->get('request', $instance);
14✔
1180
        if (static::isCli()) {
14✔
1181
            static::setServerVars($config['server_vars'] ?? []);
7✔
1182
        }
1183
        $service = new Request($config['allowed_hosts'] ?? []);
14✔
1184
        if (isset($config['force_https']) && $config['force_https'] === true) {
14✔
1185
            $service->forceHttps();
1✔
1186
        }
1187
        if (isset($config['json_flags'])) {
14✔
1188
            $service->setJsonFlags($config['json_flags']);
12✔
1189
        }
1190
        return static::setService('request', $service, $instance);
14✔
1191
    }
1192

1193
    /**
1194
     * Get a response service.
1195
     *
1196
     * @param string $instance The response instance name
1197
     *
1198
     * @return Response
1199
     */
1200
    public static function response(string $instance = 'default') : Response
1201
    {
1202
        $service = static::getService('response', $instance);
11✔
1203
        if ($service) {
11✔
1204
            return $service; // @phpstan-ignore-line
4✔
1205
        }
1206
        if (static::isDebugging()) {
11✔
1207
            $start = \microtime(true);
3✔
1208
            $service = static::setResponse($instance);
3✔
1209
            $end = \microtime(true);
3✔
1210
            $collection = static::debugger()->getCollection('HTTP');
3✔
1211
            foreach ($collection->getCollectors() as $collector) {
3✔
1212
                if ($collector->getName() === $instance) {
3✔
1213
                    $service->setDebugCollector($collector); // @phpstan-ignore-line
3✔
1214
                    break;
3✔
1215
                }
1216
            }
1217
            static::addDebugData('response', $instance, $start, $end);
3✔
1218
            return $service;
3✔
1219
        }
1220
        return static::setResponse($instance);
8✔
1221
    }
1222

1223
    /**
1224
     * Set a response service.
1225
     *
1226
     * @param string $instance The response instance name
1227
     *
1228
     * @return Response
1229
     */
1230
    protected static function setResponse(string $instance) : Response
1231
    {
1232
        $config = static::config()->get('response', $instance);
11✔
1233
        $service = new Response(static::request($config['request_instance'] ?? 'default'));
11✔
1234
        if (!empty($config['headers'])) {
11✔
1235
            $service->setHeaders($config['headers']);
9✔
1236
        }
1237
        if (!empty($config['auto_etag'])) {
11✔
1238
            $service->setAutoEtag(
9✔
1239
                $config['auto_etag']['active'] ?? true,
9✔
1240
                $config['auto_etag']['hash_algo'] ?? null
9✔
1241
            );
9✔
1242
        }
1243
        if (isset($config['auto_language']) && $config['auto_language'] === true) {
11✔
1244
            $service->setContentLanguage(
9✔
1245
                static::language($config['language_instance'] ?? 'default')->getCurrentLocale()
9✔
1246
            );
9✔
1247
        }
1248
        if (isset($config['cache'])) {
11✔
1249
            $config['cache'] === false
10✔
1250
                ? $service->setNoCache()
1✔
1251
                : $service->setCache($config['cache']['seconds'], $config['cache']['public'] ?? false);
9✔
1252
        }
1253
        if (!empty($config['csp'])) {
11✔
1254
            $service->setCsp(new CSP($config['csp']));
9✔
1255
        }
1256
        if (!empty($config['csp_report_only'])) {
11✔
1257
            $service->setCspReportOnly(new CSP($config['csp_report_only']));
9✔
1258
        }
1259
        if (isset($config['json_flags'])) {
11✔
1260
            $service->setJsonFlags($config['json_flags']);
9✔
1261
        }
1262
        if (isset($config['replace_headers'])) {
11✔
1263
            $service->setReplaceHeaders($config['replace_headers']);
9✔
1264
        }
1265
        return static::setService('response', $service, $instance);
11✔
1266
    }
1267

1268
    /**
1269
     * Get a session service.
1270
     *
1271
     * @param string $instance The session instance name
1272
     *
1273
     * @return Session
1274
     */
1275
    public static function session(string $instance = 'default') : Session
1276
    {
1277
        $service = static::getService('session', $instance);
5✔
1278
        if ($service) {
5✔
1279
            return $service; // @phpstan-ignore-line
5✔
1280
        }
1281
        if (static::isDebugging()) {
5✔
1282
            $start = \microtime(true);
1✔
1283
            $service = static::setSession($instance);
1✔
1284
            $end = \microtime(true);
1✔
1285
            $collector = new SessionCollector($instance);
1✔
1286
            $service->setDebugCollector($collector);
1✔
1287
            $collection = static::debugger()->getCollection('Session')
1✔
1288
                ?? new SessionCollection('Session');
1✔
1289
            $collection->addCollector($collector);
1✔
1290
            static::debugger()->addCollection($collection);
1✔
1291
            static::addDebugData('session', $instance, $start, $end);
1✔
1292
            return $service;
1✔
1293
        }
1294
        return static::setSession($instance);
4✔
1295
    }
1296

1297
    /**
1298
     * Set a session service.
1299
     *
1300
     * @param string $instance The session instance name
1301
     *
1302
     * @return Session
1303
     */
1304
    protected static function setSession(string $instance) : Session
1305
    {
1306
        $config = static::config()->get('session', $instance);
5✔
1307
        if (isset($config['save_handler']['class'])) {
5✔
1308
            $logger = null;
3✔
1309
            if (isset($config['logger_instance'])) {
3✔
1310
                $logger = static::logger($config['logger_instance']);
1✔
1311
            }
1312
            $saveHandler = new $config['save_handler']['class'](
3✔
1313
                $config['save_handler']['config'] ?? [],
3✔
1314
                $logger
3✔
1315
            );
3✔
1316
            if ($saveHandler instanceof DatabaseHandler
3✔
1317
                && isset($config['save_handler']['database_instance'])
3✔
1318
            ) {
1319
                $saveHandler->setDatabase(
1✔
1320
                    static::database($config['save_handler']['database_instance'])
1✔
1321
                );
1✔
1322
            }
1323
        }
1324
        // @phpstan-ignore-next-line
1325
        $service = new Session($config['options'] ?? [], $saveHandler ?? null);
5✔
1326
        if (isset($config['auto_start']) && $config['auto_start'] === true) {
5✔
1327
            $service->start();
2✔
1328
        }
1329
        return static::setService('session', $service, $instance);
5✔
1330
    }
1331

1332
    /**
1333
     * Get a validation service.
1334
     *
1335
     * @param string $instance The validation instance name
1336
     *
1337
     * @return Validation
1338
     */
1339
    public static function validation(string $instance = 'default') : Validation
1340
    {
1341
        $service = static::getService('validation', $instance);
21✔
1342
        if ($service) {
21✔
1343
            return $service; // @phpstan-ignore-line
2✔
1344
        }
1345
        if (static::isDebugging()) {
21✔
1346
            $start = \microtime(true);
2✔
1347
            $service = static::setValidation($instance);
2✔
1348
            $end = \microtime(true);
2✔
1349
            $collector = new ValidationCollector($instance);
2✔
1350
            $service->setDebugCollector($collector);
2✔
1351
            $collection = static::debugger()->getCollection('Validation')
2✔
1352
                ?? new ValidationCollection('Validation');
2✔
1353
            $collection->addCollector($collector);
2✔
1354
            static::debugger()->addCollection($collection);
2✔
1355
            static::addDebugData('validation', $instance, $start, $end);
2✔
1356
            return $service;
2✔
1357
        }
1358
        return static::setValidation($instance);
19✔
1359
    }
1360

1361
    /**
1362
     * Set a validation service.
1363
     *
1364
     * @param string $instance The validation instance name
1365
     *
1366
     * @return Validation
1367
     */
1368
    protected static function setValidation(string $instance) : Validation
1369
    {
1370
        $config = static::config()->get('validation', $instance);
21✔
1371
        $language = null;
21✔
1372
        if (isset($config['language_instance'])) {
21✔
1373
            $language = static::language($config['language_instance']);
20✔
1374
        }
1375
        return static::setService(
21✔
1376
            'validation',
21✔
1377
            new Validation(
21✔
1378
                $config['validators'] ?? [
21✔
1379
                Validator::class,
21✔
1380
                FilesValidator::class,
21✔
1381
            ],
1382
                $language
21✔
1383
            ),
21✔
1384
            $instance
21✔
1385
        );
21✔
1386
    }
1387

1388
    /**
1389
     * Get a view service.
1390
     *
1391
     * @param string $instance The view instance name
1392
     *
1393
     * @return View
1394
     */
1395
    public static function view(string $instance = 'default') : View
1396
    {
1397
        $service = static::getService('view', $instance);
3✔
1398
        if ($service) {
3✔
1399
            return $service; // @phpstan-ignore-line
1✔
1400
        }
1401
        if (static::isDebugging()) {
3✔
1402
            $start = \microtime(true);
1✔
1403
            $service = static::setView($instance);
1✔
1404
            $service->setInstanceName($instance);
1✔
1405
            $end = \microtime(true);
1✔
1406
            $collector = new ViewsCollector($instance);
1✔
1407
            $service->setDebugCollector($collector);
1✔
1408
            $collection = static::debugger()->getCollection('Views')
1✔
1409
                ?? new ViewsCollection('Views');
1✔
1410
            $collection->addCollector($collector);
1✔
1411
            static::debugger()->addCollection($collection);
1✔
1412
            static::addDebugData('view', $instance, $start, $end);
1✔
1413
            return $service;
1✔
1414
        }
1415
        return static::setView($instance);
2✔
1416
    }
1417

1418
    /**
1419
     * Set a view service.
1420
     *
1421
     * @param string $instance The view instance name
1422
     *
1423
     * @return View
1424
     */
1425
    protected static function setView(string $instance) : View
1426
    {
1427
        $config = static::config()->get('view', $instance);
3✔
1428
        $service = new View($config['base_dir'] ?? null, $config['extension'] ?? '.php');
3✔
1429
        if (isset($config['layout_prefix'])) {
3✔
1430
            $service->setLayoutPrefix($config['layout_prefix']);
3✔
1431
        }
1432
        if (isset($config['include_prefix'])) {
3✔
1433
            $service->setIncludePrefix($config['include_prefix']);
3✔
1434
        }
1435
        if (isset($config['show_debug_comments']) && $config['show_debug_comments'] === false) {
3✔
1436
            $service->disableDebugComments();
3✔
1437
        }
1438
        if (isset($config['throw_exceptions_in_destructor'])) {
3✔
1439
            $service->setThrowExceptionsInDestructor($config['throw_exceptions_in_destructor']);
3✔
1440
        }
1441
        return static::setService('view', $service, $instance);
3✔
1442
    }
1443

1444
    /**
1445
     * Tell if it is a command-line request.
1446
     *
1447
     * @return bool
1448
     */
1449
    public static function isCli() : bool
1450
    {
1451
        if (static::$isCli === null) {
72✔
1452
            static::$isCli = \PHP_SAPI === 'cli' || \defined('STDIN');
65✔
1453
        }
1454
        return static::$isCli;
72✔
1455
    }
1456

1457
    /**
1458
     * Set if it is a CLI request. Used for testing.
1459
     *
1460
     * @param bool $is
1461
     */
1462
    public static function setIsCli(bool $is) : void
1463
    {
1464
        static::$isCli = $is;
9✔
1465
    }
1466

1467
    /**
1468
     * Tell if the App is in debug mode.
1469
     *
1470
     * @return bool
1471
     */
1472
    public static function isDebugging() : bool
1473
    {
1474
        return isset(static::$debugCollector);
109✔
1475
    }
1476

1477
    /**
1478
     * Add services data to the debug collector.
1479
     *
1480
     * @param string $service Service name
1481
     * @param string $instance Service instance name
1482
     * @param float $start Microtime right before setting up the service
1483
     * @param float $end Microtime right after setting up the service
1484
     */
1485
    public static function addDebugData(
1486
        string $service,
1487
        string $instance,
1488
        float $start,
1489
        float $end
1490
    ) : void {
1491
        static::$debugCollector->addData([
11✔
1492
            'service' => $service,
11✔
1493
            'instance' => $instance,
11✔
1494
            'start' => $start,
11✔
1495
            'end' => $end,
11✔
1496
        ]);
11✔
1497
    }
1498
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc