1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @file |
5
|
|
|
* Contains \Drupal\Core\DependencyInjection\YamlFileLoader. |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Drupal\Core\DependencyInjection; |
9
|
|
|
|
10
|
|
|
use Drupal\Component\FileCache\FileCacheFactory; |
11
|
|
|
use Drupal\Component\Serialization\Yaml; |
12
|
|
|
use Symfony\Component\DependencyInjection\Alias; |
13
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
14
|
|
|
use Symfony\Component\DependencyInjection\Definition; |
15
|
|
|
use Symfony\Component\DependencyInjection\DefinitionDecorator; |
16
|
|
|
use Symfony\Component\DependencyInjection\Reference; |
17
|
|
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* YamlFileLoader loads YAML files service definitions. |
21
|
|
|
* |
22
|
|
|
* Drupal does not use Symfony's Config component, and Symfony's dependency on |
23
|
|
|
* it cannot be removed easily. Therefore, this is a partial but mostly literal |
24
|
|
|
* copy of upstream, which does not depend on the Config component. |
25
|
|
|
* |
26
|
|
|
* @see \Symfony\Component\DependencyInjection\Loader\YamlFileLoader |
27
|
|
|
* @see https://github.com/symfony/symfony/pull/10920 |
28
|
|
|
* |
29
|
|
|
* NOTE: 98% of this code is a literal copy of Symfony's YamlFileLoader. |
30
|
|
|
* |
31
|
|
|
* This file does NOT follow Drupal coding standards, so as to simplify future |
32
|
|
|
* synchronizations. |
33
|
|
|
*/ |
34
|
|
|
class YamlFileLoader |
35
|
|
|
{ |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var \Drupal\Core\DependencyInjection\ContainerBuilder $container |
39
|
|
|
*/ |
40
|
|
|
protected $container; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* File cache object. |
44
|
|
|
* |
45
|
|
|
* @var \Drupal\Component\FileCache\FileCacheInterface |
46
|
|
|
*/ |
47
|
|
|
protected $fileCache; |
48
|
|
|
|
49
|
|
|
|
50
|
|
|
public function __construct(ContainerBuilder $container) |
51
|
|
|
{ |
52
|
|
|
$this->container = $container; |
53
|
|
|
$this->fileCache = FileCacheFactory::get('container_yaml_loader'); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Loads a Yaml file. |
58
|
|
|
* |
59
|
|
|
* @param mixed $file The resource |
60
|
|
|
*/ |
61
|
|
|
public function load($file) |
62
|
|
|
{ |
63
|
|
|
// Load from the file cache, fall back to loading the file. |
64
|
|
|
// @todo Refactor this to cache parsed definition objects in |
65
|
|
|
// https://www.drupal.org/node/2464053 |
66
|
|
|
$content = $this->fileCache->get($file); |
67
|
|
|
if (!$content) { |
68
|
|
|
$content = $this->loadFile($file); |
69
|
|
|
$this->fileCache->set($file, $content); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
// Not supported. |
73
|
|
|
//$this->container->addResource(new FileResource($path)); |
74
|
|
|
|
75
|
|
|
// empty file |
76
|
|
|
if (null === $content) { |
77
|
|
|
return; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
// imports |
81
|
|
|
// Not supported. |
82
|
|
|
//$this->parseImports($content, $file); |
83
|
|
|
|
84
|
|
|
// parameters |
85
|
|
View Code Duplication |
if (isset($content['parameters'])) { |
|
|
|
|
86
|
|
|
if (!is_array($content['parameters'])) { |
87
|
|
|
throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $file)); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
foreach ($content['parameters'] as $key => $value) { |
91
|
|
|
$this->container->setParameter($key, $this->resolveServices($value)); |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
// extensions |
96
|
|
|
// Not supported. |
97
|
|
|
//$this->loadFromExtensions($content); |
98
|
|
|
|
99
|
|
|
// services |
100
|
|
|
$this->parseDefinitions($content, $file); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Parses definitions |
105
|
|
|
* |
106
|
|
|
* @param array $content |
107
|
|
|
* @param string $file |
108
|
|
|
*/ |
109
|
|
View Code Duplication |
private function parseDefinitions($content, $file) |
110
|
|
|
{ |
111
|
|
|
if (!isset($content['services'])) { |
112
|
|
|
return; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
if (!is_array($content['services'])) { |
116
|
|
|
throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file)); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
foreach ($content['services'] as $id => $service) { |
120
|
|
|
$this->parseDefinition($id, $service, $file); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Parses a definition. |
126
|
|
|
* |
127
|
|
|
* @param string $id |
128
|
|
|
* @param array $service |
129
|
|
|
* @param string $file |
130
|
|
|
* |
131
|
|
|
* @throws InvalidArgumentException When tags are invalid |
132
|
|
|
*/ |
133
|
|
|
private function parseDefinition($id, $service, $file) |
134
|
|
|
{ |
135
|
|
View Code Duplication |
if (is_string($service) && 0 === strpos($service, '@')) { |
|
|
|
|
136
|
|
|
$this->container->setAlias($id, substr($service, 1)); |
137
|
|
|
|
138
|
|
|
return; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
View Code Duplication |
if (!is_array($service)) { |
|
|
|
|
142
|
|
|
throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but %s found for service "%s" in %s. Check your YAML syntax.', gettype($service), $id, $file)); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
View Code Duplication |
if (isset($service['alias'])) { |
|
|
|
|
146
|
|
|
$public = !array_key_exists('public', $service) || (bool) $service['public']; |
147
|
|
|
$this->container->setAlias($id, new Alias($service['alias'], $public)); |
148
|
|
|
|
149
|
|
|
return; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
View Code Duplication |
if (isset($service['parent'])) { |
|
|
|
|
153
|
|
|
$definition = new DefinitionDecorator($service['parent']); |
154
|
|
|
} else { |
155
|
|
|
$definition = new Definition(); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
if (isset($service['class'])) { |
159
|
|
|
$definition->setClass($service['class']); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
if (isset($service['scope'])) { |
163
|
|
|
$definition->setScope($service['scope']); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
if (isset($service['synthetic'])) { |
167
|
|
|
$definition->setSynthetic($service['synthetic']); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
if (isset($service['synchronized'])) { |
171
|
|
|
$definition->setSynchronized($service['synchronized'], 'request' !== $id); |
|
|
|
|
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
if (isset($service['lazy'])) { |
175
|
|
|
$definition->setLazy($service['lazy']); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
if (isset($service['public'])) { |
179
|
|
|
$definition->setPublic($service['public']); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
if (isset($service['abstract'])) { |
183
|
|
|
$definition->setAbstract($service['abstract']); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
View Code Duplication |
if (isset($service['factory'])) { |
|
|
|
|
187
|
|
|
if (is_string($service['factory'])) { |
188
|
|
|
if (strpos($service['factory'], ':') !== false && strpos($service['factory'], '::') === false) { |
189
|
|
|
$parts = explode(':', $service['factory']); |
190
|
|
|
$definition->setFactory(array($this->resolveServices('@'.$parts[0]), $parts[1])); |
191
|
|
|
} else { |
192
|
|
|
$definition->setFactory($service['factory']); |
193
|
|
|
} |
194
|
|
|
} else { |
195
|
|
|
$definition->setFactory(array($this->resolveServices($service['factory'][0]), $service['factory'][1])); |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if (isset($service['factory_class'])) { |
200
|
|
|
$definition->setFactoryClass($service['factory_class']); |
|
|
|
|
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
if (isset($service['factory_method'])) { |
204
|
|
|
$definition->setFactoryMethod($service['factory_method']); |
|
|
|
|
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
if (isset($service['factory_service'])) { |
208
|
|
|
$definition->setFactoryService($service['factory_service']); |
|
|
|
|
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if (isset($service['file'])) { |
212
|
|
|
$definition->setFile($service['file']); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
if (isset($service['arguments'])) { |
216
|
|
|
$definition->setArguments($this->resolveServices($service['arguments'])); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
if (isset($service['properties'])) { |
220
|
|
|
$definition->setProperties($this->resolveServices($service['properties'])); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
View Code Duplication |
if (isset($service['configurator'])) { |
|
|
|
|
224
|
|
|
if (is_string($service['configurator'])) { |
225
|
|
|
$definition->setConfigurator($service['configurator']); |
226
|
|
|
} else { |
227
|
|
|
$definition->setConfigurator(array($this->resolveServices($service['configurator'][0]), $service['configurator'][1])); |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
View Code Duplication |
if (isset($service['calls'])) { |
|
|
|
|
232
|
|
|
if (!is_array($service['calls'])) { |
233
|
|
|
throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
foreach ($service['calls'] as $call) { |
237
|
|
|
if (isset($call['method'])) { |
238
|
|
|
$method = $call['method']; |
239
|
|
|
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array(); |
240
|
|
|
} else { |
241
|
|
|
$method = $call[0]; |
242
|
|
|
$args = isset($call[1]) ? $this->resolveServices($call[1]) : array(); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
$definition->addMethodCall($method, $args); |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
|
249
|
|
View Code Duplication |
if (isset($service['tags'])) { |
|
|
|
|
250
|
|
|
if (!is_array($service['tags'])) { |
251
|
|
|
throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
foreach ($service['tags'] as $tag) { |
255
|
|
|
if (!is_array($tag)) { |
256
|
|
|
throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
if (!isset($tag['name'])) { |
260
|
|
|
throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file)); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
$name = $tag['name']; |
264
|
|
|
unset($tag['name']); |
265
|
|
|
|
266
|
|
|
foreach ($tag as $attribute => $value) { |
267
|
|
|
if (!is_scalar($value) && null !== $value) { |
268
|
|
|
throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file)); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
$definition->addTag($name, $tag); |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
|
276
|
|
View Code Duplication |
if (isset($service['decorates'])) { |
|
|
|
|
277
|
|
|
$renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null; |
278
|
|
|
$definition->setDecoratedService($service['decorates'], $renameId); |
|
|
|
|
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
$this->container->setDefinition($id, $definition); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Loads a YAML file. |
286
|
|
|
* |
287
|
|
|
* @param string $file |
288
|
|
|
* |
289
|
|
|
* @return array The file content |
290
|
|
|
* |
291
|
|
|
* @throws InvalidArgumentException when the given file is not a local file or when it does not exist |
292
|
|
|
*/ |
293
|
|
|
protected function loadFile($file) |
294
|
|
|
{ |
295
|
|
|
if (!stream_is_local($file)) { |
296
|
|
|
throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file)); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
if (!file_exists($file)) { |
300
|
|
|
throw new InvalidArgumentException(sprintf('The service file "%s" is not valid.', $file)); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
return $this->validate(Yaml::decode(file_get_contents($file)), $file); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* Validates a YAML file. |
308
|
|
|
* |
309
|
|
|
* @param mixed $content |
310
|
|
|
* @param string $file |
311
|
|
|
* |
312
|
|
|
* @return array |
313
|
|
|
* |
314
|
|
|
* @throws InvalidArgumentException When service file is not valid |
315
|
|
|
*/ |
316
|
|
|
private function validate($content, $file) |
317
|
|
|
{ |
318
|
|
|
if (null === $content) { |
319
|
|
|
return $content; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
if (!is_array($content)) { |
323
|
|
|
throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file)); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
if ($invalid_keys = array_diff_key($content, array('parameters' => 1, 'services' => 1))) { |
327
|
|
|
throw new InvalidArgumentException(sprintf('The service file "%s" is not valid: it contains invalid keys %s. Services have to be added under "services" and Parameters under "parameters".', $file, $invalid_keys)); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
return $content; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Resolves services. |
335
|
|
|
* |
336
|
|
|
* @param string|array $value |
337
|
|
|
* |
338
|
|
|
* @return array|string|Reference |
339
|
|
|
*/ |
340
|
|
View Code Duplication |
private function resolveServices($value) |
341
|
|
|
{ |
342
|
|
|
if (is_array($value)) { |
343
|
|
|
$value = array_map(array($this, 'resolveServices'), $value); |
344
|
|
|
} elseif (is_string($value) && 0 === strpos($value, '@=')) { |
345
|
|
|
// Not supported. |
346
|
|
|
//return new Expression(substr($value, 2)); |
347
|
|
|
throw new InvalidArgumentException(sprintf("'%s' is an Expression, but expressions are not supported.", $value)); |
348
|
|
|
} elseif (is_string($value) && 0 === strpos($value, '@')) { |
349
|
|
|
if (0 === strpos($value, '@@')) { |
350
|
|
|
$value = substr($value, 1); |
351
|
|
|
$invalidBehavior = null; |
352
|
|
|
} elseif (0 === strpos($value, '@?')) { |
353
|
|
|
$value = substr($value, 2); |
354
|
|
|
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; |
355
|
|
|
} else { |
356
|
|
|
$value = substr($value, 1); |
357
|
|
|
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
if ('=' === substr($value, -1)) { |
361
|
|
|
$value = substr($value, 0, -1); |
362
|
|
|
$strict = false; |
363
|
|
|
} else { |
364
|
|
|
$strict = true; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
if (null !== $invalidBehavior) { |
368
|
|
|
$value = new Reference($value, $invalidBehavior, $strict); |
369
|
|
|
} |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
return $value; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
} |
376
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.