Security update for Core, with self-updated composer
[yaffs-website] / web / core / tests / Drupal / Tests / Component / DependencyInjection / Dumper / OptimizedPhpArrayDumperTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest.
6  */
7
8 namespace Drupal\Tests\Component\DependencyInjection\Dumper {
9
10   use Drupal\Component\Utility\Crypt;
11   use PHPUnit\Framework\TestCase;
12   use Symfony\Component\DependencyInjection\Definition;
13   use Symfony\Component\DependencyInjection\Reference;
14   use Symfony\Component\DependencyInjection\Parameter;
15   use Symfony\Component\ExpressionLanguage\Expression;
16   use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
17   use Symfony\Component\DependencyInjection\ContainerInterface;
18   use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
19   use Symfony\Component\DependencyInjection\Exception\RuntimeException;
20
21   /**
22    * @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
23    * @group DependencyInjection
24    */
25   class OptimizedPhpArrayDumperTest extends TestCase {
26
27     /**
28      * The container builder instance.
29      *
30      * @var \Symfony\Component\DependencyInjection\ContainerBuilder
31      */
32     protected $containerBuilder;
33
34     /**
35      * The definition for the container to build in tests.
36      *
37      * @var array
38      */
39     protected $containerDefinition;
40
41     /**
42      * Whether the dumper uses the machine-optimized format or not.
43      *
44      * @var bool
45      */
46     protected $machineFormat = TRUE;
47
48     /**
49      * Stores the dumper class to use.
50      *
51      * @var string
52      */
53     protected $dumperClass = '\Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper';
54
55     /**
56      * The dumper instance.
57      *
58      * @var \Symfony\Component\DependencyInjection\Dumper\DumperInterface
59      */
60     protected $dumper;
61
62     /**
63      * {@inheritdoc}
64      */
65     protected function setUp() {
66       // Setup a mock container builder.
67       $this->containerBuilder = $this->prophesize('\Symfony\Component\DependencyInjection\ContainerBuilder');
68       $this->containerBuilder->getAliases()->willReturn([]);
69       $this->containerBuilder->getParameterBag()->willReturn(new ParameterBag());
70       $this->containerBuilder->getDefinitions()->willReturn(NULL);
71       $this->containerBuilder->isFrozen()->willReturn(TRUE);
72
73       $definition = [];
74       $definition['aliases'] = [];
75       $definition['parameters'] = [];
76       $definition['services'] = [];
77       $definition['frozen'] = TRUE;
78       $definition['machine_format'] = $this->machineFormat;
79
80       $this->containerDefinition = $definition;
81
82       // Create the dumper.
83       $this->dumper = new $this->dumperClass($this->containerBuilder->reveal());
84     }
85
86     /**
87      * Tests that an empty container works properly.
88      *
89      * @covers ::dump
90      * @covers ::getArray
91      * @covers ::supportsMachineFormat
92      */
93     public function testDumpForEmptyContainer() {
94       $serialized_definition = $this->dumper->dump();
95       $this->assertEquals(serialize($this->containerDefinition), $serialized_definition);
96     }
97
98     /**
99      * Tests that alias processing works properly.
100      *
101      * @covers ::getAliases
102      *
103      * @dataProvider getAliasesDataProvider
104      */
105     public function testGetAliases($aliases, $definition_aliases) {
106       $this->containerDefinition['aliases'] = $definition_aliases;
107       $this->containerBuilder->getAliases()->willReturn($aliases);
108       $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
109     }
110
111     /**
112      * Data provider for testGetAliases().
113      *
114      * @return array[]
115      *   Returns data-set elements with:
116      *     - aliases as returned by ContainerBuilder.
117      *     - aliases as expected in the container definition.
118      */
119     public function getAliasesDataProvider() {
120       return [
121         [[], []],
122         [
123           ['foo' => 'foo.alias'],
124           ['foo' => 'foo.alias'],
125         ],
126         [
127           ['foo' => 'foo.alias', 'foo.alias' => 'foo.alias.alias'],
128           ['foo' => 'foo.alias.alias', 'foo.alias' => 'foo.alias.alias'],
129         ],
130       ];
131     }
132
133     /**
134      * Tests that parameter processing works properly.
135      *
136      * @covers ::getParameters
137      * @covers ::prepareParameters
138      * @covers ::escape
139      * @covers ::dumpValue
140      * @covers ::getReferenceCall
141      *
142      * @dataProvider getParametersDataProvider
143      */
144     public function testGetParameters($parameters, $definition_parameters, $is_frozen) {
145       $this->containerDefinition['parameters'] = $definition_parameters;
146       $this->containerDefinition['frozen'] = $is_frozen;
147
148       $parameter_bag = new ParameterBag($parameters);
149       $this->containerBuilder->getParameterBag()->willReturn($parameter_bag);
150       $this->containerBuilder->isFrozen()->willReturn($is_frozen);
151
152       if (isset($parameters['reference'])) {
153         $definition = new Definition('\stdClass');
154         $this->containerBuilder->getDefinition('referenced_service')->willReturn($definition);
155       }
156
157       $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
158     }
159
160     /**
161      * Data provider for testGetParameters().
162      *
163      * @return array[]
164      *   Returns data-set elements with:
165      *     - parameters as returned by ContainerBuilder.
166      *     - parameters as expected in the container definition.
167      *     - frozen value
168      */
169     public function getParametersDataProvider() {
170       return [
171         [[], [], TRUE],
172         [
173           ['foo' => 'value_foo'],
174           ['foo' => 'value_foo'],
175           TRUE,
176         ],
177         [
178           ['foo' => ['llama' => 'yes']],
179           ['foo' => ['llama' => 'yes']],
180           TRUE,
181         ],
182         [
183           ['foo' => '%llama%', 'llama' => 'yes'],
184           ['foo' => '%%llama%%', 'llama' => 'yes'],
185           TRUE,
186         ],
187         [
188           ['foo' => '%llama%', 'llama' => 'yes'],
189           ['foo' => '%llama%', 'llama' => 'yes'],
190           FALSE,
191         ],
192         [
193           ['reference' => new Reference('referenced_service')],
194           ['reference' => $this->getServiceCall('referenced_service')],
195           TRUE,
196         ],
197       ];
198     }
199
200     /**
201      * Tests that service processing works properly.
202      *
203      * @covers ::getServiceDefinitions
204      * @covers ::getServiceDefinition
205      * @covers ::dumpMethodCalls
206      * @covers ::dumpCollection
207      * @covers ::dumpCallable
208      * @covers ::dumpValue
209      * @covers ::getPrivateServiceCall
210      * @covers ::getReferenceCall
211      * @covers ::getServiceCall
212      * @covers ::getParameterCall
213      *
214      * @dataProvider getDefinitionsDataProvider
215      *
216      * @group legacy
217      */
218     public function testGetServiceDefinitions($services, $definition_services) {
219       $this->containerDefinition['services'] = $definition_services;
220
221       $this->containerBuilder->getDefinitions()->willReturn($services);
222
223       $bar_definition = new Definition('\stdClass');
224       $this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
225
226       $private_definition = new Definition('\stdClass');
227       $private_definition->setPublic(FALSE);
228
229       $this->containerBuilder->getDefinition('private_definition')->willReturn($private_definition);
230
231       $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
232     }
233
234     /**
235      * Data provider for testGetServiceDefinitions().
236      *
237      * @return array[]
238      *   Returns data-set elements with:
239      *     - parameters as returned by ContainerBuilder.
240      *     - parameters as expected in the container definition.
241      *     - frozen value
242      */
243     public function getDefinitionsDataProvider() {
244       $base_service_definition = [
245         'class' => '\stdClass',
246         'public' => TRUE,
247         'file' => FALSE,
248         'synthetic' => FALSE,
249         'lazy' => FALSE,
250         'arguments' => [],
251         'arguments_count' => 0,
252         'properties' => [],
253         'calls' => [],
254         'shared' => TRUE,
255         'factory' => FALSE,
256         'configurator' => FALSE,
257       ];
258
259       // Test basic flags.
260       $service_definitions[] = [] + $base_service_definition;
261
262       $service_definitions[] = [
263         'public' => FALSE,
264       ] + $base_service_definition;
265
266       $service_definitions[] = [
267         'file' => 'test_include.php',
268       ] + $base_service_definition;
269
270       $service_definitions[] = [
271         'synthetic' => TRUE,
272       ] + $base_service_definition;
273
274       $service_definitions[] = [
275         'shared' => FALSE,
276       ] + $base_service_definition;
277
278       $service_definitions[] = [
279         'lazy' => TRUE,
280       ] + $base_service_definition;
281
282       // Test a basic public Reference.
283       $service_definitions[] = [
284         'arguments' => ['foo', new Reference('bar')],
285         'arguments_count' => 2,
286         'arguments_expected' => $this->getCollection(['foo', $this->getServiceCall('bar')]),
287       ] + $base_service_definition;
288
289       // Test a public reference that should not throw an Exception.
290       $reference = new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE);
291       $service_definitions[] = [
292         'arguments' => [$reference],
293         'arguments_count' => 1,
294         'arguments_expected' => $this->getCollection([$this->getServiceCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)]),
295       ] + $base_service_definition;
296
297       // Test a private shared service, denoted by having a Reference.
298       $private_definition = [
299         'class' => '\stdClass',
300         'public' => FALSE,
301         'arguments_count' => 0,
302       ];
303
304       $service_definitions[] = [
305         'arguments' => ['foo', new Reference('private_definition')],
306         'arguments_count' => 2,
307         'arguments_expected' => $this->getCollection([
308           'foo',
309           $this->getPrivateServiceCall('private_definition', $private_definition, TRUE),
310         ]),
311       ] + $base_service_definition;
312
313       // Test a private non-shared service, denoted by having a Definition.
314       $private_definition_object = new Definition('\stdClass');
315       $private_definition_object->setPublic(FALSE);
316
317       $service_definitions[] = [
318         'arguments' => ['foo', $private_definition_object],
319         'arguments_count' => 2,
320         'arguments_expected' => $this->getCollection([
321           'foo',
322           $this->getPrivateServiceCall(NULL, $private_definition),
323         ]),
324       ] + $base_service_definition;
325
326       // Test a deep collection without a reference.
327       $service_definitions[] = [
328         'arguments' => [[['foo']]],
329         'arguments_count' => 1,
330       ] + $base_service_definition;
331
332       // Test a deep collection with a reference to resolve.
333       $service_definitions[] = [
334         'arguments' => [[new Reference('bar')]],
335         'arguments_count' => 1,
336         'arguments_expected' => $this->getCollection([$this->getCollection([$this->getServiceCall('bar')])]),
337       ] + $base_service_definition;
338
339       // Test a collection with a variable to resolve.
340       $service_definitions[] = [
341         'arguments' => [new Parameter('llama_parameter')],
342         'arguments_count' => 1,
343         'arguments_expected' => $this->getCollection([$this->getParameterCall('llama_parameter')]),
344       ] + $base_service_definition;
345
346       // Test objects that have _serviceId property.
347       $drupal_service = new \stdClass();
348       $drupal_service->_serviceId = 'bar';
349
350       $service_definitions[] = [
351         'arguments' => [$drupal_service],
352         'arguments_count' => 1,
353         'arguments_expected' => $this->getCollection([$this->getServiceCall('bar')]),
354       ] + $base_service_definition;
355
356       // Test getMethodCalls.
357       $calls = [
358         ['method', $this->getCollection([])],
359         ['method2', $this->getCollection([])],
360       ];
361       $service_definitions[] = [
362         'calls' => $calls,
363       ] + $base_service_definition;
364
365       $service_definitions[] = [
366           'shared' => FALSE,
367         ] + $base_service_definition;
368
369       // Test factory.
370       $service_definitions[] = [
371         'factory' => [new Reference('bar'), 'factoryMethod'],
372         'factory_expected' => [$this->getServiceCall('bar'), 'factoryMethod'],
373       ] + $base_service_definition;
374
375       // Test invalid factory - needed to test deep dumpValue().
376       $service_definitions[] = [
377         'factory' => [['foo', 'llama'], 'factoryMethod'],
378       ] + $base_service_definition;
379
380       // Test properties.
381       $service_definitions[] = [
382         'properties' => ['_value' => 'llama'],
383       ] + $base_service_definition;
384
385       // Test configurator.
386       $service_definitions[] = [
387         'configurator' => [new Reference('bar'), 'configureService'],
388         'configurator_expected' => [$this->getServiceCall('bar'), 'configureService'],
389       ] + $base_service_definition;
390
391       $services_provided = [];
392       $services_provided[] = [
393         [],
394         [],
395       ];
396
397       foreach ($service_definitions as $service_definition) {
398         $definition = $this->prophesize('\Symfony\Component\DependencyInjection\Definition');
399         $definition->getClass()->willReturn($service_definition['class']);
400         $definition->isPublic()->willReturn($service_definition['public']);
401         $definition->getFile()->willReturn($service_definition['file']);
402         $definition->isSynthetic()->willReturn($service_definition['synthetic']);
403         $definition->isLazy()->willReturn($service_definition['lazy']);
404         $definition->getArguments()->willReturn($service_definition['arguments']);
405         $definition->getProperties()->willReturn($service_definition['properties']);
406         $definition->getMethodCalls()->willReturn($service_definition['calls']);
407         $definition->isShared()->willReturn($service_definition['shared']);
408         $definition->getDecoratedService()->willReturn(NULL);
409         $definition->getFactory()->willReturn($service_definition['factory']);
410         $definition->getConfigurator()->willReturn($service_definition['configurator']);
411
412         // Preserve order.
413         $filtered_service_definition = [];
414         foreach ($base_service_definition as $key => $value) {
415           $filtered_service_definition[$key] = $service_definition[$key];
416           unset($service_definition[$key]);
417
418           if ($key == 'class' || $key == 'arguments_count') {
419             continue;
420           }
421
422           if ($filtered_service_definition[$key] === $base_service_definition[$key]) {
423             unset($filtered_service_definition[$key]);
424           }
425         }
426
427         // Add remaining properties.
428         $filtered_service_definition += $service_definition;
429
430         // Allow to set _expected values.
431         foreach (['arguments', 'factory', 'configurator'] as $key) {
432           $expected = $key . '_expected';
433           if (isset($filtered_service_definition[$expected])) {
434             $filtered_service_definition[$key] = $filtered_service_definition[$expected];
435             unset($filtered_service_definition[$expected]);
436           }
437         }
438
439         if (isset($filtered_service_definition['public']) && $filtered_service_definition['public'] === FALSE) {
440           $services_provided[] = [
441             ['foo_service' => $definition->reveal()],
442             [],
443           ];
444           continue;
445         }
446
447         $services_provided[] = [
448           ['foo_service' => $definition->reveal()],
449           ['foo_service' => $this->serializeDefinition($filtered_service_definition)],
450         ];
451       }
452
453       return $services_provided;
454     }
455
456     /**
457      * Helper function to serialize a definition.
458      *
459      * Used to override serialization.
460      */
461     protected function serializeDefinition(array $service_definition) {
462       return serialize($service_definition);
463     }
464
465     /**
466      * Helper function to return a service definition.
467      */
468     protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
469       return (object) [
470         'type' => 'service',
471         'id' => $id,
472         'invalidBehavior' => $invalid_behavior,
473       ];
474     }
475
476     /**
477      * Tests that references to aliases work correctly.
478      *
479      * @covers ::getReferenceCall
480      *
481      * @dataProvider publicPrivateDataProvider
482      *
483      * @group legacy
484      */
485     public function testGetServiceDefinitionWithReferenceToAlias($public) {
486       $bar_definition = new Definition('\stdClass');
487       $bar_definition_php_array = [
488         'class' => '\stdClass',
489       ];
490       if (!$public) {
491         $bar_definition->setPublic(FALSE);
492         $bar_definition_php_array['public'] = FALSE;
493       }
494       $bar_definition_php_array['arguments_count'] = 0;
495
496       $services['bar'] = $bar_definition;
497
498       $aliases['bar.alias'] = 'bar';
499
500       $foo = new Definition('\stdClass');
501       $foo->addArgument(new Reference('bar.alias'));
502
503       $services['foo'] = $foo;
504
505       $this->containerBuilder->getAliases()->willReturn($aliases);
506       $this->containerBuilder->getDefinitions()->willReturn($services);
507       $this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
508       $dump = $this->dumper->getArray();
509       if ($public) {
510         $service_definition = $this->getServiceCall('bar');
511       }
512       else {
513         $service_definition = $this->getPrivateServiceCall('bar', $bar_definition_php_array, TRUE);
514       }
515       $data = [
516          'class' => '\stdClass',
517          'arguments' => $this->getCollection([
518            $service_definition,
519          ]),
520          'arguments_count' => 1,
521       ];
522       $this->assertEquals($this->serializeDefinition($data), $dump['services']['foo'], 'Expected definition matches dump.');
523     }
524
525     public function publicPrivateDataProvider() {
526       return [
527         [TRUE],
528         [FALSE],
529       ];
530     }
531
532     /**
533      * Tests that getDecoratedService() is unsupported.
534      *
535      * Tests that the correct InvalidArgumentException is thrown for
536      * getDecoratedService().
537      *
538      * @covers ::getServiceDefinition
539      *
540      * @group legacy
541      */
542     public function testGetServiceDefinitionForDecoratedService() {
543       $bar_definition = new Definition('\stdClass');
544       $bar_definition->setDecoratedService(new Reference('foo'));
545       $services['bar'] = $bar_definition;
546
547       $this->containerBuilder->getDefinitions()->willReturn($services);
548       $this->setExpectedException(InvalidArgumentException::class);
549       $this->dumper->getArray();
550     }
551
552     /**
553      * Tests that the correct RuntimeException is thrown for expressions.
554      *
555      * @covers ::dumpValue
556      */
557     public function testGetServiceDefinitionForExpression() {
558       $expression = new Expression();
559
560       $bar_definition = new Definition('\stdClass');
561       $bar_definition->addArgument($expression);
562       $services['bar'] = $bar_definition;
563
564       $this->containerBuilder->getDefinitions()->willReturn($services);
565       $this->setExpectedException(RuntimeException::class);
566       $this->dumper->getArray();
567     }
568
569     /**
570      * Tests that the correct RuntimeException is thrown for dumping an object.
571      *
572      * @covers ::dumpValue
573      */
574     public function testGetServiceDefinitionForObject() {
575       $service = new \stdClass();
576
577       $bar_definition = new Definition('\stdClass');
578       $bar_definition->addArgument($service);
579       $services['bar'] = $bar_definition;
580
581       $this->containerBuilder->getDefinitions()->willReturn($services);
582       $this->setExpectedException(RuntimeException::class);
583       $this->dumper->getArray();
584     }
585
586     /**
587      * Tests that the correct RuntimeException is thrown for dumping a resource.
588      *
589      * @covers ::dumpValue
590      */
591     public function testGetServiceDefinitionForResource() {
592       $resource = fopen('php://memory', 'r');
593
594       $bar_definition = new Definition('\stdClass');
595       $bar_definition->addArgument($resource);
596       $services['bar'] = $bar_definition;
597
598       $this->containerBuilder->getDefinitions()->willReturn($services);
599       $this->setExpectedException(RuntimeException::class);
600       $this->dumper->getArray();
601     }
602
603     /**
604      * Helper function to return a private service definition.
605      */
606     protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
607       if (!$id) {
608         $hash = Crypt::hashBase64(serialize($service_definition));
609         $id = 'private__' . $hash;
610       }
611       return (object) [
612         'type' => 'private_service',
613         'id' => $id,
614         'value' => $service_definition,
615         'shared' => $shared,
616       ];
617     }
618
619     /**
620      * Helper function to return a machine-optimized collection.
621      */
622     protected function getCollection($collection, $resolve = TRUE) {
623       return (object) [
624         'type' => 'collection',
625         'value' => $collection,
626         'resolve' => $resolve,
627       ];
628     }
629
630     /**
631      * Helper function to return a parameter definition.
632      */
633     protected function getParameterCall($name) {
634       return (object) [
635         'type' => 'parameter',
636         'name' => $name,
637       ];
638     }
639
640   }
641
642 }
643
644 /**
645  * As Drupal Core does not ship with ExpressionLanguage component we need to
646  * define a dummy, else it cannot be tested.
647  */
648 namespace Symfony\Component\ExpressionLanguage {
649
650   if (!class_exists('\Symfony\Component\ExpressionLanguage\Expression')) {
651     /**
652      * Dummy class to ensure non-existent Symfony component can be tested.
653      */
654     class Expression {
655
656       /**
657        * Gets the string representation of the expression.
658        */
659       public function __toString() {
660         return 'dummy_expression';
661       }
662
663     }
664   }
665 }