Completed
Push — master ( 46bc02...4d66a1 )
by Matthieu
12s
created

ContainerBuilder::build()   B

Complexity

Conditions 8
Paths 9

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 53
Code Lines 33

Importance

Changes 0
Metric Value
cc 8
eloc 33
nc 9
nop 0
dl 0
loc 53
rs 7.1199
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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