Application::getServiceProvider()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
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
66
/**
67
 * @class Application
68
 * @package Platine\Framework\App
69
 */
70
class Application extends Container
71
{
72
    /**
73
     * The application version
74
     */
75
    public const VERSION = '2.0.0-dev';
76
77
    /**
78
     * The event dispatcher instance
79
     * @var DispatcherInterface
80
     */
81
    protected DispatcherInterface $dispatcher;
82
83
    /**
84
     * The stop watch instance
85
     * @var Watch
86
     */
87
    protected Watch $watch;
88
89
    /**
90
     * The base path for this application
91
     * @var string
92
     */
93
    protected string $basePath = '';
94
95
    /**
96
     * The vendor path
97
     * @var string
98
     */
99
    protected string $vendorPath = '';
100
101
    /**
102
     * The Application path
103
     * @var string
104
     */
105
    protected string $appPath = '';
106
107
    /**
108
     * The application root path
109
     * @var string
110
     */
111
    protected string $rootPath = '';
112
113
    /**
114
     * The application configuration path
115
     * @var string
116
     */
117
    protected string $configPath = 'config';
118
119
    /**
120
     * The application storage path
121
     * @var string
122
     */
123
    protected string $storagePath = 'storage';
124
125
    /**
126
     * The custom app name space
127
     * @var string
128
     */
129
    protected string $namespace = '';
130
131
    /**
132
     * The list of service providers
133
     * @var array<string, ServiceProvider>
134
     */
135
    protected array $providers = [];
136
137
    /**
138
     * Whether the system already booted
139
     * @var bool
140
     */
141
    protected bool $booted = false;
142
143
    /**
144
     * The application environment
145
     * @var string
146
     */
147
    protected string $env = 'dev';
148
149
    /**
150
     * The environment file name
151
     * @var string
152
     */
153
    protected string $environmentFile = '.env';
154
155
    /**
156
     * Create new instance
157
     * @param string $basePath
158
     */
159
    public function __construct(string $basePath = '')
160
    {
161
        parent::__construct();
162
163
        $this->basePath = $basePath;
164
        $this->watch = new Watch();
165
        $this->loadCoreServiceProviders();
166
167
        $this->dispatcher = $this->get(DispatcherInterface::class);
168
    }
169
170
    /**
171
     * Return the application version
172
     * @return string
173
     */
174
    public function version(): string
175
    {
176
        return self::VERSION;
177
    }
178
179
    /**
180
     * Return the environment file
181
     * @return string
182
     */
183
    public function getEnvironmentFile(): string
184
    {
185
        return $this->environmentFile;
186
    }
187
188
189
    /**
190
     * Set the environment file
191
     * @param string $environmentFile
192
     * @return $this
193
     */
194
    public function setEnvironmentFile(string $environmentFile): self
195
    {
196
        $this->environmentFile = $environmentFile;
197
198
        return $this;
199
    }
200
201
    /**
202
     * Return the root path
203
     * @return string
204
     */
205
    public function getRootPath(): string
206
    {
207
        return $this->rootPath;
208
    }
209
210
    /**
211
     * Set root path
212
     * @param string $rootPath
213
     * @return $this
214
     */
215
    public function setRootPath(string $rootPath): self
216
    {
217
        $this->rootPath = $rootPath;
218
219
        return $this;
220
    }
221
222
    /**
223
     * Return the application name space
224
     * @return string
225
     */
226
    public function getNamespace(): string
227
    {
228
        return $this->namespace;
229
    }
230
231
    /**
232
     * Set the application name space
233
     * @param string $namespace
234
     * @return $this
235
     */
236
    public function setNamespace(string $namespace): self
237
    {
238
        $this->namespace = $namespace;
239
        return $this;
240
    }
241
242
    /**
243
     * Return the current environment
244
     * @return string
245
     */
246
    public function getEnvironment(): string
247
    {
248
        return $this->env;
249
    }
250
251
    /**
252
     * Set the environment
253
     * @param string $env
254
     * @return $this
255
     */
256
    public function setEnvironment(string $env): self
257
    {
258
        $this->env = $env;
259
260
        return $this;
261
    }
262
263
    /**
264
     * Return the storage path
265
     * @return string
266
     */
267
    public function getStoragePath(): string
268
    {
269
        return $this->storagePath;
270
    }
271
272
    /**
273
     * Set the storage path
274
     * @param string $storagePath
275
     * @return $this
276
     */
277
    public function setStoragePath(string $storagePath): self
278
    {
279
        $this->storagePath = $storagePath;
280
281
        return $this;
282
    }
283
284
285
    /**
286
     * Return the vendor path
287
     * @return string
288
     */
289
    public function getVendorPath(): string
290
    {
291
        return $this->vendorPath;
292
    }
293
294
    /**
295
     * Set vendor path
296
     * @param string $vendorPath
297
     * @return $this
298
     */
299
    public function setVendorPath(string $vendorPath): self
300
    {
301
        $this->vendorPath = $vendorPath;
302
303
        return $this;
304
    }
305
306
     /**
307
     * Return the application root path
308
     * @return string
309
     */
310
    public function getAppPath(): string
311
    {
312
        return $this->appPath;
313
    }
314
315
    /**
316
     * Set Application path
317
     * @param string $appPath
318
     * @return $this
319
     */
320
    public function setAppPath(string $appPath): self
321
    {
322
        $this->appPath = $appPath;
323
324
        return $this;
325
    }
326
327
    /**
328
     * Return the application base path
329
     * @return string
330
     */
331
    public function getBasePath(): string
332
    {
333
        return $this->basePath;
334
    }
335
336
    /**
337
     * Set the application base path
338
     * @param string $basePath
339
     * @return $this
340
     */
341
    public function setBasePath(string $basePath): self
342
    {
343
        $this->basePath = $basePath;
344
345
        return $this;
346
    }
347
348
    /**
349
     * Return the application configuration path
350
     * @return string
351
     */
352
    public function getConfigPath(): string
353
    {
354
        return $this->configPath;
355
    }
356
357
    /**
358
     * Set the application configuration path
359
     * @param string $configPath
360
     * @return $this
361
     */
362
    public function setConfigPath(string $configPath): self
363
    {
364
        $this->configPath = $configPath;
365
366
        return $this;
367
    }
368
369
    /**
370
     * Return the watch instance
371
     * @return Watch
372
     */
373
    public function watch(): Watch
374
    {
375
        return $this->watch;
376
    }
377
378
    /**
379
     * Return the current maintenance driver
380
     * @return MaintenanceDriverInterface
381
     */
382
    public function maintenance(): MaintenanceDriverInterface
383
    {
384
        return $this->get(MaintenanceDriverInterface::class);
385
    }
386
387
    /**
388
     * Whether the application is in maintenance
389
     * @return bool
390
     */
391
    public function isInMaintenance(): bool
392
    {
393
        return $this->maintenance()->active();
394
    }
395
396
    /**
397
     * Dispatches an event to all registered listeners.
398
     * @param  string|EventInterface $eventName the name of event
399
     * of instance of EventInterface
400
     * @param  EventInterface|null $event  the instance of EventInterface or null
401
     * @return EventInterface
402
     */
403
    public function dispatch(
404
        string|EventInterface $eventName,
405
        ?EventInterface $event = null
406
    ): EventInterface {
407
        return $this->dispatcher->dispatch($eventName, $event);
408
    }
409
410
    /**
411
     * Register a listener for the given event.
412
     *
413
     * @param string $eventName the name of event
414
     * @param ListenerInterface|callable|string $listener the Listener
415
     * interface or any callable
416
     * @param int $priority the listener execution priority
417
     * @return $this
418
     */
419
    public function listen(
420
        string $eventName,
421
        ListenerInterface|callable|string $listener,
422
        int $priority = DispatcherInterface::PRIORITY_DEFAULT
423
    ): self {
424
        if (is_string($listener)) {
425
            $listener = $this->createListener($listener);
426
        }
427
        $this->dispatcher->addListener($eventName, $listener, $priority);
428
429
        return $this;
430
    }
431
432
    /**
433
     * Add event subscriber
434
     * @param SubscriberInterface $subscriber
435
     * @return $this
436
     */
437
    public function subscribe(SubscriberInterface $subscriber): self
438
    {
439
        $this->dispatcher->addSubscriber($subscriber);
440
441
        return $this;
442
    }
443
444
    /**
445
     * Return the list of providers
446
     * @return array<string, ServiceProvider>
447
     */
448
    public function getProviders(): array
449
    {
450
        return $this->providers;
451
    }
452
453
    /**
454
     * Return the list of service providers commands
455
     * @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...
456
     */
457
    public function getProvidersCommands(): array
458
    {
459
        $commands = [];
460
        foreach ($this->providers as /** @var ServiceProvider $provider */ $provider) {
461
            $commands = array_merge($commands, $provider->getCommands());
462
        }
463
464
        return $commands;
465
    }
466
467
    /**
468
     * Return the list of service providers tasks
469
     * @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...
470
     */
471
    public function getProvidersTasks(): array
472
    {
473
        $tasks = [];
474
        foreach ($this->providers as /** @var ServiceProvider $provider */ $provider) {
475
            $tasks = array_merge($tasks, $provider->getTasks());
476
        }
477
478
        return $tasks;
479
    }
480
481
    /**
482
     * Boot the application
483
     * @return void
484
     */
485
    public function boot(): void
486
    {
487
        if ($this->booted) {
488
            return;
489
        }
490
491
        foreach ($this->providers as $provider) {
492
            $this->bootServiceProvider($provider);
493
        }
494
495
        $this->booted = true;
496
    }
497
498
    /**
499
     * Register the service provider
500
     * @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...
501
     * @param bool $force whether to force registration of provider
502
     * if already loaded
503
     * @return ServiceProvider
504
     */
505
    public function registerServiceProvider(
506
        string|ServiceProvider $provider,
507
        bool $force = false
508
    ): ServiceProvider {
509
        $registered = $this->getServiceProvider($provider);
510
        if ($registered && !$force) {
511
            return $registered;
512
        }
513
514
        if (is_string($provider)) {
515
            $provider = $this->createServiceProvider($provider);
516
        }
517
518
        $provider->register();
519
520
        $this->markProviderAsRegistered($provider);
521
522
        if ($this->booted) {
523
            $this->bootServiceProvider($provider);
524
        }
525
526
        return $provider;
527
    }
528
529
    /**
530
     * Return the registered service provider if exist
531
     * @param string|ServiceProvider $provider
532
     * @return ServiceProvider|null
533
     */
534
    public function getServiceProvider(string|ServiceProvider $provider): ?ServiceProvider
535
    {
536
        $name = is_string($provider)
537
                ? $provider
538
                : get_class($provider);
539
540
        return $this->providers[$name] ?? null;
541
    }
542
543
    /**
544
     * Load configured service providers
545
     * @return void
546
     */
547
    public function registerConfiguredServiceProviders(): void
548
    {
549
        /** @template T @var Config<T> $config */
550
        $config = $this->get(Config::class);
551
552
        /** @var class-string<ServiceProvider>[] $providers */
553
        $providers = $config->get('providers', []);
554
        $this->watch->start('register-service-providers');
555
        foreach ($providers as $provider) {
556
            $this->registerServiceProvider($provider);
557
        }
558
        $this->watch->stop('register-service-providers');
559
    }
560
561
    /**
562
     * Load configured events and listeners
563
     * @return void
564
     */
565
    public function registerConfiguredEvents(): void
566
    {
567
        /** @template T @var Config<T> $config */
568
        $config = $this->get(Config::class);
569
570
        /** @var array<string, string[]> $events */
571
        $events = $config->get('events', []);
572
        $this->watch->start('register-event-listeners');
573
        foreach ($events as $eventName => $listeners) {
574
            foreach ($listeners as $listener) {
575
                $this->listen($eventName, $listener);
576
            }
577
        }
578
        $this->watch->stop('register-event-listeners');
579
    }
580
581
    /**
582
     * Load the application configuration
583
     * @return void
584
     */
585
    public function registerConfiguration(): void
586
    {
587
        $loader = new FileLoader($this->getConfigPath());
588
        $config = new Config($loader, $this->env);
589
        $this->instance($loader);
590
        $this->instance($config);
591
592
        date_default_timezone_set($config->get('app.timezone', 'UTC'));
593
594
        //Set the envirnoment
595
        $this->setEnvironment($config->get('app.env', 'dev'));
596
    }
597
598
    /**
599
     * Load the environment variables if the file exists
600
     * @return void
601
     */
602
    public function registerEnvironmentVariables(): void
603
    {
604
        $this->watch->start('register-environment-variables');
605
        if (!empty($this->rootPath)) {
606
            $path = Path::normalizePath($this->rootPath);
607
            $file = rtrim($path, DIRECTORY_SEPARATOR)
608
                    . DIRECTORY_SEPARATOR . $this->environmentFile;
609
610
            if (is_file($file)) {
611
                (new Loader())
612
                    ->load(
613
                        $file,
614
                        false,
615
                        Loader::ENV | Loader::PUTENV
616
                    );
617
            }
618
        }
619
        $this->watch->stop('register-environment-variables');
620
    }
621
622
    /**
623
     * Create service provider
624
     * @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...
625
     * @return ServiceProvider
626
     */
627
    protected function createServiceProvider(string $provider): ServiceProvider
628
    {
629
        return new $provider($this);
630
    }
631
632
    /**
633
     * Boot the given service provider
634
     * @param ServiceProvider $provider
635
     * @return void
636
     */
637
    protected function bootServiceProvider(ServiceProvider $provider): void
638
    {
639
        $provider->boot();
640
    }
641
642
    /**
643
     * Set the given service provider as registered
644
     * @param ServiceProvider $provider
645
     * @return void
646
     */
647
    protected function markProviderAsRegistered(ServiceProvider $provider): void
648
    {
649
        $this->providers[get_class($provider)] = $provider;
650
    }
651
652
    /**
653
     * Load framework core service providers
654
     * @return void
655
     */
656
    protected function loadCoreServiceProviders(): void
657
    {
658
        $this->watch->start('load-core-service-provider');
659
        $this->registerServiceProvider(new BaseServiceProvider($this));
660
        $this->registerServiceProvider(new EventServiceProvider($this));
661
        $this->watch->stop('load-core-service-provider');
662
    }
663
664
    /**
665
     * Create listener using the container or direct class instance
666
     * @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...
667
     * @return ListenerInterface
668
     */
669
    protected function createListener(string $listener): ListenerInterface
670
    {
671
        if ($this->has($listener)) {
672
            return $this->get($listener);
673
        }
674
675
        if (class_exists($listener)) {
676
            /** @var ListenerInterface $o */
677
            $o = new $listener();
678
679
            return $o;
680
        }
681
682
        throw new InvalidArgumentException(sprintf(
683
            'Can not resolve the listener class [%s], check if this is the'
684
                . ' identifier of container or class exists',
685
            $listener
686
        ));
687
    }
688
}
689