Completed
Push — master ( ab2421...ac78a3 )
by Matthieu
02:21
created

ContainerBuilder::build()   C

Complexity

Conditions 7
Paths 9

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 46
Code Lines 27

Importance

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