Passed
Pull Request — main (#13)
by Dimitri
17:30 queued 14s
created

Container::make()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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

276
                if (is_a($classname = $locator->getClassname(/** @scrutinizer ignore-type */ $file), AbstractProvider::class, true)) {
Loading history...
277
                    self::$providerNames[] = $classname;
278
                }
279
            }
280
281
            self::$discovered = true;
282
        }
283
    }
284
}
285