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