5 * Contains \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest.
8 namespace Drupal\Tests\Component\DependencyInjection\Dumper {
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;
22 * @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
23 * @group DependencyInjection
25 class OptimizedPhpArrayDumperTest extends TestCase {
28 * The container builder instance.
30 * @var \Symfony\Component\DependencyInjection\ContainerBuilder
32 protected $containerBuilder;
35 * The definition for the container to build in tests.
39 protected $containerDefinition;
42 * Whether the dumper uses the machine-optimized format or not.
46 protected $machineFormat = TRUE;
49 * Stores the dumper class to use.
53 protected $dumperClass = '\Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper';
56 * The dumper instance.
58 * @var \Symfony\Component\DependencyInjection\Dumper\DumperInterface
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->isCompiled()->willReturn(TRUE);
74 $definition['aliases'] = [];
75 $definition['parameters'] = [];
76 $definition['services'] = [];
77 $definition['frozen'] = TRUE;
78 $definition['machine_format'] = $this->machineFormat;
80 $this->containerDefinition = $definition;
83 $this->dumper = new $this->dumperClass($this->containerBuilder->reveal());
87 * Tests that an empty container works properly.
91 * @covers ::supportsMachineFormat
93 public function testDumpForEmptyContainer() {
94 $serialized_definition = $this->dumper->dump();
95 $this->assertEquals(serialize($this->containerDefinition), $serialized_definition);
99 * Tests that alias processing works properly.
101 * @covers ::getAliases
103 * @dataProvider getAliasesDataProvider
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.');
112 * Data provider for testGetAliases().
115 * Returns data-set elements with:
116 * - aliases as returned by ContainerBuilder.
117 * - aliases as expected in the container definition.
119 public function getAliasesDataProvider() {
123 ['foo' => 'foo.alias'],
124 ['foo' => 'foo.alias'],
127 ['foo' => 'foo.alias', 'foo.alias' => 'foo.alias.alias'],
128 ['foo' => 'foo.alias.alias', 'foo.alias' => 'foo.alias.alias'],
134 * Tests that parameter processing works properly.
136 * @covers ::getParameters
137 * @covers ::prepareParameters
139 * @covers ::dumpValue
140 * @covers ::getReferenceCall
142 * @dataProvider getParametersDataProvider
144 public function testGetParameters($parameters, $definition_parameters, $is_frozen) {
145 $this->containerDefinition['parameters'] = $definition_parameters;
146 $this->containerDefinition['frozen'] = $is_frozen;
148 $parameter_bag = new ParameterBag($parameters);
149 $this->containerBuilder->getParameterBag()->willReturn($parameter_bag);
150 $this->containerBuilder->isCompiled()->willReturn($is_frozen);
152 if (isset($parameters['reference'])) {
153 $definition = new Definition('\stdClass');
154 $this->containerBuilder->getDefinition('referenced_service')->willReturn($definition);
157 $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
161 * Data provider for testGetParameters().
164 * Returns data-set elements with:
165 * - parameters as returned by ContainerBuilder.
166 * - parameters as expected in the container definition.
169 public function getParametersDataProvider() {
173 ['foo' => 'value_foo'],
174 ['foo' => 'value_foo'],
178 ['foo' => ['llama' => 'yes']],
179 ['foo' => ['llama' => 'yes']],
183 ['foo' => '%llama%', 'llama' => 'yes'],
184 ['foo' => '%%llama%%', 'llama' => 'yes'],
188 ['foo' => '%llama%', 'llama' => 'yes'],
189 ['foo' => '%llama%', 'llama' => 'yes'],
193 ['reference' => new Reference('referenced_service')],
194 ['reference' => $this->getServiceCall('referenced_service')],
201 * Tests that service processing works properly.
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
214 * @dataProvider getDefinitionsDataProvider
218 public function testGetServiceDefinitions($services, $definition_services) {
219 $this->containerDefinition['services'] = $definition_services;
221 $this->containerBuilder->getDefinitions()->willReturn($services);
223 $bar_definition = new Definition('\stdClass');
224 $this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
226 $private_definition = new Definition('\stdClass');
227 $private_definition->setPublic(FALSE);
229 $this->containerBuilder->getDefinition('private_definition')->willReturn($private_definition);
231 $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
235 * Data provider for testGetServiceDefinitions().
238 * Returns data-set elements with:
239 * - parameters as returned by ContainerBuilder.
240 * - parameters as expected in the container definition.
243 public function getDefinitionsDataProvider() {
244 $base_service_definition = [
245 'class' => '\stdClass',
248 'synthetic' => FALSE,
251 'arguments_count' => 0,
256 'configurator' => FALSE,
260 $service_definitions[] = [] + $base_service_definition;
262 $service_definitions[] = [
264 ] + $base_service_definition;
266 $service_definitions[] = [
267 'file' => 'test_include.php',
268 ] + $base_service_definition;
270 $service_definitions[] = [
272 ] + $base_service_definition;
274 $service_definitions[] = [
276 ] + $base_service_definition;
278 $service_definitions[] = [
280 ] + $base_service_definition;
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;
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;
297 // Test a private shared service, denoted by having a Reference.
298 $private_definition = [
299 'class' => '\stdClass',
301 'arguments_count' => 0,
304 $service_definitions[] = [
305 'arguments' => ['foo', new Reference('private_definition')],
306 'arguments_count' => 2,
307 'arguments_expected' => $this->getCollection([
309 $this->getPrivateServiceCall('private_definition', $private_definition, TRUE),
311 ] + $base_service_definition;
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);
317 $service_definitions[] = [
318 'arguments' => ['foo', $private_definition_object],
319 'arguments_count' => 2,
320 'arguments_expected' => $this->getCollection([
322 $this->getPrivateServiceCall(NULL, $private_definition),
324 ] + $base_service_definition;
326 // Test a deep collection without a reference.
327 $service_definitions[] = [
328 'arguments' => [[['foo']]],
329 'arguments_count' => 1,
330 ] + $base_service_definition;
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;
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;
346 // Test objects that have _serviceId property.
347 $drupal_service = new \stdClass();
348 $drupal_service->_serviceId = 'bar';
350 $service_definitions[] = [
351 'arguments' => [$drupal_service],
352 'arguments_count' => 1,
353 'arguments_expected' => $this->getCollection([$this->getServiceCall('bar')]),
354 ] + $base_service_definition;
356 // Test getMethodCalls.
358 ['method', $this->getCollection([])],
359 ['method2', $this->getCollection([])],
361 $service_definitions[] = [
363 ] + $base_service_definition;
365 $service_definitions[] = [
367 ] + $base_service_definition;
370 $service_definitions[] = [
371 'factory' => [new Reference('bar'), 'factoryMethod'],
372 'factory_expected' => [$this->getServiceCall('bar'), 'factoryMethod'],
373 ] + $base_service_definition;
375 // Test invalid factory - needed to test deep dumpValue().
376 $service_definitions[] = [
377 'factory' => [['foo', 'llama'], 'factoryMethod'],
378 ] + $base_service_definition;
381 $service_definitions[] = [
382 'properties' => ['_value' => 'llama'],
383 ] + $base_service_definition;
385 // Test configurator.
386 $service_definitions[] = [
387 'configurator' => [new Reference('bar'), 'configureService'],
388 'configurator_expected' => [$this->getServiceCall('bar'), 'configureService'],
389 ] + $base_service_definition;
391 $services_provided = [];
392 $services_provided[] = [
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']);
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]);
418 if ($key == 'class' || $key == 'arguments_count') {
422 if ($filtered_service_definition[$key] === $base_service_definition[$key]) {
423 unset($filtered_service_definition[$key]);
427 // Add remaining properties.
428 $filtered_service_definition += $service_definition;
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]);
439 if (isset($filtered_service_definition['public']) && $filtered_service_definition['public'] === FALSE) {
440 $services_provided[] = [
441 ['foo_service' => $definition->reveal()],
447 $services_provided[] = [
448 ['foo_service' => $definition->reveal()],
449 ['foo_service' => $this->serializeDefinition($filtered_service_definition)],
453 return $services_provided;
457 * Helper function to serialize a definition.
459 * Used to override serialization.
461 protected function serializeDefinition(array $service_definition) {
462 return serialize($service_definition);
466 * Helper function to return a service definition.
468 protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
472 'invalidBehavior' => $invalid_behavior,
477 * Tests that references to aliases work correctly.
479 * @covers ::getReferenceCall
481 * @dataProvider publicPrivateDataProvider
485 public function testGetServiceDefinitionWithReferenceToAlias($public) {
486 $bar_definition = new Definition('\stdClass');
487 $bar_definition_php_array = [
488 'class' => '\stdClass',
491 $bar_definition->setPublic(FALSE);
492 $bar_definition_php_array['public'] = FALSE;
494 $bar_definition_php_array['arguments_count'] = 0;
496 $services['bar'] = $bar_definition;
498 $aliases['bar.alias'] = 'bar';
500 $foo = new Definition('\stdClass');
501 $foo->addArgument(new Reference('bar.alias'));
503 $services['foo'] = $foo;
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();
510 $service_definition = $this->getServiceCall('bar');
513 $service_definition = $this->getPrivateServiceCall('bar', $bar_definition_php_array, TRUE);
516 'class' => '\stdClass',
517 'arguments' => $this->getCollection([
520 'arguments_count' => 1,
522 $this->assertEquals($this->serializeDefinition($data), $dump['services']['foo'], 'Expected definition matches dump.');
525 public function publicPrivateDataProvider() {
533 * Tests that getDecoratedService() is unsupported.
535 * Tests that the correct InvalidArgumentException is thrown for
536 * getDecoratedService().
538 * @covers ::getServiceDefinition
542 public function testGetServiceDefinitionForDecoratedService() {
543 $bar_definition = new Definition('\stdClass');
544 $bar_definition->setDecoratedService(new Reference('foo'));
545 $services['bar'] = $bar_definition;
547 $this->containerBuilder->getDefinitions()->willReturn($services);
548 if (method_exists($this, 'expectException')) {
549 $this->expectException(InvalidArgumentException::class);
552 $this->setExpectedException(InvalidArgumentException::class);
554 $this->dumper->getArray();
558 * Tests that the correct RuntimeException is thrown for expressions.
560 * @covers ::dumpValue
562 public function testGetServiceDefinitionForExpression() {
563 $expression = new Expression();
565 $bar_definition = new Definition('\stdClass');
566 $bar_definition->addArgument($expression);
567 $services['bar'] = $bar_definition;
569 $this->containerBuilder->getDefinitions()->willReturn($services);
570 if (method_exists($this, 'expectException')) {
571 $this->expectException(RuntimeException::class);
574 $this->setExpectedException(RuntimeException::class);
576 $this->dumper->getArray();
580 * Tests that the correct RuntimeException is thrown for dumping an object.
582 * @covers ::dumpValue
584 public function testGetServiceDefinitionForObject() {
585 $service = new \stdClass();
587 $bar_definition = new Definition('\stdClass');
588 $bar_definition->addArgument($service);
589 $services['bar'] = $bar_definition;
591 $this->containerBuilder->getDefinitions()->willReturn($services);
592 if (method_exists($this, 'expectException')) {
593 $this->expectException(RuntimeException::class);
596 $this->setExpectedException(RuntimeException::class);
598 $this->dumper->getArray();
602 * Tests that the correct RuntimeException is thrown for dumping a resource.
604 * @covers ::dumpValue
606 public function testGetServiceDefinitionForResource() {
607 $resource = fopen('php://memory', 'r');
609 $bar_definition = new Definition('\stdClass');
610 $bar_definition->addArgument($resource);
611 $services['bar'] = $bar_definition;
613 $this->containerBuilder->getDefinitions()->willReturn($services);
614 if (method_exists($this, 'expectException')) {
615 $this->expectException(RuntimeException::class);
618 $this->setExpectedException(RuntimeException::class);
620 $this->dumper->getArray();
624 * Helper function to return a private service definition.
626 protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
628 $hash = Crypt::hashBase64(serialize($service_definition));
629 $id = 'private__' . $hash;
632 'type' => 'private_service',
634 'value' => $service_definition,
640 * Helper function to return a machine-optimized collection.
642 protected function getCollection($collection, $resolve = TRUE) {
644 'type' => 'collection',
645 'value' => $collection,
646 'resolve' => $resolve,
651 * Helper function to return a parameter definition.
653 protected function getParameterCall($name) {
655 'type' => 'parameter',
665 * As Drupal Core does not ship with ExpressionLanguage component we need to
666 * define a dummy, else it cannot be tested.
668 namespace Symfony\Component\ExpressionLanguage {
670 if (!class_exists('\Symfony\Component\ExpressionLanguage\Expression')) {
672 * Dummy class to ensure non-existent Symfony component can be tested.
677 * Gets the string representation of the expression.
679 public function __toString() {
680 return 'dummy_expression';