Passed
Push — develop ( 027947...5e3b9c )
by nguereza
02:57
created

Application::getServiceProvider()   A

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