Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace DI;
6
 
7
use DI\Compiler\Compiler;
8
use DI\Definition\Source\AttributeBasedAutowiring;
9
use DI\Definition\Source\DefinitionArray;
10
use DI\Definition\Source\DefinitionFile;
11
use DI\Definition\Source\DefinitionSource;
12
use DI\Definition\Source\NoAutowiring;
13
use DI\Definition\Source\ReflectionBasedAutowiring;
14
use DI\Definition\Source\SourceCache;
15
use DI\Definition\Source\SourceChain;
16
use DI\Proxy\ProxyFactory;
17
use InvalidArgumentException;
18
use Psr\Container\ContainerInterface;
19
 
20
/**
21
 * Helper to create and configure a Container.
22
 *
23
 * With the default options, the container created is appropriate for the development environment.
24
 *
25
 * Example:
26
 *
27
 *     $builder = new ContainerBuilder();
28
 *     $container = $builder->build();
29
 *
30
 * @api
31
 *
32
 * @since  3.2
33
 * @author Matthieu Napoli <matthieu@mnapoli.fr>
34
 *
35
 * @psalm-template ContainerClass of Container
36
 */
37
class ContainerBuilder
38
{
39
    /**
40
     * Name of the container class, used to create the container.
41
     * @var class-string<Container>
42
     * @psalm-var class-string<ContainerClass>
43
     */
44
    private string $containerClass;
45
 
46
    /**
47
     * Name of the container parent class, used on compiled container.
48
     * @var class-string<Container>
49
     * @psalm-var class-string<ContainerClass>
50
     */
51
    private string $containerParentClass;
52
 
53
    private bool $useAutowiring = true;
54
 
55
    private bool $useAttributes = false;
56
 
57
    /**
58
     * If set, write the proxies to disk in this directory to improve performances.
59
     */
60
    private ?string $proxyDirectory = null;
61
 
62
    /**
63
     * If PHP-DI is wrapped in another container, this references the wrapper.
64
     */
65
    private ?ContainerInterface $wrapperContainer = null;
66
 
67
    /**
68
     * @var DefinitionSource[]|string[]|array[]
69
     */
70
    private array $definitionSources = [];
71
 
72
    /**
73
     * Whether the container has already been built.
74
     */
75
    private bool $locked = false;
76
 
77
    private ?string $compileToDirectory = null;
78
 
79
    private bool $sourceCache = false;
80
 
81
    protected string $sourceCacheNamespace = '';
82
 
83
    /**
84
     * @param class-string<Container> $containerClass Name of the container class, used to create the container.
85
     * @psalm-param class-string<ContainerClass> $containerClass
86
     */
87
    public function __construct(string $containerClass = Container::class)
88
    {
89
        $this->containerClass = $containerClass;
90
    }
91
 
92
    /**
93
     * Build and return a container.
94
     *
95
     * @return Container
96
     * @psalm-return ContainerClass
97
     */
98
    public function build()
99
    {
100
        $sources = array_reverse($this->definitionSources);
101
 
102
        if ($this->useAttributes) {
103
            $autowiring = new AttributeBasedAutowiring;
104
            $sources[] = $autowiring;
105
        } elseif ($this->useAutowiring) {
106
            $autowiring = new ReflectionBasedAutowiring;
107
            $sources[] = $autowiring;
108
        } else {
109
            $autowiring = new NoAutowiring;
110
        }
111
 
112
        $sources = array_map(function ($definitions) use ($autowiring) {
113
            if (is_string($definitions)) {
114
                // File
115
                return new DefinitionFile($definitions, $autowiring);
116
            }
117
            if (is_array($definitions)) {
118
                return new DefinitionArray($definitions, $autowiring);
119
            }
120
 
121
            return $definitions;
122
        }, $sources);
123
        $source = new SourceChain($sources);
124
 
125
        // Mutable definition source
126
        $source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));
127
 
128
        if ($this->sourceCache) {
129
            if (!SourceCache::isSupported()) {
130
                throw new \Exception('APCu is not enabled, PHP-DI cannot use it as a cache');
131
            }
132
            // Wrap the source with the cache decorator
133
            $source = new SourceCache($source, $this->sourceCacheNamespace);
134
        }
135
 
136
        $proxyFactory = new ProxyFactory($this->proxyDirectory);
137
 
138
        $this->locked = true;
139
 
140
        $containerClass = $this->containerClass;
141
 
142
        if ($this->compileToDirectory) {
143
            $compiler = new Compiler($proxyFactory);
144
            $compiledContainerFile = $compiler->compile(
145
                $source,
146
                $this->compileToDirectory,
147
                $containerClass,
148
                $this->containerParentClass,
149
                $this->useAutowiring
150
            );
151
            // Only load the file if it hasn't been already loaded
152
            // (the container can be created multiple times in the same process)
153
            if (!class_exists($containerClass, false)) {
154
                require $compiledContainerFile;
155
            }
156
        }
157
 
158
        return new $containerClass($source, $proxyFactory, $this->wrapperContainer);
159
    }
160
 
161
    /**
162
     * Compile the container for optimum performances.
163
     *
164
     * Be aware that the container is compiled once and never updated!
165
     *
166
     * Therefore:
167
     *
168
     * - in production you should clear that directory every time you deploy
169
     * - in development you should not compile the container
170
     *
171
     * @see https://php-di.org/doc/performances.html
172
     *
173
     * @psalm-template T of CompiledContainer
174
     *
175
     * @param string $directory Directory in which to put the compiled container.
176
     * @param string $containerClass Name of the compiled class. Customize only if necessary.
177
     * @param class-string<Container> $containerParentClass Name of the compiled container parent class. Customize only if necessary.
178
     * @psalm-param class-string<T> $containerParentClass
179
     *
180
     * @psalm-return self<T>
181
     */
182
    public function enableCompilation(
183
        string $directory,
184
        string $containerClass = 'CompiledContainer',
185
        string $containerParentClass = CompiledContainer::class
186
    ) : self {
187
        $this->ensureNotLocked();
188
 
189
        $this->compileToDirectory = $directory;
190
        $this->containerClass = $containerClass;
191
        $this->containerParentClass = $containerParentClass;
192
 
193
        return $this;
194
    }
195
 
196
    /**
197
     * Enable or disable the use of autowiring to guess injections.
198
     *
199
     * Enabled by default.
200
     *
201
     * @return $this
202
     */
203
    public function useAutowiring(bool $bool) : self
204
    {
205
        $this->ensureNotLocked();
206
 
207
        $this->useAutowiring = $bool;
208
 
209
        return $this;
210
    }
211
 
212
    /**
213
     * Enable or disable the use of PHP 8 attributes to configure injections.
214
     *
215
     * Disabled by default.
216
     *
217
     * @return $this
218
     */
219
    public function useAttributes(bool $bool) : self
220
    {
221
        $this->ensureNotLocked();
222
 
223
        $this->useAttributes = $bool;
224
 
225
        return $this;
226
    }
227
 
228
    /**
229
     * Configure the proxy generation.
230
     *
231
     * For dev environment, use `writeProxiesToFile(false)` (default configuration)
232
     * For production environment, use `writeProxiesToFile(true, 'tmp/proxies')`
233
     *
234
     * @see https://php-di.org/doc/lazy-injection.html
235
     *
236
     * @param bool $writeToFile If true, write the proxies to disk to improve performances
237
     * @param string|null $proxyDirectory Directory where to write the proxies
238
     * @return $this
239
     * @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null
240
     */
241
    public function writeProxiesToFile(bool $writeToFile, string $proxyDirectory = null) : self
242
    {
243
        $this->ensureNotLocked();
244
 
245
        if ($writeToFile && $proxyDirectory === null) {
246
            throw new InvalidArgumentException(
247
                'The proxy directory must be specified if you want to write proxies on disk'
248
            );
249
        }
250
        $this->proxyDirectory = $writeToFile ? $proxyDirectory : null;
251
 
252
        return $this;
253
    }
254
 
255
    /**
256
     * If PHP-DI's container is wrapped by another container, we can
257
     * set this so that PHP-DI will use the wrapper rather than itself for building objects.
258
     *
259
     * @return $this
260
     */
261
    public function wrapContainer(ContainerInterface $otherContainer) : self
262
    {
263
        $this->ensureNotLocked();
264
 
265
        $this->wrapperContainer = $otherContainer;
266
 
267
        return $this;
268
    }
269
 
270
    /**
271
     * Add definitions to the container.
272
     *
273
     * @param string|array|DefinitionSource ...$definitions Can be an array of definitions, the
274
     *                                                      name of a file containing definitions
275
     *                                                      or a DefinitionSource object.
276
     * @return $this
277
     */
278
    public function addDefinitions(string|array|DefinitionSource ...$definitions) : self
279
    {
280
        $this->ensureNotLocked();
281
 
282
        foreach ($definitions as $definition) {
283
            $this->definitionSources[] = $definition;
284
        }
285
 
286
        return $this;
287
    }
288
 
289
    /**
290
     * Enables the use of APCu to cache definitions.
291
     *
292
     * You must have APCu enabled to use it.
293
     *
294
     * Before using this feature, you should try these steps first:
295
     * - enable compilation if not already done (see `enableCompilation()`)
296
     * - if you use autowiring or attributes, add all the classes you are using into your configuration so that
297
     *   PHP-DI knows about them and compiles them
298
     * Once this is done, you can try to optimize performances further with APCu. It can also be useful if you use
299
     * `Container::make()` instead of `get()` (`make()` calls cannot be compiled so they are not optimized).
300
     *
301
     * Remember to clear APCu on each deploy else your application will have a stale cache. Do not enable the cache
302
     * in development environment: any change you will make to the code will be ignored because of the cache.
303
     *
304
     * @see https://php-di.org/doc/performances.html
305
     *
306
     * @param string $cacheNamespace use unique namespace per container when sharing a single APC memory pool to prevent cache collisions
307
     * @return $this
308
     */
309
    public function enableDefinitionCache(string $cacheNamespace = '') : self
310
    {
311
        $this->ensureNotLocked();
312
 
313
        $this->sourceCache = true;
314
        $this->sourceCacheNamespace = $cacheNamespace;
315
 
316
        return $this;
317
    }
318
 
319
    /**
320
     * Are we building a compiled container?
321
     */
322
    public function isCompilationEnabled() : bool
323
    {
324
        return (bool) $this->compileToDirectory;
325
    }
326
 
327
    private function ensureNotLocked() : void
328
    {
329
        if ($this->locked) {
330
            throw new \LogicException('The ContainerBuilder cannot be modified after the container has been built');
331
        }
332
    }
333
}