Completed
Push — master ( 0f0da2...360bf1 )
by Matthieu
01:46 queued 01:43
created

ContainerBuilder::enableDefinitionCache()   A

Complexity

Conditions 1
Paths 1

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 8
Code Lines 4

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace DI;
6
7
use DI\Definition\Source\AnnotationBasedAutowiring;
8
use DI\Definition\Source\DefinitionArray;
9
use DI\Definition\Source\DefinitionFile;
10
use DI\Definition\Source\DefinitionSource;
11
use DI\Definition\Source\NoAutowiring;
12
use DI\Definition\Source\ReflectionBasedAutowiring;
13
use DI\Definition\Source\SourceCache;
14
use DI\Definition\Source\SourceChain;
15
use DI\Proxy\ProxyFactory;
16
use InvalidArgumentException;
17
use Psr\Container\ContainerInterface;
18
19
/**
20
 * Helper to create and configure a Container.
21
 *
22
 * With the default options, the container created is appropriate for the development environment.
23
 *
24
 * Example:
25
 *
26
 *     $builder = new ContainerBuilder();
27
 *     $container = $builder->build();
28
 *
29
 * @api
30
 *
31
 * @since  3.2
32
 * @author Matthieu Napoli <[email protected]>
33
 */
34
class ContainerBuilder
35
{
36
    /**
37
     * Name of the container class, used to create the container.
38
     * @var string
39
     */
40
    private $containerClass;
41
42
    /**
43
     * Name of the container parent class, used on compiled container.
44
     * @var string
45
     */
46
    private $containerParentClass;
47
48
    /**
49
     * @var bool
50
     */
51
    private $useAutowiring = true;
52
53
    /**
54
     * @var bool
55
     */
56
    private $useAnnotations = false;
57
58
    /**
59
     * @var bool
60
     */
61
    private $ignorePhpDocErrors = false;
62
63
    /**
64
     * If true, write the proxies to disk to improve performances.
65
     * @var bool
66
     */
67
    private $writeProxiesToFile = false;
68
69
    /**
70
     * Directory where to write the proxies (if $writeProxiesToFile is enabled).
71
     * @var string
72
     */
73
    private $proxyDirectory;
74
75
    /**
76
     * If PHP-DI is wrapped in another container, this references the wrapper.
77
     * @var ContainerInterface
78
     */
79
    private $wrapperContainer;
80
81
    /**
82
     * @var DefinitionSource[]|string[]|array[]
83
     */
84
    private $definitionSources = [];
85
86
    /**
87
     * Whether the container has already been built.
88
     * @var bool
89
     */
90
    private $locked = false;
91
92
    /**
93
     * @var string|null
94
     */
95
    private $compileToDirectory;
96
97
    /**
98
     * @var bool
99
     */
100
    private $sourceCache = false;
101
102
    /**
103
     * Build a container configured for the dev environment.
104
     */
105
    public static function buildDevContainer() : Container
106
    {
107
        return new Container;
108
    }
109
110
    /**
111
     * @param string $containerClass Name of the container class, used to create the container.
112
     */
113
    public function __construct(string $containerClass = 'DI\Container')
114
    {
115
        $this->containerClass = $containerClass;
116
    }
117
118
    /**
119
     * Build and return a container.
120
     *
121
     * @return Container
122
     */
123
    public function build()
124
    {
125
        $sources = array_reverse($this->definitionSources);
126
127
        if ($this->useAnnotations) {
128
            $autowiring = new AnnotationBasedAutowiring($this->ignorePhpDocErrors);
129
            $sources[] = $autowiring;
130
        } elseif ($this->useAutowiring) {
131
            $autowiring = new ReflectionBasedAutowiring;
132
            $sources[] = $autowiring;
133
        } else {
134
            $autowiring = new NoAutowiring;
135
        }
136
137
        $sources = array_map(function ($definitions) use ($autowiring) {
138
            if (is_string($definitions)) {
139
                // File
140
                return new DefinitionFile($definitions, $autowiring);
141
            } elseif (is_array($definitions)) {
142
                return new DefinitionArray($definitions, $autowiring);
143
            }
144
145
            return $definitions;
146
        }, $sources);
147
        $source = new SourceChain($sources);
148
149
        // Mutable definition source
150
        $source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));
151
152
        if ($this->sourceCache) {
153
            if (!SourceCache::isSupported()) {
154
                throw new \Exception('APCu is not enabled, PHP-DI cannot use it as a cache');
155
            }
156
            // Wrap the source with the cache decorator
157
            $source = new SourceCache($source);
158
        }
159
160
        $proxyFactory = new ProxyFactory($this->writeProxiesToFile, $this->proxyDirectory);
161
162
        $this->locked = true;
163
164
        $containerClass = $this->containerClass;
165
166
        if ($this->compileToDirectory) {
0 ignored issues
show
Bug Best Practice introduced by Matthieu Napoli
The expression $this->compileToDirectory of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
167
            $compiler = new Compiler;
168
            $compiledContainerFile = $compiler->compile(
169
                $source,
170
                $this->compileToDirectory,
171
                $containerClass,
172
                $this->containerParentClass,
173
                $this->useAutowiring || $this->useAnnotations
174
            );
175
            // Only load the file if it hasn't been already loaded
176
            // (the container can be created multiple times in the same process)
177
            if (!class_exists($containerClass, false)) {
178
                require $compiledContainerFile;
179
            }
180
        }
181
182
        return new $containerClass($source, $proxyFactory, $this->wrapperContainer);
183
    }
184
185
    /**
186
     * Compile the container for optimum performances.
187
     *
188
     * Be aware that the container is compiled once and never updated!
189
     *
190
     * Therefore:
191
     *
192
     * - in production you should clear that directory every time you deploy
193
     * - in development you should not compile the container
194
     *
195
     * @see http://php-di.org/doc/performances.html
196
     *
197
     * @param string $directory Directory in which to put the compiled container.
198
     * @param string $containerClass Name of the compiled class. Customize only if necessary.
199
     * @param string $containerParentClass Name of the compiled container parent class. Customize only if necessary.
200
     */
201
    public function enableCompilation(
202
        string $directory,
203
        string $containerClass = 'CompiledContainer',
204
        string $containerParentClass = CompiledContainer::class
205
    ) : self {
206
        $this->ensureNotLocked();
207
208
        $this->compileToDirectory = $directory;
209
        $this->containerClass = $containerClass;
210
        $this->containerParentClass = $containerParentClass;
211
212
        return $this;
213
    }
214
215
    /**
216
     * Enable or disable the use of autowiring to guess injections.
217
     *
218
     * Enabled by default.
219
     *
220
     * @return $this
221
     */
222
    public function useAutowiring(bool $bool) : self
223
    {
224
        $this->ensureNotLocked();
225
226
        $this->useAutowiring = $bool;
227
228
        return $this;
229
    }
230
231
    /**
232
     * Enable or disable the use of annotations to guess injections.
233
     *
234
     * Disabled by default.
235
     *
236
     * @return $this
237
     */
238
    public function useAnnotations(bool $bool) : self
239
    {
240
        $this->ensureNotLocked();
241
242
        $this->useAnnotations = $bool;
243
244
        return $this;
245
    }
246
247
    /**
248
     * Enable or disable ignoring phpdoc errors (non-existent classes in `@param` or `@var`).
249
     *
250
     * @return $this
251
     */
252
    public function ignorePhpDocErrors(bool $bool) : self
253
    {
254
        $this->ensureNotLocked();
255
256
        $this->ignorePhpDocErrors = $bool;
257
258
        return $this;
259
    }
260
261
    /**
262
     * Configure the proxy generation.
263
     *
264
     * For dev environment, use `writeProxiesToFile(false)` (default configuration)
265
     * For production environment, use `writeProxiesToFile(true, 'tmp/proxies')`
266
     *
267
     * @see http://php-di.org/doc/lazy-injection.html
268
     *
269
     * @param bool $writeToFile If true, write the proxies to disk to improve performances
270
     * @param string|null $proxyDirectory Directory where to write the proxies
271
     * @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null
272
     * @return $this
273
     */
274
    public function writeProxiesToFile(bool $writeToFile, string $proxyDirectory = null) : self
275
    {
276
        $this->ensureNotLocked();
277
278
        $this->writeProxiesToFile = $writeToFile;
279
280
        if ($writeToFile && $proxyDirectory === null) {
281
            throw new InvalidArgumentException(
282
                'The proxy directory must be specified if you want to write proxies on disk'
283
            );
284
        }
285
        $this->proxyDirectory = $proxyDirectory;
286
287
        return $this;
288
    }
289
290
    /**
291
     * If PHP-DI's container is wrapped by another container, we can
292
     * set this so that PHP-DI will use the wrapper rather than itself for building objects.
293
     *
294
     * @return $this
295
     */
296
    public function wrapContainer(ContainerInterface $otherContainer) : self
297
    {
298
        $this->ensureNotLocked();
299
300
        $this->wrapperContainer = $otherContainer;
301
302
        return $this;
303
    }
304
305
    /**
306
     * Add definitions to the container.
307
     *
308
     * @param string|array|DefinitionSource $definitions Can be an array of definitions, the
309
     *                                                   name of a file containing definitions
310
     *                                                   or a DefinitionSource object.
311
     * @return $this
312
     */
313
    public function addDefinitions($definitions) : self
314
    {
315
        $this->ensureNotLocked();
316
317
        if (!is_string($definitions) && !is_array($definitions) && !($definitions instanceof DefinitionSource)) {
318
            throw new InvalidArgumentException(sprintf(
319
                '%s parameter must be a string, an array or a DefinitionSource object, %s given',
320
                'ContainerBuilder::addDefinitions()',
321
                is_object($definitions) ? get_class($definitions) : gettype($definitions)
322
            ));
323
        }
324
325
        $this->definitionSources[] = $definitions;
326
327
        return $this;
328
    }
329
330
    /**
331
     * Enables the use of APCu to cache definitions.
332
     *
333
     * You must have APCu enabled to use it.
334
     *
335
     * Before using this feature, you should try these steps first:
336
     * - enable compilation if not already done (see `enableCompilation()`)
337
     * - if you use autowiring or annotations, add all the classes you are using into your configuration so that
338
     *   PHP-DI knows about them and compiles them
339
     * Once this is done, you can try to optimize performances further with APCu. It can also be useful if you use
340
     * `Container::make()` instead of `get()` (`make()` calls cannot be compiled so they are not optimized).
341
     *
342
     * Remember to clear APCu on each deploy else your application will have a stale cache. Do not enable the cache
343
     * in development environment: any change you will make to the code will be ignored because of the cache.
344
     *
345
     * @see http://php-di.org/doc/performances.html
346
     *
347
     * @return $this
348
     */
349
    public function enableDefinitionCache() : self
350
    {
351
        $this->ensureNotLocked();
352
353
        $this->sourceCache = true;
354
355
        return $this;
356
    }
357
358
    /**
359
     * Are we building a compiled container?
360
     */
361
    public function isCompiled() : bool
362
    {
363
        return (bool) $this->compileToDirectory;
364
    }
365
366
    private function ensureNotLocked()
367
    {
368
        if ($this->locked) {
369
            throw new \LogicException('The ContainerBuilder cannot be modified after the container has been built');
370
        }
371
    }
372
}
373