class OptimizedPhpArrayDumperTest

Same name and namespace in other branches
  1. 11.x core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest
  2. 10 core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest
  3. 8.9.x core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest

@coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
@group DependencyInjection

Hierarchy

  • class \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest uses \Drupal\Tests\PhpUnitCompatibilityTrait, \Symfony\Bridge\PhpUnit\ExpectDeprecationTrait extends \PHPUnit\Framework\TestCase

Expanded class hierarchy of OptimizedPhpArrayDumperTest

File

core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php, line 27

Namespace

Drupal\Tests\Component\DependencyInjection\Dumper
View source
class OptimizedPhpArrayDumperTest extends TestCase {
  use PhpUnitCompatibilityTrait;
  use ExpectDeprecationTrait;
  
  /**
   * The container builder instance.
   *
   * @var \Symfony\Component\DependencyInjection\ContainerBuilder
   */
  protected $containerBuilder;
  
  /**
   * The definition for the container to build in tests.
   *
   * @var array
   */
  protected $containerDefinition;
  
  /**
   * Whether the dumper uses the machine-optimized format or not.
   *
   * @var bool
   */
  protected $machineFormat = TRUE;
  
  /**
   * Stores the dumper class to use.
   *
   * @var string
   */
  protected $dumperClass = '\\Drupal\\Component\\DependencyInjection\\Dumper\\OptimizedPhpArrayDumper';
  
  /**
   * The dumper instance.
   *
   * @var \Symfony\Component\DependencyInjection\Dumper\DumperInterface
   */
  protected $dumper;
  
  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    // Setup a mock container builder.
    $this->containerBuilder = $this->prophesize('\\Symfony\\Component\\DependencyInjection\\ContainerBuilder');
    $this->containerBuilder
      ->getAliases()
      ->willReturn([]);
    $this->containerBuilder
      ->getParameterBag()
      ->willReturn(new ParameterBag());
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn(NULL);
    $this->containerBuilder
      ->isCompiled()
      ->willReturn(TRUE);
    $definition = [];
    $definition['aliases'] = [];
    $definition['parameters'] = [];
    $definition['services'] = [];
    $definition['frozen'] = TRUE;
    $definition['machine_format'] = $this->machineFormat;
    $this->containerDefinition = $definition;
    // Create the dumper.
    $this->dumper = new $this->dumperClass($this->containerBuilder
      ->reveal());
  }
  
  /**
   * Tests that an empty container works properly.
   *
   * @covers ::dump
   * @covers ::getArray
   * @covers ::supportsMachineFormat
   */
  public function testDumpForEmptyContainer() {
    $serialized_definition = $this->dumper
      ->dump();
    $this->assertEquals(serialize($this->containerDefinition), $serialized_definition);
  }
  
  /**
   * Tests that alias processing works properly.
   *
   * @covers ::getAliases
   *
   * @dataProvider getAliasesDataProvider
   */
  public function testGetAliases($aliases, $definition_aliases) {
    $this->containerDefinition['aliases'] = $definition_aliases;
    $this->containerBuilder
      ->getAliases()
      ->willReturn($aliases);
    $this->assertEquals($this->containerDefinition, $this->dumper
      ->getArray(), 'Expected definition matches dump.');
  }
  
  /**
   * Data provider for testGetAliases().
   *
   * @return array[]
   *   Returns data-set elements with:
   *     - aliases as returned by ContainerBuilder.
   *     - aliases as expected in the container definition.
   */
  public function getAliasesDataProvider() {
    return [
      [
        [],
        [],
      ],
      [
        [
          'foo' => 'foo.alias',
        ],
        [
          'foo' => 'foo.alias',
        ],
      ],
      [
        [
          'foo' => 'foo.alias',
          'foo.alias' => 'foo.alias.alias',
        ],
        [
          'foo' => 'foo.alias.alias',
          'foo.alias' => 'foo.alias.alias',
        ],
      ],
    ];
  }
  
  /**
   * Tests that parameter processing works properly.
   *
   * @covers ::getParameters
   * @covers ::prepareParameters
   * @covers ::escape
   * @covers ::dumpValue
   * @covers ::getReferenceCall
   *
   * @dataProvider getParametersDataProvider
   */
  public function testGetParameters($parameters, $definition_parameters, $is_frozen) {
    $this->containerDefinition['parameters'] = $definition_parameters;
    $this->containerDefinition['frozen'] = $is_frozen;
    $parameter_bag = new ParameterBag($parameters);
    $this->containerBuilder
      ->getParameterBag()
      ->willReturn($parameter_bag);
    $this->containerBuilder
      ->isCompiled()
      ->willReturn($is_frozen);
    if (isset($parameters['reference'])) {
      $definition = new Definition('\\stdClass');
      $definition->setPublic(TRUE);
      $this->containerBuilder
        ->getDefinition('referenced_service')
        ->willReturn($definition);
    }
    $this->assertEquals($this->containerDefinition, $this->dumper
      ->getArray(), 'Expected definition matches dump.');
  }
  
  /**
   * Data provider for testGetParameters().
   *
   * @return array[]
   *   Returns data-set elements with:
   *     - parameters as returned by ContainerBuilder.
   *     - parameters as expected in the container definition.
   *     - frozen value
   */
  public function getParametersDataProvider() {
    return [
      [
        [],
        [],
        TRUE,
      ],
      [
        [
          'foo' => 'value_foo',
        ],
        [
          'foo' => 'value_foo',
        ],
        TRUE,
      ],
      [
        [
          'foo' => [
            'llama' => 'yes',
          ],
        ],
        [
          'foo' => [
            'llama' => 'yes',
          ],
        ],
        TRUE,
      ],
      [
        [
          'foo' => '%llama%',
          'llama' => 'yes',
        ],
        [
          'foo' => '%%llama%%',
          'llama' => 'yes',
        ],
        TRUE,
      ],
      [
        [
          'foo' => '%llama%',
          'llama' => 'yes',
        ],
        [
          'foo' => '%llama%',
          'llama' => 'yes',
        ],
        FALSE,
      ],
      [
        [
          'reference' => new Reference('referenced_service'),
        ],
        [
          'reference' => $this->getServiceCall('referenced_service'),
        ],
        TRUE,
      ],
    ];
  }
  
  /**
   * Tests that service processing works properly.
   *
   * @covers ::getServiceDefinitions
   * @covers ::getServiceDefinition
   * @covers ::dumpMethodCalls
   * @covers ::dumpCollection
   * @covers ::dumpCallable
   * @covers ::dumpValue
   * @covers ::getPrivateServiceCall
   * @covers ::getReferenceCall
   * @covers ::getServiceCall
   * @covers ::getParameterCall
   *
   * @dataProvider getDefinitionsDataProvider
   */
  public function testGetServiceDefinitions($services, $definition_services) {
    $this->containerDefinition['services'] = $definition_services;
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn($services);
    $bar_definition = new Definition('\\stdClass');
    $bar_definition->setPublic(TRUE);
    $this->containerBuilder
      ->getDefinition('bar')
      ->willReturn($bar_definition);
    $private_definition = new Definition('\\stdClass');
    $private_definition->setPublic(FALSE);
    $this->containerBuilder
      ->getDefinition('private_definition')
      ->willReturn($private_definition);
    $this->assertEquals($this->containerDefinition, $this->dumper
      ->getArray(), 'Expected definition matches dump.');
  }
  
  /**
   * Data provider for testGetServiceDefinitions().
   *
   * @return array[]
   *   Returns data-set elements with:
   *     - parameters as returned by ContainerBuilder.
   *     - parameters as expected in the container definition.
   *     - frozen value
   */
  public function getDefinitionsDataProvider() {
    $base_service_definition = [
      'class' => '\\stdClass',
      'public' => TRUE,
      'file' => FALSE,
      'synthetic' => FALSE,
      'lazy' => FALSE,
      'arguments' => [],
      'arguments_count' => 0,
      'properties' => [],
      'calls' => [],
      'shared' => TRUE,
      'factory' => FALSE,
      'configurator' => FALSE,
    ];
    // Test basic flags.
    $service_definitions[] = [] + $base_service_definition;
    $service_definitions[] = [
      'public' => FALSE,
    ] + $base_service_definition;
    $service_definitions[] = [
      'file' => 'test_include.php',
    ] + $base_service_definition;
    $service_definitions[] = [
      'synthetic' => TRUE,
    ] + $base_service_definition;
    $service_definitions[] = [
      'shared' => FALSE,
    ] + $base_service_definition;
    $service_definitions[] = [
      'lazy' => TRUE,
    ] + $base_service_definition;
    // Test a basic public Reference.
    $service_definitions[] = [
      'arguments' => [
        'foo',
        new Reference('bar'),
      ],
      'arguments_count' => 2,
      'arguments_expected' => $this->getCollection([
        'foo',
        $this->getServiceCall('bar'),
      ]),
    ] + $base_service_definition;
    // Test a public reference that should not throw an Exception.
    $reference = new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE);
    $service_definitions[] = [
      'arguments' => [
        $reference,
      ],
      'arguments_count' => 1,
      'arguments_expected' => $this->getCollection([
        $this->getServiceCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE),
      ]),
    ] + $base_service_definition;
    // Test a private shared service, denoted by having a Reference.
    $private_definition = [
      'class' => '\\stdClass',
      'public' => FALSE,
      'arguments_count' => 0,
    ];
    $service_definitions[] = [
      'arguments' => [
        'foo',
        new Reference('private_definition'),
      ],
      'arguments_count' => 2,
      'arguments_expected' => $this->getCollection([
        'foo',
        $this->getPrivateServiceCall('private_definition', $private_definition, TRUE),
      ]),
    ] + $base_service_definition;
    // Test a private non-shared service, denoted by having a Definition.
    $private_definition_object = new Definition('\\stdClass');
    $private_definition_object->setPublic(FALSE);
    $service_definitions[] = [
      'arguments' => [
        'foo',
        $private_definition_object,
      ],
      'arguments_count' => 2,
      'arguments_expected' => $this->getCollection([
        'foo',
        $this->getPrivateServiceCall(NULL, $private_definition),
      ]),
    ] + $base_service_definition;
    // Test a deep collection without a reference.
    $service_definitions[] = [
      'arguments' => [
        [
          [
            'foo',
          ],
        ],
      ],
      'arguments_count' => 1,
    ] + $base_service_definition;
    // Test a deep collection with a reference to resolve.
    $service_definitions[] = [
      'arguments' => [
        [
          new Reference('bar'),
        ],
      ],
      'arguments_count' => 1,
      'arguments_expected' => $this->getCollection([
        $this->getCollection([
          $this->getServiceCall('bar'),
        ]),
      ]),
    ] + $base_service_definition;
    // Test a collection with a variable to resolve.
    $service_definitions[] = [
      'arguments' => [
        new Parameter('llama_parameter'),
      ],
      'arguments_count' => 1,
      'arguments_expected' => $this->getCollection([
        $this->getParameterCall('llama_parameter'),
      ]),
    ] + $base_service_definition;
    // Test getMethodCalls.
    $calls = [
      [
        'method',
        $this->getCollection([]),
      ],
      [
        'method2',
        $this->getCollection([]),
      ],
    ];
    $service_definitions[] = [
      'calls' => $calls,
    ] + $base_service_definition;
    $service_definitions[] = [
      'shared' => FALSE,
    ] + $base_service_definition;
    // Test factory.
    $service_definitions[] = [
      'factory' => [
        new Reference('bar'),
        'factoryMethod',
      ],
      'factory_expected' => [
        $this->getServiceCall('bar'),
        'factoryMethod',
      ],
    ] + $base_service_definition;
    // Test invalid factory - needed to test deep dumpValue().
    $service_definitions[] = [
      'factory' => [
        [
          'foo',
          'llama',
        ],
        'factoryMethod',
      ],
    ] + $base_service_definition;
    // Test properties.
    $service_definitions[] = [
      'properties' => [
        '_value' => 'llama',
      ],
    ] + $base_service_definition;
    // Test configurator.
    $service_definitions[] = [
      'configurator' => [
        new Reference('bar'),
        'configureService',
      ],
      'configurator_expected' => [
        $this->getServiceCall('bar'),
        'configureService',
      ],
    ] + $base_service_definition;
    $services_provided = [];
    $services_provided[] = [
      [],
      [],
    ];
    foreach ($service_definitions as $service_definition) {
      $definition = $this->prophesize('\\Symfony\\Component\\DependencyInjection\\Definition');
      $definition->getClass()
        ->willReturn($service_definition['class']);
      $definition->isPublic()
        ->willReturn($service_definition['public']);
      $definition->getFile()
        ->willReturn($service_definition['file']);
      $definition->isSynthetic()
        ->willReturn($service_definition['synthetic']);
      $definition->isLazy()
        ->willReturn($service_definition['lazy']);
      $definition->getArguments()
        ->willReturn($service_definition['arguments']);
      $definition->getProperties()
        ->willReturn($service_definition['properties']);
      $definition->getMethodCalls()
        ->willReturn($service_definition['calls']);
      $definition->isShared()
        ->willReturn($service_definition['shared']);
      $definition->getDecoratedService()
        ->willReturn(NULL);
      $definition->getFactory()
        ->willReturn($service_definition['factory']);
      $definition->getConfigurator()
        ->willReturn($service_definition['configurator']);
      // Preserve order.
      $filtered_service_definition = [];
      foreach ($base_service_definition as $key => $value) {
        $filtered_service_definition[$key] = $service_definition[$key];
        unset($service_definition[$key]);
        if ($key == 'class' || $key == 'arguments_count') {
          continue;
        }
        if ($filtered_service_definition[$key] === $base_service_definition[$key]) {
          unset($filtered_service_definition[$key]);
        }
      }
      // Add remaining properties.
      $filtered_service_definition += $service_definition;
      // Allow to set _expected values.
      foreach ([
        'arguments',
        'factory',
        'configurator',
      ] as $key) {
        $expected = $key . '_expected';
        if (isset($filtered_service_definition[$expected])) {
          $filtered_service_definition[$key] = $filtered_service_definition[$expected];
          unset($filtered_service_definition[$expected]);
        }
      }
      if (isset($filtered_service_definition['public']) && $filtered_service_definition['public'] === FALSE) {
        $services_provided[] = [
          [
            'foo_service' => $definition->reveal(),
          ],
          [],
        ];
        continue;
      }
      $services_provided[] = [
        [
          'foo_service' => $definition->reveal(),
        ],
        [
          'foo_service' => $this->serializeDefinition($filtered_service_definition),
        ],
      ];
    }
    return $services_provided;
  }
  
  /**
   * Helper function to serialize a definition.
   *
   * Used to override serialization.
   */
  protected function serializeDefinition(array $service_definition) {
    return serialize($service_definition);
  }
  
  /**
   * Helper function to return a service definition.
   */
  protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
    return (object) [
      'type' => 'service',
      'id' => $id,
      'invalidBehavior' => $invalid_behavior,
    ];
  }
  
  /**
   * Tests that references to aliases work correctly.
   *
   * @covers ::getReferenceCall
   *
   * @dataProvider publicPrivateDataProvider
   */
  public function testGetServiceDefinitionWithReferenceToAlias($public) {
    $bar_definition = new Definition('\\stdClass');
    $bar_definition_php_array = [
      'class' => '\\stdClass',
    ];
    if ($public) {
      $bar_definition->setPublic(TRUE);
    }
    else {
      $bar_definition->setPublic(FALSE);
      $bar_definition_php_array['public'] = FALSE;
    }
    $bar_definition_php_array['arguments_count'] = 0;
    $services['bar'] = $bar_definition;
    $aliases['bar.alias'] = 'bar';
    $foo = new Definition('\\stdClass');
    $foo->setPublic(TRUE);
    $foo->addArgument(new Reference('bar.alias'));
    $services['foo'] = $foo;
    $this->containerBuilder
      ->getAliases()
      ->willReturn($aliases);
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn($services);
    $this->containerBuilder
      ->getDefinition('bar')
      ->willReturn($bar_definition);
    $dump = $this->dumper
      ->getArray();
    if ($public) {
      $service_definition = $this->getServiceCall('bar');
    }
    else {
      $service_definition = $this->getPrivateServiceCall('bar', $bar_definition_php_array, TRUE);
    }
    $data = [
      'class' => '\\stdClass',
      'arguments' => $this->getCollection([
        $service_definition,
      ]),
      'arguments_count' => 1,
    ];
    $this->assertEquals($this->serializeDefinition($data), $dump['services']['foo'], 'Expected definition matches dump.');
  }
  public function publicPrivateDataProvider() {
    return [
      [
        TRUE,
      ],
      [
        FALSE,
      ],
    ];
  }
  
  /**
   * Tests that getDecoratedService() is unsupported.
   *
   * Tests that the correct InvalidArgumentException is thrown for
   * getDecoratedService().
   *
   * @covers ::getServiceDefinition
   */
  public function testGetServiceDefinitionForDecoratedService() {
    $bar_definition = new Definition('\\stdClass');
    $bar_definition->setPublic(TRUE);
    $bar_definition->setDecoratedService(new Reference('foo'));
    $services['bar'] = $bar_definition;
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn($services);
    $this->expectException(InvalidArgumentException::class);
    $this->dumper
      ->getArray();
  }
  
  /**
   * Tests that the correct RuntimeException is thrown for expressions.
   *
   * @covers ::dumpValue
   */
  public function testGetServiceDefinitionForExpression() {
    $expression = new Expression('');
    $bar_definition = new Definition('\\stdClass');
    $bar_definition->setPublic(TRUE);
    $bar_definition->addArgument($expression);
    $services['bar'] = $bar_definition;
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn($services);
    $this->expectException(RuntimeException::class);
    $this->dumper
      ->getArray();
  }
  
  /**
   * Tests that the correct RuntimeException is thrown for dumping an object.
   *
   * @covers ::dumpValue
   */
  public function testGetServiceDefinitionForObject() {
    $service = new \stdClass();
    $bar_definition = new Definition('\\stdClass');
    $bar_definition->setPublic(TRUE);
    $bar_definition->addArgument($service);
    $services['bar'] = $bar_definition;
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn($services);
    $this->expectException(RuntimeException::class);
    $this->dumper
      ->getArray();
  }
  
  /**
   * Tests that the correct RuntimeException is thrown for dumping an object.
   *
   * @covers ::dumpValue
   * @group legacy
   */
  public function testGetServiceDefinitionForObjectServiceId() {
    $service = new \stdClass();
    $service->_serviceId = 'foo';
    $services['foo'] = new Definition('\\stdClass');
    $services['bar'] = new Definition('\\stdClass');
    $services['bar']->addArgument($service);
    foreach ($services as $s) {
      $s->setPublic(TRUE);
    }
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn($services);
    $this->containerBuilder
      ->getDefinition('foo')
      ->willReturn($services['foo']);
    $this->containerBuilder
      ->getDefinition('bar')
      ->willReturn($services['bar']);
    $this->expectDeprecation('_serviceId is deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. Use \\Drupal\\Core\\DrupalKernelInterface::getServiceIdMapping() instead. See https://www.drupal.org/node/3292540');
    $a = $this->dumper
      ->getArray();
    $this->assertEquals($this->serializeDefinition([
      'class' => '\\stdClass',
      // Legacy code takes care of converting _serviceId into this.
'arguments' => $this->getCollection([
        $this->getServiceCall('foo'),
      ]),
      'arguments_count' => 1,
    ]), $a['services']['bar']);
  }
  
  /**
   * Tests that the correct RuntimeException is thrown for dumping a resource.
   *
   * @covers ::dumpValue
   */
  public function testGetServiceDefinitionForResource() {
    $resource = fopen('php://memory', 'r');
    $bar_definition = new Definition('\\stdClass');
    $bar_definition->setPublic(TRUE);
    $bar_definition->addArgument($resource);
    $services['bar'] = $bar_definition;
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn($services);
    $this->expectException(RuntimeException::class);
    $this->dumper
      ->getArray();
  }
  
  /**
   * Tests that service arguments with escaped percents are correctly dumped.
   *
   * @dataProvider percentsEscapeProvider
   */
  public function testPercentsEscape($expected, $argument) {
    $definition = new Definition('\\stdClass', [
      $argument,
    ]);
    $definition->setPublic(TRUE);
    $this->containerBuilder
      ->getDefinitions()
      ->willReturn([
      'test' => $definition,
    ]);
    $dump = $this->dumper
      ->getArray();
    $this->assertEquals($this->serializeDefinition([
      'class' => '\\stdClass',
      'arguments' => $this->getCollection([
        $this->getRaw($expected),
      ]),
      'arguments_count' => 1,
    ]), $dump['services']['test']);
  }
  
  /**
   * Data provider for testPercentsEscape().
   *
   * @return array[]
   *   Returns data-set elements with:
   *     - expected final value.
   *     - escaped value in service definition.
   */
  public function percentsEscapeProvider() {
    return [
      [
        '%foo%',
        '%%foo%%',
      ],
      [
        'foo%bar%',
        'foo%%bar%%',
      ],
      [
        '%foo%bar',
        '%%foo%%bar',
      ],
      [
        '%',
        '%',
      ],
      [
        '%',
        '%%',
      ],
      [
        '%%',
        '%%%',
      ],
      [
        '%%',
        '%%%%',
      ],
    ];
  }
  
  /**
   * Helper function to return a private service definition.
   */
  protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
    if (!$id) {
      $hash = Crypt::hashBase64(serialize($service_definition));
      $id = 'private__' . $hash;
    }
    return (object) [
      'type' => 'private_service',
      'id' => $id,
      'value' => $service_definition,
      'shared' => $shared,
    ];
  }
  
  /**
   * Helper function to return a machine-optimized collection.
   */
  protected function getCollection($collection, $resolve = TRUE) {
    return (object) [
      'type' => 'collection',
      'value' => $collection,
      'resolve' => $resolve,
    ];
  }
  
  /**
   * Helper function to return a parameter definition.
   */
  protected function getParameterCall($name) {
    return (object) [
      'type' => 'parameter',
      'name' => $name,
    ];
  }
  
  /**
   * Helper function to return a raw value definition.
   */
  protected function getRaw($value) {
    return (object) [
      'type' => 'raw',
      'value' => $value,
    ];
  }

}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.