Issues (536)

src/Container/Container.php (3 issues)

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 list<AbstractProvider>
0 ignored issues
show
The type BlitzPHP\Container\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
     */
37
    private static array $providers = [];
38
39
    /**
40
     * Noms des providers deja charges (cache)
41
     *
42
     * @var list<class-string<AbstractProvider>>
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 8
        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
     *
88
     * @param class-string<T>|string $name       Nom de l'entrée ou nom de la classe.
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T>|string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>|string.
Loading history...
89
     * @param array                  $parameters Paramètres optionnels à utiliser pour construire l'entrée.
90
     *                                           Utilisez ceci pour forcer des paramètres spécifiques à des valeurs spécifiques.
91
     *                                           Les paramètres non définis dans ce tableau seront résolus à l'aide du conteneur.
92
     *
93
     * @return mixed|T
94
     */
95
    public function make(string $name, array $parameters = []): mixed
96
    {
97 28
        return $this->container->make($name, $parameters);
98
    }
99
100
    /**
101
     * Appelle la fonction donnée en utilisant les paramètres donnés.
102
     * Les paramètres manquants seront résolus à partir du conteneur.
103
     *
104
     * @param array|callable|string $callback   Fonction à appeler.
105
     * @param array                 $parameters Paramètres facultatifs à utiliser pour construire l'entrée.
106
     *                                          Utilisez ceci pour forcer des paramètres spécifiques à des valeurs spécifiques.
107
     *                                          Les paramètres non définis dans ce tableau seront résolus en utilisant le conteneur.
108
     *                                          Peut être indexé par les noms de paramètre ou non indexé (même ordre que les paramètres).
109
     *                                          Le tableau peut également contenir des définitions DI, par ex. DI\get().
110
     */
111
    public function call(array|callable|string $callback, array $parameters = []): mixed
112
    {
113 4
        return $this->container->call($callback, $parameters);
114
    }
115
116
    /**
117
     * Defini un element au conteneur sous forme de factory
118
     * Si l'element existe déjà, il sera remplacé
119
     */
120
    public function add(string $key, Closure $callback): void
121
    {
122
        $this->container->set($key, $callback);
123
124
        $this->container->set(self::class, $this);
125
    }
126
127
    /**
128
     * Defini un element au conteneur sous forme de factory
129
     * Si l'element existe déjà, il sera ignoré
130
     */
131
    public function addIf(string $key, Closure $callback): void
132
    {
133
        if (! $this->has($key)) {
134
            $this->add($key, $callback);
135
        }
136
    }
137
138
    /**
139
     * Defini plusieurs elements au conteneur sous forme de factory
140
     * L'element qui existera déjà sera remplacé par la correspondance du tableau
141
     *
142
     * @param array<string, Closure> $keys
143
     */
144
    public function merge(array $keys): void
145
    {
146
        foreach ($keys as $key => $callback) {
147
            if ($callback instanceof Closure) {
148
                $this->add($key, $callback);
149
            }
150
        }
151
    }
152
153
    /**
154
     * Defini plusieurs elements au conteneur sous forme de factory
155
     * L'element qui existera déjà sera ignoré
156
     *
157
     * @param array<string, Closure> $keys
158
     */
159
    public function mergeIf(array $keys): void
160
    {
161
        foreach ($keys as $key => $callback) {
162
            if ($callback instanceof Closure) {
163
                $this->addIf($key, $callback);
164
            }
165
        }
166
    }
167
168
    /**
169
     * Verifie qu'une entree a été explicitement définie dans le conteneur
170
     */
171
    public function bound(string $key): bool
172
    {
173
        return in_array($key, $this->getKnownEntryNames(), true);
174
    }
175
176
    /**
177
     * Methode magique pour acceder aux methodes de php-di
178
     *
179
     * @param mixed $name
180
     * @param mixed $arguments
181
     */
182
    public function __call($name, $arguments)
183
    {
184
        if (method_exists($this->container, $name)) {
185 52
            return call_user_func_array([$this->container, $name], $arguments);
186
        }
187
188
        throw new BadMethodCallException('Methode "' . $name . '" non definie');
189
    }
190
191
    /**
192
     * Initialise le conteneur et injecte les services providers.
193
     *
194
     * @internal
195
     */
196
    public function initialize()
197
    {
198
        if ($this->initialized) {
199
            return;
200
        }
201
202
        $builder = new ContainerBuilder();
203
        $builder->useAutowiring(true);
204
        $builder->useAttributes(true);
205
206
        if (on_prod(true)) {
207
            if (extension_loaded('apcu')) {
208
                $builder->enableDefinitionCache(str_replace([' ', '/', '\\', '.'], '', APP_PATH));
209
            }
210
211
            $builder->enableCompilation(FRAMEWORK_STORAGE_PATH . 'cache');
212
        }
213
214
        $this->discoveProviders();
215
216
        foreach (self::$providerNames as $provider) {
217
            $builder->addDefinitions($provider::definitions());
218
        }
219
220
        $this->container = $builder->build();
221
222
        $this->registryProviders();
223
224
        $this->initialized = true;
225
    }
226
227
    /**
228
     * Listes des providers chargés par le framework
229
     *
230
     * @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...
231
     */
232
    public static function providers(): array
233
    {
234
        return array_combine(self::$providerNames, self::$providers);
235
    }
236
237
    /**
238
     * Enregistre les provider dans le conteneur
239
     */
240
    private function registryProviders(): void
241
    {
242
        foreach (self::$providerNames as $classname) {
243
            $provider = $this->container->make($classname, [
244
                'container' => $this,
245
            ]);
246
            $this->container->call([$provider, 'register']);
247
            self::$providers[] = $provider;
248
        }
249
250
        $this->set(self::class, $this);
251
        $this->set(ContainerInterface::class, $this);
252
    }
253
254
    /**
255
     * Recherche tous les fournisseurs disponibles et les charge en cache
256
     */
257
    private function discoveProviders(): void
258
    {
259
        if (! self::$discovered) {
260
            $locator = service('locator');
261
            $files   = array_merge(
262
                $locator->search('Config/Providers'),
263
                $locator->listFiles('Providers/'),
264
            );
265
266
            $appProviders  = array_filter($files, static fn ($name) => str_starts_with($name, APP_PATH));
267
            $systProviders = array_filter($files, static fn ($name) => str_starts_with($name, SYST_PATH));
268
            $files         = array_diff($files, $appProviders, $systProviders);
269
270
            $files = [
271
                ...$files, // Les founisseurs des vendors sont les premier a etre remplacer si besoin
272
                ...$systProviders, // Les founisseurs du systeme viennent ensuite pour eventuelement remplacer pour les vendors sont les
273
                ...$appProviders, // Ceux de l'application ont peu de chance de modifier quelque chose mais peuvent le faire
274
            ];
275
276
            // Obtenez des instances de toutes les classes de providers et mettez-les en cache localement.
277
            foreach ($files as $file) {
278
                if (is_a($classname = $locator->getClassname($file), AbstractProvider::class, true)) {
279
                    self::$providerNames[] = $classname;
280
                }
281
            }
282
283
            self::$discovered = true;
284
        }
285
    }
286
}
287