Completed
Push — master ( 55f764...5a160c )
by Matthieu
02:37
created

ContainerBuilder::enableCompilation()   A

Complexity

Conditions 1
Paths 1

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 9
Code Lines 5

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 9
rs 9.6666
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\SourceChain;
14
use DI\Proxy\ProxyFactory;
15
use InvalidArgumentException;
16
use Psr\Container\ContainerInterface;
17
18
/**
19
 * Helper to create and configure a Container.
20
 *
21
 * With the default options, the container created is appropriate for the development environment.
22
 *
23
 * Example:
24
 *
25
 *     $builder = new ContainerBuilder();
26
 *     $container = $builder->build();
27
 *
28
 * @api
29
 *
30
 * @since  3.2
31
 * @author Matthieu Napoli <[email protected]>
32
 */
33
class ContainerBuilder
34
{
35
    /**
36
     * Name of the container class, used to create the container.
37
     * @var string
38
     */
39
    private $containerClass;
40
41
    /**
42
     * @var bool
43
     */
44
    private $useAutowiring = true;
45
46
    /**
47
     * @var bool
48
     */
49
    private $useAnnotations = false;
50
51
    /**
52
     * @var bool
53
     */
54
    private $ignorePhpDocErrors = false;
55
56
    /**
57
     * If true, write the proxies to disk to improve performances.
58
     * @var bool
59
     */
60
    private $writeProxiesToFile = false;
61
62
    /**
63
     * Directory where to write the proxies (if $writeProxiesToFile is enabled).
64
     * @var string
65
     */
66
    private $proxyDirectory;
67
68
    /**
69
     * If PHP-DI is wrapped in another container, this references the wrapper.
70
     * @var ContainerInterface
71
     */
72
    private $wrapperContainer;
73
74
    /**
75
     * @var DefinitionSource[]|string[]|array[]
76
     */
77
    private $definitionSources = [];
78
79
    /**
80
     * Whether the container has already been built.
81
     * @var bool
82
     */
83
    private $locked = false;
84
85
    /**
86
     * @var string|null
87
     */
88
    private $compileToDirectory;
89
90
    /**
91
     * Build a container configured for the dev environment.
92
     */
93
    public static function buildDevContainer() : Container
94
    {
95
        return new Container;
96
    }
97
98
    /**
99
     * @param string $containerClass Name of the container class, used to create the container.
100
     */
101
    public function __construct(string $containerClass = 'DI\Container')
102
    {
103
        $this->containerClass = $containerClass;
104
    }
105
106
    /**
107
     * Build and return a container.
108
     *
109
     * @return Container
110
     */
111
    public function build()
112
    {
113
        $sources = array_reverse($this->definitionSources);
114
115
        if ($this->useAnnotations) {
116
            $autowiring = new AnnotationBasedAutowiring($this->ignorePhpDocErrors);
117
            $sources[] = $autowiring;
118
        } elseif ($this->useAutowiring) {
119
            $autowiring = new ReflectionBasedAutowiring;
120
            $sources[] = $autowiring;
121
        } else {
122
            $autowiring = new NoAutowiring;
123
        }
124
125
        $sources = array_map(function ($definitions) use ($autowiring) {
126
            if (is_string($definitions)) {
127
                // File
128
                return new DefinitionFile($definitions, $autowiring);
129
            } elseif (is_array($definitions)) {
130
                return new DefinitionArray($definitions, $autowiring);
131
            }
132
133
            return $definitions;
134
        }, $sources);
135
        $source = new SourceChain($sources);
136
137
        // Mutable definition source
138
        $source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));
139
140
        $proxyFactory = new ProxyFactory($this->writeProxiesToFile, $this->proxyDirectory);
141
142
        $this->locked = true;
143
144
        $containerClass = $this->containerClass;
145
146
        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...
147
            $compiler = new Compiler;
148
            $compiledContainerFile = $compiler->compile($source, $this->compileToDirectory, $containerClass);
149
            // Only load the file if it hasn't been already loaded
150
            // (the container can be created multiple times in the same process)
151
            if (!class_exists($containerClass, false)) {
152
                require $compiledContainerFile;
153
            }
154
        }
155
156
        return new $containerClass($source, $proxyFactory, $this->wrapperContainer);
157
    }
158
159
    /**
160
     * Compile the container for optimum performances.
161
     *
162
     * Be aware that the container is compiled once and never updated!
163
     *
164
     * Therefore:
165
     *
166
     * - in production you should clear that directory every time you deploy
167
     * - in development you should not compile the container
168
     *
169
     * If you provide a filename it must be a valid class name! For example:
170
     *
171
     * - `ContainerProd.php` -> valid since `ContainerProd` is a valid class name
172
     * - `Container-Prod.php` -> invalid since `Container-Prod` is NOT a valid class name
173
     *
174
     * @param string $directory Directory in which to put the compiled container.
175
     * @param string $className Name of the class. Customize only if necessary.
176
     */
177
    public function enableCompilation(string $directory, string $className = 'CompiledContainer') : ContainerBuilder
178
    {
179
        $this->ensureNotLocked();
180
181
        $this->compileToDirectory = $directory;
182
        $this->containerClass = $className;
183
184
        return $this;
185
    }
186
187
    /**
188
     * Enable or disable the use of autowiring to guess injections.
189
     *
190
     * Enabled by default.
191
     *
192
     * @return $this
193
     */
194
    public function useAutowiring(bool $bool) : ContainerBuilder
195
    {
196
        $this->ensureNotLocked();
197
198
        $this->useAutowiring = $bool;
199
200
        return $this;
201
    }
202
203
    /**
204
     * Enable or disable the use of annotations to guess injections.
205
     *
206
     * Disabled by default.
207
     *
208
     * @return $this
209
     */
210
    public function useAnnotations(bool $bool) : ContainerBuilder
211
    {
212
        $this->ensureNotLocked();
213
214
        $this->useAnnotations = $bool;
215
216
        return $this;
217
    }
218
219
    /**
220
     * Enable or disable ignoring phpdoc errors (non-existent classes in [email protected]` or [email protected]`).
221
     *
222
     * @return $this
223
     */
224
    public function ignorePhpDocErrors(bool $bool) : ContainerBuilder
225
    {
226
        $this->ensureNotLocked();
227
228
        $this->ignorePhpDocErrors = $bool;
229
230
        return $this;
231
    }
232
233
    /**
234
     * Configure the proxy generation.
235
     *
236
     * For dev environment, use writeProxiesToFile(false) (default configuration)
237
     * For production environment, use writeProxiesToFile(true, 'tmp/proxies')
238
     *
239
     * @param bool $writeToFile If true, write the proxies to disk to improve performances
240
     * @param string|null $proxyDirectory Directory where to write the proxies
241
     * @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null
242
     * @return $this
243
     */
244
    public function writeProxiesToFile(bool $writeToFile, string $proxyDirectory = null) : ContainerBuilder
245
    {
246
        $this->ensureNotLocked();
247
248
        $this->writeProxiesToFile = $writeToFile;
249
250
        if ($writeToFile && $proxyDirectory === null) {
251
            throw new InvalidArgumentException(
252
                'The proxy directory must be specified if you want to write proxies on disk'
253
            );
254
        }
255
        $this->proxyDirectory = $proxyDirectory;
256
257
        return $this;
258
    }
259
260
    /**
261
     * If PHP-DI's container is wrapped by another container, we can
262
     * set this so that PHP-DI will use the wrapper rather than itself for building objects.
263
     *
264
     * @return $this
265
     */
266
    public function wrapContainer(ContainerInterface $otherContainer) : ContainerBuilder
267
    {
268
        $this->ensureNotLocked();
269
270
        $this->wrapperContainer = $otherContainer;
271
272
        return $this;
273
    }
274
275
    /**
276
     * Add definitions to the container.
277
     *
278
     * @param string|array|DefinitionSource $definitions Can be an array of definitions, the
279
     *                                                   name of a file containing definitions
280
     *                                                   or a DefinitionSource object.
281
     * @return $this
282
     */
283
    public function addDefinitions($definitions) : ContainerBuilder
284
    {
285
        $this->ensureNotLocked();
286
287
        if (!is_string($definitions) && !is_array($definitions) && !($definitions instanceof DefinitionSource)) {
288
            throw new InvalidArgumentException(sprintf(
289
                '%s parameter must be a string, an array or a DefinitionSource object, %s given',
290
                'ContainerBuilder::addDefinitions()',
291
                is_object($definitions) ? get_class($definitions) : gettype($definitions)
292
            ));
293
        }
294
295
        $this->definitionSources[] = $definitions;
296
297
        return $this;
298
    }
299
300
    /**
301
     * Are we building a compiled container?
302
     */
303
    public function isCompiled() : bool
304
    {
305
        return (bool) $this->compileToDirectory;
306
    }
307
308
    private function ensureNotLocked()
309
    {
310
        if ($this->locked) {
311
            throw new \LogicException('The ContainerBuilder cannot be modified after the container has been built');
312
        }
313
    }
314
}
315