Passed
Push — main ( 1fe2cd...c1deb1 )
by Dimitri
04:10
created

Container::add()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 5
ccs 0
cts 2
cp 0
crap 2
rs 10
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 BadMethodCallException;
15
use Closure;
16
use DI\Container as DIContainer;
17
use DI\ContainerBuilder;
18
use Psr\Container\ContainerInterface;
19
20
/**
21
 * Conteneur d’injection de dépendances.
22
 *
23
 * @method mixed call(array|callable|string $callable, array $parameters = []) Appelez la fonction donnée en utilisant les paramètres donnés.
24
 *                                                                             Les paramètres manquants seront résolus à partir du conteneur.
25
 *
26
 * @param array $parameters Paramètres facultatifs à utiliser pour construire l'entrée.
27
 *                          Utilisez ceci pour forcer des paramètres spécifiques à des valeurs spécifiques.
28
 *                          Les paramètres non définis dans ce tableau seront résolus en utilisant le conteneur.
29
 *
30
 * @method string debugEntry(string $name) Obtenir les informations de débogage de l'entrée.
31
 *
32
 * @param array $parameters Paramètres à utiliser.
33
 *                          Peut être indexé par les noms de paramètre ou non indexé (même ordre que les paramètres).
34
 *                          Le tableau peut également contenir des définitions DI, par ex. DI\get().
35
 *
36
 * @method array  getKnownEntryNames()                       Obtenez des entrées de conteneur définies.
37
 * @method object injectOn(object $instance)                 Injectez toutes les dépendances sur une instance existante.
38
 * @method void   set(string $name, mixed $value)            Définissez un objet ou une valeur dans le conteneur.
39
 * @method mixed  make(string $name, array $parameters = []) Construire une entrée du conteneur par son nom.
40
 *                                                           Cette méthode se comporte comme singleton() sauf qu'elle résout à nouveau l'entrée à chaque fois.
41
 *                                                           Par exemple, si l'entrée est une classe, une nouvelle instance sera créée à chaque fois.
42
 *                                                           Cette méthode fait que le conteneur se comporte comme une usine.
43
 */
44
class Container implements ContainerInterface
45
{
46
    protected DIContainer $container;
47
48
    /**
49
     * Providers deja charges (cache)
50
     *
51
     * @var AbstractProvider[]
52
     */
53
    private static array $providers = [];
54
55
    /**
56
     * Noms des providers deja charges (cache)
57
     *
58
     * @var array<class-string<AbstractProvider>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string<AbstractProvider>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string<AbstractProvider>>.
Loading history...
59
     */
60
    private static array $providerNames = [];
61
62
    /**
63
     * Avons-nous déjà découvert les fournisseurs ?
64
     */
65
    private static bool $discovered = false;
66
67
    /**
68
     * Drapeau pour determiner si le conteneur est deja initialiser
69
     */
70
    private bool $initialized = false;
71
72
    /**
73
     * Renvoie une entrée du conteneur par son nom.
74
     *
75
     * @param string $name Nom de l’entrée ou nom de classe.
76
     *
77
     * @return mixed
78
     */
79
    public function get(string $name)
80
    {
81 8
        return $this->container->get($name);
82
    }
83
84
    /**
85
     * Testez si le conteneur peut fournir quelque chose pour le nom donné.
86
     *
87
     * @param string $name Nom d'entrée ou nom de classe
88
     */
89
    public function has(string $name): bool
90
    {
91 10
        return $this->container->has($name);
92
    }
93
94
    /**
95
     * Defini un element au conteneur sous forme de factory
96
     * Si l'element existe déjà, il sera remplacé
97
     */
98
    public function add(string $key, Closure $callback): void
99
    {
100
        $this->container->set($key, $callback);
101
102
        $this->container->set(self::class, $this);
103
    }
104
105
    /**
106
     * Defini un element au conteneur sous forme de factory
107
     * Si l'element existe déjà, il sera ignoré
108
     */
109
    public function addIf(string $key, Closure $callback): void
110
    {
111
        if (! $this->has($key)) {
112
            $this->add($key, $callback);
113
        }
114
    }
115
116
    /**
117
     * Defini plusieurs elements au conteneur sous forme de factory
118
     * L'element qui existera déjà sera remplacé par la correspondance du tableau
119
     *
120
     * @param array<string, Closure> $keys
121
     */
122
    public function merge(array $keys): void
123
    {
124
        foreach ($keys as $key => $callback) {
125
            if ($callback instanceof Closure) {
126
                $this->add($key, $callback);
127
            }
128
        }
129
    }
130
131
    /**
132
     * Defini plusieurs elements au conteneur sous forme de factory
133
     * L'element qui existera déjà sera ignoré
134
     *
135
     * @param array<string, Closure> $keys
136
     */
137
    public function mergeIf(array $keys): void
138
    {
139
        foreach ($keys as $key => $callback) {
140
            if ($callback instanceof Closure) {
141
                $this->addIf($key, $callback);
142
            }
143
        }
144
    }
145
146
    /**
147
     * Verifie qu'une entree a été explicitement définie dans le conteneur
148
     */
149
    public function bound(string $key): bool
150
    {
151
        return in_array($key, $this->getKnownEntryNames(), true);
152
    }
153
154
    /**
155
     * Methode magique pour acceder aux methodes de php-di
156
     *
157
     * @param mixed $name
158
     * @param mixed $arguments
159
     */
160
    public function __call($name, $arguments)
161
    {
162
        if (method_exists($this->container, $name)) {
163 64
            return call_user_func_array([$this->container, $name], $arguments);
164
        }
165
166
        throw new BadMethodCallException('Methode "' . $name . '" non definie');
167
    }
168
169
    /**
170
     * Initialise le conteneur et injecte les services providers.
171
     *
172
     * @internal
173
     */
174
    public function initialize()
175
    {
176
        if ($this->initialized) {
177
            return;
178
        }
179
180
        $builder = new ContainerBuilder();
181
        $builder->useAutowiring(true);
182
        $builder->useAttributes(true);
183
184
        if (on_prod(true)) {
185
            if (extension_loaded('apcu')) {
186
                $builder->enableDefinitionCache(str_replace([' ', '/', '\\', '.'], '', APP_PATH));
187
            }
188
189
            $builder->enableCompilation(FRAMEWORK_STORAGE_PATH . 'cache');
190
        }
191
192
        $this->discoveProviders();
193
194
        foreach (self::$providerNames as $provider) {
195
            $builder->addDefinitions($provider::definitions());
196
        }
197
198
        $this->container = $builder->build();
199
200
        $this->registryProviders();
201
202
        $this->initialized = true;
203
    }
204
205
    /**
206
     * Listes des providers chargés par le framework
207
     *
208
     * @return array<class-string<AbstractProvider>, AbstractProvider>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string<Abstr...der>, AbstractProvider> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string<AbstractProvider>, AbstractProvider>.
Loading history...
209
     */
210
    public static function providers(): array
211
    {
212
        return array_combine(self::$providerNames, self::$providers);
213
    }
214
215
    /**
216
     * Enregistre les provider dans le conteneur
217
     */
218
    private function registryProviders(): void
219
    {
220
        foreach (self::$providerNames as $classname) {
221
            $provider = $this->container->make($classname, [
222
                'container' => $this,
223
            ]);
224
            $this->container->call([$provider, 'register']);
225
            self::$providers[] = $provider;
226
        }
227
228
        $this->set(self::class, $this);
229
        $this->set(ContainerInterface::class, $this);
230
    }
231
232
    /**
233
     * Recherche tous les fournisseurs disponibles et les charge en cache
234
     */
235
    private function discoveProviders(): void
236
    {
237
        if (! self::$discovered) {
238
            $locator = Services::locator();
239
            $files   = array_merge(
240
                $locator->search('Config/Providers'),
241
                $locator->listFiles('Providers/'),
242
            );
243
244
            $appProviders  = array_filter($files, static fn ($name) => str_starts_with($name, APP_PATH));
245
            $systProviders = array_filter($files, static fn ($name) => str_starts_with($name, SYST_PATH));
246
            $files         = array_diff($files, $appProviders, $systProviders);
247
248
            $files = [
249
                ...$files, // Les founisseurs des vendors sont les premier a etre remplacer si besoin
250
                ...$systProviders, // Les founisseurs du systeme viennent ensuite pour eventuelement remplacer pour les vendors sont les
251
                ...$appProviders, // Ceux de l'application ont peu de chance de modifier quelque chose mais peuvent le faire
252
            ];
253
254
            // Obtenez des instances de toutes les classes de providers et mettez-les en cache localement.
255
            foreach ($files as $file) {
256
                if (is_a($classname = $locator->getClassname($file), AbstractProvider::class, true)) {
0 ignored issues
show
Bug introduced by
$file of type array is incompatible with the type string expected by parameter $file of BlitzPHP\Autoloader\Locator::getClassname(). ( Ignorable by Annotation )

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

256
                if (is_a($classname = $locator->getClassname(/** @scrutinizer ignore-type */ $file), AbstractProvider::class, true)) {
Loading history...
257
                    self::$providerNames[] = $classname;
258
                }
259
            }
260
261
            self::$discovered = true;
262
        }
263
    }
264
}
265