Test Failed
Push — develop ( 13062d...dd4b8b )
by nguereza
04:03
created

Application::reference()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant
7
 * PHP Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file Application.php
34
 *
35
 *  The Platine Application class
36
 *
37
 *  @package    Platine\Framework\App
38
 *  @author Platine Developers team
39
 *  @copyright  Copyright (c) 2020
40
 *  @license    http://opensource.org/licenses/MIT  MIT License
41
 *  @link   https://www.platine-php.com
42
 *  @version 1.0.0
43
 *  @filesource
44
 */
45
46
declare(strict_types=1);
47
48
namespace Platine\Framework\App;
49
50
use InvalidArgumentException;
51
use Platine\Config\Config;
52
use Platine\Config\FileLoader;
53
use Platine\Container\Container;
54
use Platine\Event\DispatcherInterface;
55
use Platine\Event\EventInterface;
56
use Platine\Event\Listener\ListenerInterface;
57
use Platine\Event\SubscriberInterface;
58
use Platine\Framework\Env\Loader;
59
use Platine\Framework\Helper\Timer\Watch;
60
use Platine\Framework\Http\Maintenance\MaintenanceDriverInterface;
61
use Platine\Framework\Service\Provider\BaseServiceProvider;
62
use Platine\Framework\Service\Provider\EventServiceProvider;
63
use Platine\Framework\Service\ServiceProvider;
64
use Platine\Stdlib\Helper\Path;
65
use Platine\Stdlib\Helper\Str;
66
67
/**
68
 * @class Application
69
 * @package Platine\Framework\App
70
 */
71
class Application extends Container
72
{
73
    /**
74
     * The application version
75
     */
76
    public const VERSION = '2.0.0-dev';
77
78
    /**
79
     * The event dispatcher instance
80
     * @var DispatcherInterface
81
     */
82
    protected DispatcherInterface $dispatcher;
83
84
    /**
85
     * The stop watch instance
86
     * @var Watch
87
     */
88
    protected Watch $watch;
89
90
    /**
91
     * The base path for this application
92
     * @var string
93
     */
94
    protected string $basePath = '';
95
96
    /**
97
     * The vendor path
98
     * @var string
99
     */
100
    protected string $vendorPath = '';
101
102
    /**
103
     * The Application path
104
     * @var string
105
     */
106
    protected string $appPath = '';
107
108
    /**
109
     * The application root path
110
     * @var string
111
     */
112
    protected string $rootPath = '';
113
114
    /**
115
     * The application configuration path
116
     * @var string
117
     */
118
    protected string $configPath = 'config';
119
120
    /**
121
     * The application storage path
122
     * @var string
123
     */
124
    protected string $storagePath = 'storage';
125
126
    /**
127
     * The custom app name space
128
     * @var string
129
     */
130
    protected string $namespace = '';
131
132
    /**
133
     * The list of service providers
134
     * @var array<string, ServiceProvider>
135
     */
136
    protected array $providers = [];
137
138
    /**
139
     * Whether the system already booted
140
     * @var bool
141
     */
142
    protected bool $booted = false;
143
144
    /**
145
     * The application environment
146
     * @var string
147
     */
148
    protected string $env = 'dev';
149
150
    /**
151
     * The environment file name
152
     * @var string
153
     */
154
    protected string $environmentFile = '.env';
155
156
    /**
157
     * The application instance reference useful to track the handling
158
     * of each request
159
     * @var string
160
     */
161
    protected string $reference;
162
163
    /**
164
     * Create new instance
165
     * @param string $basePath
166
     */
167
    public function __construct(string $basePath = '')
168
    {
169
        parent::__construct();
170
        $this->reference = Str::randomString('hexdec');
171
172
        $this->basePath = $basePath;
173
        $this->watch = new Watch();
174
175
        $watchKey = sprintf('request-handling-%s', $this->reference);
176
        $this->watch()->start($watchKey);
177
        $this->loadCoreServiceProviders();
178
179
        $this->dispatcher = $this->get(DispatcherInterface::class);
180
    }
181
182
    /**
183
     * Return the application version
184
     * @return string
185
     */
186
    public function version(): string
187
    {
188
        return self::VERSION;
189
    }
190
191
    /**
192
     * Return the application instance reference
193
     * @return string
194
     */
195
    public function reference(): string
196
    {
197
        return $this->reference;
198
    }
199
200
    /**
201
     * Return the environment file
202
     * @return string
203
     */
204
    public function getEnvironmentFile(): string
205
    {
206
        return $this->environmentFile;
207
    }
208
209
210
    /**
211
     * Set the environment file
212
     * @param string $environmentFile
213
     * @return $this
214
     */
215
    public function setEnvironmentFile(string $environmentFile): self
216
    {
217
        $this->environmentFile = $environmentFile;
218
219
        return $this;
220
    }
221
222
    /**
223
     * Return the root path
224
     * @return string
225
     */
226
    public function getRootPath(): string
227
    {
228
        return $this->rootPath;
229
    }
230
231
    /**
232
     * Set root path
233
     * @param string $rootPath
234
     * @return $this
235
     */
236
    public function setRootPath(string $rootPath): self
237
    {
238
        $this->rootPath = $rootPath;
239
240
        return $this;
241
    }
242
243
    /**
244
     * Return the application name space
245
     * @return string
246
     */
247
    public function getNamespace(): string
248
    {
249
        return $this->namespace;
250
    }
251
252
    /**
253
     * Set the application name space
254
     * @param string $namespace
255
     * @return $this
256
     */
257
    public function setNamespace(string $namespace): self
258
    {
259
        $this->namespace = $namespace;
260
        return $this;
261
    }
262
263
    /**
264
     * Return the current environment
265
     * @return string
266
     */
267
    public function getEnvironment(): string
268
    {
269
        return $this->env;
270
    }
271
272
    /**
273
     * Set the environment
274
     * @param string $env
275
     * @return $this
276
     */
277
    public function setEnvironment(string $env): self
278
    {
279
        $this->env = $env;
280
281
        return $this;
282
    }
283
284
    /**
285
     * Return the storage path
286
     * @return string
287
     */
288
    public function getStoragePath(): string
289
    {
290
        return $this->storagePath;
291
    }
292
293
    /**
294
     * Set the storage path
295
     * @param string $storagePath
296
     * @return $this
297
     */
298
    public function setStoragePath(string $storagePath): self
299
    {
300
        $this->storagePath = $storagePath;
301
302
        return $this;
303
    }
304
305
306
    /**
307
     * Return the vendor path
308
     * @return string
309
     */
310
    public function getVendorPath(): string
311
    {
312
        return $this->vendorPath;
313
    }
314
315
    /**
316
     * Set vendor path
317
     * @param string $vendorPath
318
     * @return $this
319
     */
320
    public function setVendorPath(string $vendorPath): self
321
    {
322
        $this->vendorPath = $vendorPath;
323
324
        return $this;
325
    }
326
327
     /**
328
     * Return the application root path
329
     * @return string
330
     */
331
    public function getAppPath(): string
332
    {
333
        return $this->appPath;
334
    }
335
336
    /**
337
     * Set Application path
338
     * @param string $appPath
339
     * @return $this
340
     */
341
    public function setAppPath(string $appPath): self
342
    {
343
        $this->appPath = $appPath;
344
345
        return $this;
346
    }
347
348
    /**
349
     * Return the application base path
350
     * @return string
351
     */
352
    public function getBasePath(): string
353
    {
354
        return $this->basePath;
355
    }
356
357
    /**
358
     * Set the application base path
359
     * @param string $basePath
360
     * @return $this
361
     */
362
    public function setBasePath(string $basePath): self
363
    {
364
        $this->basePath = $basePath;
365
366
        return $this;
367
    }
368
369
    /**
370
     * Return the application configuration path
371
     * @return string
372
     */
373
    public function getConfigPath(): string
374
    {
375
        return $this->configPath;
376
    }
377
378
    /**
379
     * Set the application configuration path
380
     * @param string $configPath
381
     * @return $this
382
     */
383
    public function setConfigPath(string $configPath): self
384
    {
385
        $this->configPath = $configPath;
386
387
        return $this;
388
    }
389
390
    /**
391
     * Return the watch instance
392
     * @return Watch
393
     */
394
    public function watch(): Watch
395
    {
396
        return $this->watch;
397
    }
398
399
    /**
400
     * Return the current maintenance driver
401
     * @return MaintenanceDriverInterface
402
     */
403
    public function maintenance(): MaintenanceDriverInterface
404
    {
405
        return $this->get(MaintenanceDriverInterface::class);
406
    }
407
408
    /**
409
     * Whether the application is in maintenance
410
     * @return bool
411
     */
412
    public function isInMaintenance(): bool
413
    {
414
        return $this->maintenance()->active();
415
    }
416
417
    /**
418
     * Dispatches an event to all registered listeners.
419
     * @param  string|EventInterface $eventName the name of event
420
     * of instance of EventInterface
421
     * @param  EventInterface|null $event  the instance of EventInterface or null
422
     * @return EventInterface
423
     */
424
    public function dispatch(
425
        string|EventInterface $eventName,
426
        ?EventInterface $event = null
427
    ): EventInterface {
428
        return $this->dispatcher->dispatch($eventName, $event);
429
    }
430
431
    /**
432
     * Register a listener for the given event.
433
     *
434
     * @param string $eventName the name of event
435
     * @param ListenerInterface|callable|string $listener the Listener
436
     * interface or any callable
437
     * @param int $priority the listener execution priority
438
     * @return $this
439
     */
440
    public function listen(
441
        string $eventName,
442
        ListenerInterface|callable|string $listener,
443
        int $priority = DispatcherInterface::PRIORITY_DEFAULT
444
    ): self {
445
        if (is_string($listener)) {
446
            $listener = $this->createListener($listener);
447
        }
448
        $this->dispatcher->addListener($eventName, $listener, $priority);
449
450
        return $this;
451
    }
452
453
    /**
454
     * Add event subscriber
455
     * @param SubscriberInterface $subscriber
456
     * @return $this
457
     */
458
    public function subscribe(SubscriberInterface $subscriber): self
459
    {
460
        $this->dispatcher->addSubscriber($subscriber);
461
462
        return $this;
463
    }
464
465
    /**
466
     * Return the list of providers
467
     * @return array<string, ServiceProvider>
468
     */
469
    public function getProviders(): array
470
    {
471
        return $this->providers;
472
    }
473
474
    /**
475
     * Return the list of service providers commands
476
     * @return array<class-string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string>.
Loading history...
477
     */
478
    public function getProvidersCommands(): array
479
    {
480
        $commands = [];
481
        foreach ($this->providers as /** @var ServiceProvider $provider */ $provider) {
482
            $commands = array_merge($commands, $provider->getCommands());
483
        }
484
485
        return $commands;
486
    }
487
488
    /**
489
     * Return the list of service providers tasks
490
     * @return array<class-string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string>.
Loading history...
491
     */
492
    public function getProvidersTasks(): array
493
    {
494
        $tasks = [];
495
        foreach ($this->providers as /** @var ServiceProvider $provider */ $provider) {
496
            $tasks = array_merge($tasks, $provider->getTasks());
497
        }
498
499
        return $tasks;
500
    }
501
502
    /**
503
     * Boot the application
504
     * @return void
505
     */
506
    public function boot(): void
507
    {
508
        if ($this->booted) {
509
            return;
510
        }
511
512
        foreach ($this->providers as $provider) {
513
            $this->bootServiceProvider($provider);
514
        }
515
516
        $this->booted = true;
517
    }
518
519
    /**
520
     * Register the service provider
521
     * @param class-string<ServiceProvider>|ServiceProvider $provider
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<ServiceProvider>|ServiceProvider at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<ServiceProvider>|ServiceProvider.
Loading history...
522
     * @param bool $force whether to force registration of provider
523
     * if already loaded
524
     * @return ServiceProvider
525
     */
526
    public function registerServiceProvider(
527
        string|ServiceProvider $provider,
528
        bool $force = false
529
    ): ServiceProvider {
530
        $registered = $this->getServiceProvider($provider);
531
        if ($registered && !$force) {
532
            return $registered;
533
        }
534
535
        if (is_string($provider)) {
536
            $provider = $this->createServiceProvider($provider);
537
        }
538
539
        $provider->register();
540
541
        $this->markProviderAsRegistered($provider);
542
543
        if ($this->booted) {
544
            $this->bootServiceProvider($provider);
545
        }
546
547
        return $provider;
548
    }
549
550
    /**
551
     * Return the registered service provider if exist
552
     * @param string|ServiceProvider $provider
553
     * @return ServiceProvider|null
554
     */
555
    public function getServiceProvider(string|ServiceProvider $provider): ?ServiceProvider
556
    {
557
        $name = is_string($provider)
558
                ? $provider
559
                : get_class($provider);
560
561
        return $this->providers[$name] ?? null;
562
    }
563
564
    /**
565
     * Load configured service providers
566
     * @return void
567
     */
568
    public function registerConfiguredServiceProviders(): void
569
    {
570
        /** @template T @var Config<T> $config */
571
        $config = $this->get(Config::class);
572
573
        /** @var class-string<ServiceProvider>[] $providers */
574
        $providers = $config->get('providers', []);
575
        $this->watch->start('register-service-providers');
576
        foreach ($providers as $provider) {
577
            $this->registerServiceProvider($provider);
578
        }
579
        $this->watch->stop('register-service-providers');
580
    }
581
582
    /**
583
     * Load configured events and listeners
584
     * @return void
585
     */
586
    public function registerConfiguredEvents(): void
587
    {
588
        /** @template T @var Config<T> $config */
589
        $config = $this->get(Config::class);
590
591
        /** @var array<string, string[]> $events */
592
        $events = $config->get('events', []);
593
        $this->watch->start('register-event-listeners');
594
        foreach ($events as $eventName => $listeners) {
595
            foreach ($listeners as $listener) {
596
                $this->listen($eventName, $listener);
597
            }
598
        }
599
        $this->watch->stop('register-event-listeners');
600
    }
601
602
    /**
603
     * Load the application configuration
604
     * @return void
605
     */
606
    public function registerConfiguration(): void
607
    {
608
        $loader = new FileLoader($this->getConfigPath());
609
        $config = new Config($loader, $this->env);
610
        $this->instance($loader);
611
        $this->instance($config);
612
613
        date_default_timezone_set($config->get('app.timezone', 'UTC'));
614
615
        //Set the envirnoment
616
        $this->setEnvironment($config->get('app.env', 'dev'));
617
    }
618
619
    /**
620
     * Load the environment variables if the file exists
621
     * @return void
622
     */
623
    public function registerEnvironmentVariables(): void
624
    {
625
        $this->watch->start('register-environment-variables');
626
        if (!empty($this->rootPath)) {
627
            $path = Path::normalizePath($this->rootPath);
628
            $file = rtrim($path, DIRECTORY_SEPARATOR)
629
                    . DIRECTORY_SEPARATOR . $this->environmentFile;
630
631
            if (is_file($file)) {
632
                (new Loader())
633
                    ->load(
634
                        $file,
635
                        false,
636
                        Loader::ENV | Loader::PUTENV
637
                    );
638
            }
639
        }
640
        $this->watch->stop('register-environment-variables');
641
    }
642
643
    /**
644
     * Create service provider
645
     * @param class-string<ServiceProvider> $provider
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<ServiceProvider> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<ServiceProvider>.
Loading history...
646
     * @return ServiceProvider
647
     */
648
    protected function createServiceProvider(string $provider): ServiceProvider
649
    {
650
        return new $provider($this);
651
    }
652
653
    /**
654
     * Boot the given service provider
655
     * @param ServiceProvider $provider
656
     * @return void
657
     */
658
    protected function bootServiceProvider(ServiceProvider $provider): void
659
    {
660
        $provider->boot();
661
    }
662
663
    /**
664
     * Set the given service provider as registered
665
     * @param ServiceProvider $provider
666
     * @return void
667
     */
668
    protected function markProviderAsRegistered(ServiceProvider $provider): void
669
    {
670
        $this->providers[get_class($provider)] = $provider;
671
    }
672
673
    /**
674
     * Load framework core service providers
675
     * @return void
676
     */
677
    protected function loadCoreServiceProviders(): void
678
    {
679
        $this->watch->start('load-core-service-provider');
680
        $this->registerServiceProvider(new BaseServiceProvider($this));
681
        $this->registerServiceProvider(new EventServiceProvider($this));
682
        $this->watch->stop('load-core-service-provider');
683
    }
684
685
    /**
686
     * Create listener using the container or direct class instance
687
     * @param string|class-string<ListenerInterface> $listener
0 ignored issues
show
Documentation Bug introduced by
The doc comment string|class-string<ListenerInterface> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in string|class-string<ListenerInterface>.
Loading history...
688
     * @return ListenerInterface
689
     */
690
    protected function createListener(string $listener): ListenerInterface
691
    {
692
        if ($this->has($listener)) {
693
            return $this->get($listener);
694
        }
695
696
        if (class_exists($listener)) {
697
            /** @var ListenerInterface $o */
698
            $o = new $listener();
699
700
            return $o;
701
        }
702
703
        throw new InvalidArgumentException(sprintf(
704
            'Can not resolve the listener class [%s], check if this is the'
705
                . ' identifier of container or class exists',
706
            $listener
707
        ));
708
    }
709
}
710