Completed
Push — master ( d9a69d...589556 )
by Matthieu
01:36
created

ContainerBuilder::isCompilationEnabled()   A

Complexity

Conditions 1
Paths 1

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 4
Code Lines 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace DI;
6
7
use DI\Compiler\Compiler;
8
use DI\Definition\Source\AnnotationBasedAutowiring;
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 <[email protected]>
34
 */
35
class ContainerBuilder
36
{
37
    /**
38
     * Name of the container class, used to create the container.
39
     * @var string
40
     */
41
    private $containerClass;
42
43
    /**
44
     * Name of the container parent class, used on compiled container.
45
     * @var string
46
     */
47
    private $containerParentClass;
48
49
    /**
50
     * @var bool
51
     */
52
    private $useAutowiring = true;
53
54
    /**
55
     * @var bool
56
     */
57
    private $useAnnotations = false;
58
59
    /**
60
     * @var bool
61
     */
62
    private $ignorePhpDocErrors = false;
63
64
    /**
65
     * If true, write the proxies to disk to improve performances.
66
     * @var bool
67
     */
68
    private $writeProxiesToFile = false;
69
70
    /**
71
     * Directory where to write the proxies (if $writeProxiesToFile is enabled).
72
     * @var string
73
     */
74
    private $proxyDirectory;
75
76
    /**
77
     * If PHP-DI is wrapped in another container, this references the wrapper.
78
     * @var ContainerInterface
79
     */
80
    private $wrapperContainer;
81
82
    /**
83
     * @var DefinitionSource[]|string[]|array[]
84
     */
85
    private $definitionSources = [];
86
87
    /**
88
     * Whether the container has already been built.
89
     * @var bool
90
     */
91
    private $locked = false;
92
93
    /**
94
     * @var string|null
95
     */
96
    private $compileToDirectory;
97
98
    /**
99
     * @var bool
100
     */
101
    private $sourceCache = false;
102
103
    /**
104
     * Build a container configured for the dev environment.
105
     */
106
    public static function buildDevContainer() : Container
107
    {
108
        return new Container;
109
    }
110
111
    /**
112
     * @param string $containerClass Name of the container class, used to create the container.
113
     */
114
    public function __construct(string $containerClass = 'DI\Container')
115
    {
116
        $this->containerClass = $containerClass;
117
    }
118
119
    /**
120
     * Build and return a container.
121
     *
122
     * @return Container
123
     */
124
    public function build()
125
    {
126
        $sources = array_reverse($this->definitionSources);
127
128
        if ($this->useAnnotations) {
129
            $autowiring = new AnnotationBasedAutowiring($this->ignorePhpDocErrors);
130
            $sources[] = $autowiring;
131
        } elseif ($this->useAutowiring) {
132
            $autowiring = new ReflectionBasedAutowiring;
133
            $sources[] = $autowiring;
134
        } else {
135
            $autowiring = new NoAutowiring;
136
        }
137
138
        $sources = array_map(function ($definitions) use ($autowiring) {
139
            if (is_string($definitions)) {
140
                // File
141
                return new DefinitionFile($definitions, $autowiring);
142
            } elseif (is_array($definitions)) {
143
                return new DefinitionArray($definitions, $autowiring);
144
            }
145
146
            return $definitions;
147
        }, $sources);
148
        $source = new SourceChain($sources);
149
150
        // Mutable definition source
151
        $source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));
152
153
        if ($this->sourceCache) {
154
            if (!SourceCache::isSupported()) {
155
                throw new \Exception('APCu is not enabled, PHP-DI cannot use it as a cache');
156
            }
157
            // Wrap the source with the cache decorator
158
            $source = new SourceCache($source);
159
        }
160
161
        $proxyFactory = new ProxyFactory($this->writeProxiesToFile, $this->proxyDirectory);
162
163
        $this->locked = true;
164
165
        $containerClass = $this->containerClass;
166
167
        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...
168
            $compiler = new Compiler;
169
            $compiledContainerFile = $compiler->compile(
170
                $source,
171
                $this->compileToDirectory,
172
                $containerClass,
173
                $this->containerParentClass,
174
                $this->useAutowiring || $this->useAnnotations
175
            );
176
            // Only load the file if it hasn't been already loaded
177
            // (the container can be created multiple times in the same process)
178
            if (!class_exists($containerClass, false)) {
179
                require $compiledContainerFile;
180
            }
181
        }
182
183
        return new $containerClass($source, $proxyFactory, $this->wrapperContainer);
184
    }
185
186
    /**
187
     * Compile the container for optimum performances.
188
     *
189
     * Be aware that the container is compiled once and never updated!
190
     *
191
     * Therefore:
192
     *
193
     * - in production you should clear that directory every time you deploy
194
     * - in development you should not compile the container
195
     *
196
     * @see http://php-di.org/doc/performances.html
197
     *
198
     * @param string $directory Directory in which to put the compiled container.
199
     * @param string $containerClass Name of the compiled class. Customize only if necessary.
200
     * @param string $containerParentClass Name of the compiled container parent class. Customize only if necessary.
201
     */
202
    public function enableCompilation(
203
        string $directory,
204
        string $containerClass = 'CompiledContainer',
205
        string $containerParentClass = CompiledContainer::class
206
    ) : self {
207
        $this->ensureNotLocked();
208
209
        $this->compileToDirectory = $directory;
210
        $this->containerClass = $containerClass;
211
        $this->containerParentClass = $containerParentClass;
212
213
        return $this;
214
    }
215
216
    /**
217
     * Enable or disable the use of autowiring to guess injections.
218
     *
219
     * Enabled by default.
220
     *
221
     * @return $this
222
     */
223
    public function useAutowiring(bool $bool) : self
224
    {
225
        $this->ensureNotLocked();
226
227
        $this->useAutowiring = $bool;
228
229
        return $this;
230
    }
231
232
    /**
233
     * Enable or disable the use of annotations to guess injections.
234
     *
235
     * Disabled by default.
236
     *
237
     * @return $this
238
     */
239
    public function useAnnotations(bool $bool) : self
240
    {
241
        $this->ensureNotLocked();
242
243
        $this->useAnnotations = $bool;
244
245
        return $this;
246
    }
247
248
    /**
249
     * Enable or disable ignoring phpdoc errors (non-existent classes in `@param` or `@var`).
250
     *
251
     * @return $this
252
     */
253
    public function ignorePhpDocErrors(bool $bool) : self
254
    {
255
        $this->ensureNotLocked();
256
257
        $this->ignorePhpDocErrors = $bool;
258
259
        return $this;
260
    }
261
262
    /**
263
     * Configure the proxy generation.
264
     *
265
     * For dev environment, use `writeProxiesToFile(false)` (default configuration)
266
     * For production environment, use `writeProxiesToFile(true, 'tmp/proxies')`
267
     *
268
     * @see http://php-di.org/doc/lazy-injection.html
269
     *
270
     * @param bool $writeToFile If true, write the proxies to disk to improve performances
271
     * @param string|null $proxyDirectory Directory where to write the proxies
272
     * @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null
273
     * @return $this
274
     */
275
    public function writeProxiesToFile(bool $writeToFile, string $proxyDirectory = null) : self
276
    {
277
        $this->ensureNotLocked();
278
279
        $this->writeProxiesToFile = $writeToFile;
280
281
        if ($writeToFile && $proxyDirectory === null) {
282
            throw new InvalidArgumentException(
283
                'The proxy directory must be specified if you want to write proxies on disk'
284
            );
285
        }
286
        $this->proxyDirectory = $proxyDirectory;
287
288
        return $this;
289
    }
290
291
    /**
292
     * If PHP-DI's container is wrapped by another container, we can
293
     * set this so that PHP-DI will use the wrapper rather than itself for building objects.
294
     *
295
     * @return $this
296
     */
297
    public function wrapContainer(ContainerInterface $otherContainer) : self
298
    {
299
        $this->ensureNotLocked();
300
301
        $this->wrapperContainer = $otherContainer;
302
303
        return $this;
304
    }
305
306
    /**
307
     * Add definitions to the container.
308
     *
309
     * @param string|array|DefinitionSource $definitions Can be an array of definitions, the
310
     *                                                   name of a file containing definitions
311
     *                                                   or a DefinitionSource object.
312
     * @return $this
313
     */
314
    public function addDefinitions($definitions) : self
315
    {
316
        $this->ensureNotLocked();
317
318
        if (!is_string($definitions) && !is_array($definitions) && !($definitions instanceof DefinitionSource)) {
319
            throw new InvalidArgumentException(sprintf(
320
                '%s parameter must be a string, an array or a DefinitionSource object, %s given',
321
                'ContainerBuilder::addDefinitions()',
322
                is_object($definitions) ? get_class($definitions) : gettype($definitions)
323
            ));
324
        }
325
326
        $this->definitionSources[] = $definitions;
327
328
        return $this;
329
    }
330
331
    /**
332
     * Enables the use of APCu to cache definitions.
333
     *
334
     * You must have APCu enabled to use it.
335
     *
336
     * Before using this feature, you should try these steps first:
337
     * - enable compilation if not already done (see `enableCompilation()`)
338
     * - if you use autowiring or annotations, add all the classes you are using into your configuration so that
339
     *   PHP-DI knows about them and compiles them
340
     * Once this is done, you can try to optimize performances further with APCu. It can also be useful if you use
341
     * `Container::make()` instead of `get()` (`make()` calls cannot be compiled so they are not optimized).
342
     *
343
     * Remember to clear APCu on each deploy else your application will have a stale cache. Do not enable the cache
344
     * in development environment: any change you will make to the code will be ignored because of the cache.
345
     *
346
     * @see http://php-di.org/doc/performances.html
347
     *
348
     * @return $this
349
     */
350
    public function enableDefinitionCache() : self
351
    {
352
        $this->ensureNotLocked();
353
354
        $this->sourceCache = true;
355
356
        return $this;
357
    }
358
359
    /**
360
     * Are we building a compiled container?
361
     */
362
    public function isCompilationEnabled() : bool
363
    {
364
        return (bool) $this->compileToDirectory;
365
    }
366
367
    private function ensureNotLocked()
368
    {
369
        if ($this->locked) {
370
            throw new \LogicException('The ContainerBuilder cannot be modified after the container has been built');
371
        }
372
    }
373
}
374