Passed
Push — main ( da6fcc...bd4e0a )
by Dimitri
03:54
created

Services::cacheServices()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 8.125

Importance

Changes 0
Metric Value
cc 5
eloc 10
c 0
b 0
f 0
nc 5
nop 0
dl 0
loc 18
ccs 3
cts 6
cp 0.5
crap 8.125
rs 9.6111
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Container;
13
14
use BlitzPHP\Autoloader\Autoloader;
15
use BlitzPHP\Autoloader\Locator;
16
use BlitzPHP\Cache\Cache;
17
use BlitzPHP\Cache\ResponseCache;
18
use BlitzPHP\Config\Config;
19
use BlitzPHP\Contracts\Autoloader\LocatorInterface;
20
use BlitzPHP\Contracts\Database\ConnectionResolverInterface;
21
use BlitzPHP\Contracts\Security\EncrypterInterface;
22
use BlitzPHP\Contracts\Session\CookieManagerInterface;
23
use BlitzPHP\Debug\Logger;
24
use BlitzPHP\Debug\Timer;
25
use BlitzPHP\Debug\Toolbar;
26
use BlitzPHP\Event\EventManager;
27
use BlitzPHP\Filesystem\Filesystem;
28
use BlitzPHP\Filesystem\FilesystemManager;
29
use BlitzPHP\Http\Negotiator;
30
use BlitzPHP\Http\Redirection;
31
use BlitzPHP\Http\Request;
32
use BlitzPHP\Http\Response;
33
use BlitzPHP\Http\ResponseEmitter;
34
use BlitzPHP\Http\ServerRequest;
35
use BlitzPHP\Http\ServerRequestFactory;
36
use BlitzPHP\Http\Uri;
37
use BlitzPHP\Http\UrlGenerator;
38
use BlitzPHP\Mail\Mail;
39
use BlitzPHP\Router\RouteCollection;
40
use BlitzPHP\Router\Router;
41
use BlitzPHP\Security\Encryption\Encryption;
42
use BlitzPHP\Session\Cookie\Cookie;
43
use BlitzPHP\Session\Cookie\CookieManager;
44
use BlitzPHP\Session\Handlers\Database as DatabaseSessionHandler;
45
use BlitzPHP\Session\Handlers\Database\MySQL as MySQLSessionHandler;
46
use BlitzPHP\Session\Handlers\Database\Postgre as PostgreSessionHandler;
47
use BlitzPHP\Session\Store;
48
use BlitzPHP\Translator\Translate;
49
use BlitzPHP\Utilities\Helpers;
50
use BlitzPHP\Utilities\String\Text;
51
use BlitzPHP\View\View;
52
use Psr\Log\LoggerInterface;
53
use stdClass;
54
55
/**
56
 * Service
57
 *
58
 * Les services sont simplement d'autres classes/bibliothèques que le système utilise
59
 * pour faire son travail. Ceci est utilisé par BlitzPHP pour permettre au coeur du
60
 * framework à échanger facilement sans affecter l'utilisation à l'intérieur
61
 * le reste de votre application.
62
 *
63
 * Ceci est utilisé à la place d'un conteneur d'injection de dépendance principalement
64
 * en raison de sa simplicité, qui permet un meilleur entretien à long terme
65
 * des applications construites sur BlitzPHP. Un effet secondaire bonus
66
 * est que les IDE sont capables de déterminer quelle classe vous appelez
67
 * alors qu'avec les conteneurs DI, il n'y a généralement aucun moyen pour eux de le faire.
68
 */
69
class Services
70
{
71
    /**
72
     * Cache des instances des services demander comme instance "partagee".
73
     * La cle est le FQCN du service.
74
     */
75
    protected static array $instances = [];
76
77
    /**
78
     * Objets simulés à tester qui sont renvoyés s'ils existent.
79
     */
80
    protected static array $mocks = [];
81
82
    /**
83
     * Cache d'autres classe de que nous avons trouver via la methode cacheService.
84
     */
85
    protected static array $services = [];
86
87
    /**
88
     * Avons-nous déjà découvert d'autres Services ?
89
     */
90
    protected static bool $discovered = false;
91
92
    /**
93
     * Un cache des noms de classes de services trouvés.
94
     *
95
     * @var array<string>
96
     */
97
    private static array $serviceNames = [];
98
99
    /**
100
     * La classe Autoloader permet de charger les fichiers simplement.
101
     */
102
    public static function autoloader(bool $shared = true): Autoloader
103
    {
104
        if (true === $shared && isset(static::$instances[Autoloader::class])) {
105 50
            return static::$instances[Autoloader::class];
106
        }
107
108 2
        $config  = static::config()->get('autoload');
109 2
        $helpers = array_merge(['url'], ($config['helpers'] ?? []));
110
111 2
        return static::$instances[Autoloader::class] = new Autoloader(/** @scrutinizer ignore-type */ $config, $helpers);
112
    }
113
114
    /**
115
     * La classe de cache fournit un moyen simple de stocker et de récupérer
116
     * données complexes pour plus tard
117
     */
118
    public static function cache(?array $config = null, bool $shared = true): Cache
119
    {
120
        if (empty($config)) {
121 4
            $config = static::config()->get('cache');
122
        }
123
124
        if (true === $shared && isset(static::$instances[Cache::class])) {
125 4
            $instance = static::$instances[Cache::class];
126
            if (empty(func_get_args()[0])) {
127 4
                return $instance;
128
            }
129
130
            return $instance->setConfig($config);
131
        }
132
133 2
        return static::$instances[Cache::class] = new Cache($config);
134
    }
135
136
    /**
137
     * La clase Config offre une api fluide por gerer les configurations de l'application
138
     */
139
    public static function config(bool $shared = true): Config
140
    {
141
        if (true === $shared && isset(static::$instances[Config::class])) {
142 159
            return static::$instances[Config::class];
143
        }
144
145
        return static::$instances[Config::class] = new Config();
146
    }
147
148
    /**
149
     * Conteneur d'injection de dependances
150
     */
151
    public static function container(bool $shared = true): Container
152
    {
153
        if (true === $shared && isset(static::$instances[Container::class])) {
154 53
            return static::$instances[Container::class];
155
        }
156
157
        return static::$instances[Container::class] = new Container();
158
    }
159
160
    /**
161
     * Gestionnaire de cookies
162
     */
163
    public static function cookie(bool $shared = true): CookieManagerInterface
164
    {
165
        if (true === $shared && isset(static::$instances[CookieManager::class])) {
166 2
            return static::$instances[CookieManager::class];
167
        }
168
169 2
        $config = (object) static::config()->get('cookie');
170
171
        return static::$instances[CookieManager::class] = (new CookieManager())->setDefaultPathAndDomain(
172
            $config->path ?: '/',
173
            $config->domain ?: '',
174
            $config->secure ?: false,
175
            $config->httponly ?: true,
176
            $config->samesite ?: 'Lax'
177 2
        );
178
    }
179
180
    /**
181
     * Émetteur de réponse au client
182
     */
183
    public static function emitter(bool $shared = true): ResponseEmitter
184
    {
185
        if (true === $shared && isset(static::$instances[ResponseEmitter::class])) {
186 2
            return static::$instances[ResponseEmitter::class];
187
        }
188
189 2
        return static::$instances[ResponseEmitter::class] = new ResponseEmitter();
190
    }
191
192
    /**
193
     * La classe Encryption fournit un cryptage bidirectionnel.
194
     */
195
    public static function encrypter(?array $config = null, bool $shared = false): EncrypterInterface
196
    {
197
        if (true === $shared && isset(static::$instances[Encryption::class])) {
198
            return static::$instances[Encryption::class];
199
        }
200
201
        $config ??= config('encryption');
202
        $config     = (object) $config;
203
        $encryption = new Encryption($config);
204
205
        return static::$instances[Encryption::class] = $encryption->initialize($config);
206
    }
207
208
    /**
209
     * Gestionnaire d'evenement
210
     */
211
    public static function event(bool $shared = true): EventManager
212
    {
213
        if (true === $shared && isset(static::$instances[EventManager::class])) {
214
            return static::$instances[EventManager::class];
215
        }
216
217
        return static::$instances[EventManager::class] = new EventManager();
218
    }
219
220
    /**
221
     * System de gestion de fichier
222
     */
223
    public static function fs(bool $shared = true): Filesystem
224
    {
225
        if (true === $shared && isset(static::$instances[Filesystem::class])) {
226 4
            return static::$instances[Filesystem::class];
227
        }
228
229 2
        return static::$instances[Filesystem::class] = new Filesystem();
230
    }
231
232
    /**
233
     * Responsable du chargement des traductions des chaînes de langue.
234
     *
235
     * @deprecated 0.9 use translators instead
236
     */
237
    public static function language(?string $locale = null, bool $shared = true): Translate
238
    {
239
        return static::translator($locale, $shared);
240
    }
241
242
    /**
243
     * Le file locator fournit des methodes utilitaire pour chercher les fichiers non-classes
244
     * dans les dossiers de namespace. C'est une excelente methode pour charger les 'vues', 'helpers', et 'libraries'.
245
     */
246
    public static function locator(bool $shared = true): LocatorInterface
247
    {
248
        if ($shared && isset(static::$instances[Locator::class])) {
249 99
            return static::$instances[Locator::class];
250
        }
251
252
        return static::$instances[Locator::class] = new Locator(static::autoloader());
253
    }
254
255
    /**
256
     * La classe Logger est une classe Logging compatible PSR-3 qui prend en charge
257
     * plusieurs gestionnaires qui traitent la journalisation réelle.
258
     *
259
     * @return Logger
260
     */
261
    public static function logger(bool $shared = true): LoggerInterface
262
    {
263
        if ($shared && isset(static::$instances[Logger::class])) {
264 21
            return static::$instances[Logger::class];
265
        }
266
267
        return static::$instances[Logger::class] = new Logger();
268
    }
269
270
    /**
271
     * La classe de mail vous permet d'envoyer par courrier électronique via mail, sendmail, SMTP.
272
     */
273
    public static function mail(?array $config = null, bool $shared = true): Mail
274
    {
275
        if (empty($config)) {
276
            $config = static::config()->get('mail');
277
        }
278
279
        if (true === $shared && isset(static::$instances[Mail::class])) {
280
            /** @var Mail $instance */
281
            $instance = static::$instances[Mail::class];
282
            if (empty(func_get_args()[0])) {
283
                return $instance;
284
            }
285
286
            return $instance->merge($config);
287
        }
288
289
        return static::$instances[Mail::class] = new Mail($config);
290
    }
291
292
    /**
293
     * La classe Input générale modélise une requête HTTP.
294
     */
295
    public static function negotiator(?ServerRequest $request = null, bool $shared = true): Negotiator
296
    {
297
        if (empty($request)) {
298 2
            $request = static::request(true);
299
        }
300
301
        if (true === $shared && isset(static::$instances[Negotiator::class])) {
302 2
            $instance = static::$instances[Negotiator::class];
303
            if (empty(func_get_args()[0])) {
304
                return $instance;
305
            }
306
307
            return $instance->setRequest($request);
308
        }
309
310 2
        return static::$instances[Negotiator::class] = new Negotiator($request);
311
    }
312
313
    /**
314
     * La classe des redirections HTTP
315
     */
316
    public static function redirection(bool $shared = true): Redirection
317
    {
318
        if (true === $shared && isset(static::$instances[Redirection::class])) {
319
            return static::$instances[Redirection::class];
320
        }
321
322
        return static::$instances[Redirection::class] = new Redirection(static::factory(UrlGenerator::class));
323
    }
324
325
    /**
326
     * La classe Resquest modélise une reqûete HTTP.
327
     */
328
    public static function request(bool $shared = true): Request
329
    {
330
        if (true === $shared && isset(static::$instances[Request::class])) {
331 33
            return static::$instances[Request::class];
332
        }
333
334
        return static::$instances[Request::class] = ServerRequestFactory::fromGlobals();
335
    }
336
337
    /**
338
     * La classe Response modélise une réponse HTTP.
339
     */
340
    public static function response(bool $shared = true): Response
341
    {
342
        if (true === $shared && isset(static::$instances[Response::class])) {
343 6
            return static::$instances[Response::class];
344
        }
345
346 1
        return static::$instances[Response::class] = new Response();
347
    }
348
349
    /**
350
     * CacheResponse
351
     */
352
    public static function responsecache(bool $shared = true): ResponseCache
353
    {
354
        if (true === $shared && isset(static::$instances[ResponseCache::class])) {
355
            return static::$instances[ResponseCache::class];
356
        }
357
358
        return static::$instances[ResponseCache::class] = new ResponseCache(static::cache(), /** @scrutinizer ignore-type */ static::config()->get('cache.cache_query_string'));
359
    }
360
361
    /**
362
     * Le service Routes est une classe qui permet de construire facilement
363
     * une collection d'itinéraires.
364
     */
365
    public static function routes(bool $shared = true): RouteCollection
366
    {
367
        if (true === $shared && isset(static::$instances[RouteCollection::class])) {
368 2
            return static::$instances[RouteCollection::class];
369
        }
370
371 8
        return static::$instances[RouteCollection::class] = new RouteCollection(static::locator(), (object) static::config()->get('routing'));
372
    }
373
374
    /**
375
     * La classe Router utilise le tableau de routes d'une RouteCollection et détermine
376
     * le contrôleur et la méthode corrects à exécuter.
377
     */
378
    public static function router(?RouteCollection $routes = null, ?ServerRequest $request = null, bool $shared = true): Router
379
    {
380
        if (true === $shared && isset(static::$instances[Router::class])) {
381 8
            return static::$instances[Router::class];
382
        }
383
384
        if (empty($routes)) {
385 8
            $routes = static::routes(true);
386
        }
387
        if (empty($request)) {
388 8
            $request = static::request(true);
389
        }
390
391 8
        return static::$instances[Router::class] = new Router($routes, $request);
392
    }
393
394
    /**
395
     * Retourne le gestionnaire de session.
396
     */
397
    public static function session(bool $shared = true): Store
398
    {
399
        if (true === $shared && isset(static::$instances[Store::class])) {
400 8
            return static::$instances[Store::class];
401
        }
402
403 19
        $config = static::config()->get('session');
404 19
        $db     = null;
405
406
        if (Text::contains($config['handler'], [DatabaseSessionHandler::class, 'database'])) {
407 19
            $group = $config['group'] ?? static::config()->get('database.connection');
408
            $db    = static::singleton(ConnectionResolverInterface::class)->connection($group);
409
410
            $driver = $db->getPlatform();
411
412
            if (Text::contains($driver, ['mysql', MySQLSessionHandler::class])) {
413
                $config['handler'] = MySQLSessionHandler::class;
414
            } elseif (Text::contains($driver, ['postgre', PostgreSessionHandler::class])) {
415
                $config['handler'] = PostgreSessionHandler::class;
416
            }
417
        }
418
419 19
        Cookie::setDefaults($cookies = /** @scrutinizer ignore-type */ static::config()->get('cookie'));
0 ignored issues
show
Bug introduced by
It seems like $cookies = static::config()->get('cookie') can also be of type null; however, parameter $options of BlitzPHP\Session\Cookie\Cookie::setDefaults() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

419
        Cookie::setDefaults(/** @scrutinizer ignore-type */ $cookies = /** @scrutinizer ignore-type */ static::config()->get('cookie'));
Loading history...
420 19
        $session = new Store((array) $config, (array) $cookies, Helpers::ipAddress());
421 19
        $session->setLogger(static::logger());
422 19
        $session->setDatabase($db);
423
424
        if (session_status() === PHP_SESSION_NONE) {
425 19
            $session->start();
426
        }
427
428 19
        return static::$instances[Store::class] = $session;
429
    }
430
431
    /**
432
     * System de gestion de fichier par disque
433
     */
434
    public static function storage(bool $shared = true): FilesystemManager
435
    {
436
        if ($shared && isset(static::$instances[FilesystemManager::class])) {
437 2
            return static::$instances[FilesystemManager::class];
438
        }
439
440 2
        return static::$instances[FilesystemManager::class] = new FilesystemManager(/** @scrutinizer ignore-type */ static::config()->get('filesystems'));
441
    }
442
443
    /**
444
     * La classe Timer fournit un moyen simple d'évaluer des parties de votre application.
445
     */
446
    public static function timer(bool $shared = true): Timer
447
    {
448
        if (true === $shared && isset(static::$instances[Timer::class])) {
449
            return static::$instances[Timer::class];
450
        }
451
452
        return static::$instances[Timer::class] = new Timer();
453
    }
454
455
    /**
456
     * Renvoie la barre d'outils de débogage.
457
     */
458
    public static function toolbar(?stdClass $config = null, bool $shared = true): Toolbar
459
    {
460
        if ($shared && isset(static::$instances[Toolbar::class])) {
461
            return static::$instances[Toolbar::class];
462
        }
463
464
        $config ??= (object) static::config()->get('toolbar');
465
466
        return static::$instances[Toolbar::class] = new Toolbar($config);
467
    }
468
469
    /**
470
     * Responsable du chargement des traductions des chaînes de langue.
471
     */
472
    public static function translator(?string $locale = null, bool $shared = true): Translate
473
    {
474
        if (empty($locale)) {
475
            if (empty($locale = static::$instances[Translate::class . 'locale'] ?? null)) {
476 2
                $config = static::config()->get('app');
477
478
                if (empty($locale = static::negotiator()->language($config['supported_locales']))) {
479 2
                    $locale = $config['language'];
480
                }
481
482 2
                static::$instances[Translate::class . 'locale'] = $locale;
483
            }
484
        }
485
486
        if (true === $shared && isset(static::$instances[Translate::class])) {
487 34
            return static::$instances[Translate::class]->setLocale($locale);
488
        }
489
490 2
        return static::$instances[Translate::class] = new Translate($locale, static::locator());
491
    }
492
493
    /**
494
     * La classe URI fournit un moyen de modéliser et de manipuler les URI.
495
     */
496
    public static function uri(?string $uri = null, bool $shared = true): Uri
497
    {
498
        if (true === $shared && isset(static::$instances[Uri::class])) {
499
            return static::$instances[Uri::class]->setURI($uri);
500
        }
501
502
        return static::$instances[Uri::class] = new Uri($uri);
503
    }
504
505
    /**
506
     * La classe Renderer est la classe qui affiche réellement un fichier à l'utilisateur.
507
     * La classe View par défaut dans BlitzPHP est intentionnellement simple, mais
508
     * le service peut facilement être remplacé par un moteur de modèle si l'utilisateur en a besoin.
509
     */
510
    public static function viewer(bool $shared = true): View
511
    {
512
        if (true === $shared && isset(static::$instances[View::class])) {
513 4
            return static::$instances[View::class];
514
        }
515
516 2
        return static::$instances[View::class] = new View();
517
    }
518
519
    /**
520
     * Offre la possibilité d'effectuer des appels insensibles à la casse des noms de service.
521
     *
522
     * @return mixed
523
     */
524
    public static function __callStatic(string $name, array $arguments)
525
    {
526
        if (null === $service = static::serviceExists($name)) {
527 2
            return static::discoverServices($name, $arguments);
528
        }
529
530
        return $service::$name(...$arguments);
531
    }
532
533
    /**
534
     * Vérifiez si le service demandé est défini et renvoyez la classe déclarante.
535
     * Renvoie null s'il n'est pas trouvé.
536
     */
537
    public static function serviceExists(string $name): ?string
538
    {
539 2
        static::cacheServices();
540 2
        $services = array_merge(self::$serviceNames, [self::class]);
541 2
        $name     = strtolower($name);
542
543
        foreach ($services as $service) {
544
            if (method_exists($service, $name)) {
545
                return $service;
546
            }
547
        }
548
549 2
        return null;
550
    }
551
552
    /**
553
     * Injectez un objet fictif pour les tests.
554
     */
555
    public static function injectMock(string $name, object $mock): void
556
    {
557 3
        static::$mocks[strtolower($name)] = $mock;
558
    }
559
560
    /**
561
     * Essaie d'obtenir un service à partir du conteneur
562
     *
563
     * @return mixed
564
     */
565
    protected static function discoverServices(string $name, array $arguments)
566
    {
567
        if (true !== array_pop($arguments)) {
568 2
            return static::factory($name, $arguments);
569
        }
570
571
        return static::singleton($name, ...$arguments);
572
    }
573
574
    protected static function cacheServices(): void
575
    {
576
        if (! static::$discovered) {
577 2
            $locator = static::locator();
578 2
            $files   = $locator->search('Config/Services');
579
580
            // Obtenez des instances de toutes les classes de service et mettez-les en cache localement.
581
            foreach ($files as $file) {
582
                if (false === $classname = $locator->findQualifiedNameFromPath($file)) {
583
                    continue;
584
                }
585
                if (self::class !== $classname) {
586
                    self::$serviceNames[] = $classname;
587
                    static::$services[]   = new $classname();
588
                }
589
            }
590
591 2
            static::$discovered = true;
592
        }
593
    }
594
595
    /**
596
     * Injecter une seule instance de la classe donnée
597
     *
598
     * @return mixed
599
     */
600
    public static function singleton(string $name)
601
    {
602
        $arguments = func_get_args();
603
        $name      = array_shift($arguments);
604
605
        if (empty(static::$instances[$name])) {
606
            if (! empty($arguments)) {
607
                static::$instances[$name] = static::factory($name, $arguments);
608
            } else {
609
                static::$instances[$name] = static::container()->get($name);
610
            }
611
        }
612
613
        return static::$instances[$name];
614
    }
615
616
    /**
617
     * Injecter une nouvelle instance de la classe donnée
618
     *
619
     * @return mixed
620
     */
621
    public static function factory(string $name, array $arguments = [])
622
    {
623 2
        return static::container()->make($name, $arguments);
624
    }
625
626
    /**
627
     * Définissez un objet ou une valeur dans le conteneur.
628
     *
629
     * @param string $name  Nom de l'entrée
630
     * @param mixed  $value utilisez les aides à la définition pour définir les objets
631
     */
632
    public static function set(string $name, $value)
633
    {
634 30
        static::$instances[$name] = $value;
635 30
        static::container()->set($name, $value);
636
    }
637
638
    /**
639
     * Réinitialisez les instances partagées et les simulations pour les tests.
640
     */
641
    public static function reset(bool $initAutoloader = true): void
642
    {
643
        // static::$mocks     = [];
644
        static::$instances = [];
645
646
        if ($initAutoloader) {
647
            static::autoloader()->initialize();
648
        }
649
    }
650
}
651