OptimizedPhpArrayDumperTest.php
Same filename in other branches
Namespace
Drupal\Tests\Component\DependencyInjection\DumperFile
-
core/
tests/ Drupal/ Tests/ Component/ DependencyInjection/ Dumper/ OptimizedPhpArrayDumperTest.php
View source
<?php
declare (strict_types=1);
namespace Drupal\Tests\Component\DependencyInjection\Dumper;
use Drupal\Component\Utility\Crypt;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophet;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
* @group DependencyInjection
*/
class OptimizedPhpArrayDumperTest extends TestCase {
use ExpectDeprecationTrait;
use ProphecyTrait;
/**
* 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([]);
$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() : void {
$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) : void {
$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 static 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) : void {
$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);
$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 static 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,
],
];
}
/**
* 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 ::getServiceClosureCall
* @covers ::getParameterCall
*
* @dataProvider getDefinitionsDataProvider
*/
public function testGetServiceDefinitions($services, $definition_services) : void {
$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 static 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' => static::getCollection([
'foo',
static::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' => static::getCollection([
static::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' => static::getCollection([
'foo',
static::getPrivateServiceCall('private_definition', $private_definition, TRUE),
]),
] + $base_service_definition;
// Test a service closure.
$service_definitions[] = [
'arguments' => [
'foo',
[
'alias-1' => new ServiceClosureArgument(new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
'alias-2' => new ServiceClosureArgument(new Reference('bar', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)),
],
],
'arguments_count' => 2,
'arguments_expected' => static::getCollection([
'foo',
static::getCollection([
'alias-1' => static::getServiceClosureCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE),
'alias-2' => static::getServiceClosureCall('bar', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE),
]),
]),
] + $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' => static::getCollection([
'foo',
static::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' => static::getCollection([
static::getCollection([
static::getServiceCall('bar'),
]),
]),
] + $base_service_definition;
// Test an IteratorArgument collection with a reference to resolve.
$service_definitions[] = [
'arguments' => [
new IteratorArgument([
new Reference('bar'),
]),
],
'arguments_count' => 1,
'arguments_expected' => static::getCollection([
static::getIterator([
static::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' => static::getCollection([
static::getParameterCall('llama_parameter'),
]),
] + $base_service_definition;
// Test getMethodCalls.
$calls = [
[
'method',
static::getCollection([]),
],
[
'method2',
static::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' => [
static::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' => [
static::getServiceCall('bar'),
'configureService',
],
] + $base_service_definition;
$services_provided = [];
$services_provided[] = [
[],
[],
];
foreach ($service_definitions as $service_definition) {
$definition = (new Prophet())->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' => static::serializeDefinition($filtered_service_definition),
],
];
}
return $services_provided;
}
/**
* Helper function to serialize a definition.
*
* Used to override serialization.
*/
protected static function serializeDefinition(array $service_definition) {
return serialize($service_definition);
}
/**
* Helper function to return a service definition.
*/
protected static function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
return (object) [
'type' => 'service',
'id' => $id,
'invalidBehavior' => $invalid_behavior,
];
}
/**
* Helper function to return a service closure definition.
*/
protected static function getServiceClosureCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
return (object) [
'type' => 'service_closure',
'id' => $id,
'invalidBehavior' => $invalid_behavior,
];
}
/**
* Tests that references to aliases work correctly.
*
* @covers ::getReferenceCall
*
* @dataProvider publicPrivateDataProvider
*/
public function testGetServiceDefinitionWithReferenceToAlias($public) : void {
$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 = static::getServiceCall('bar');
}
else {
$service_definition = static::getPrivateServiceCall('bar', $bar_definition_php_array, TRUE);
}
$data = [
'class' => '\\stdClass',
'arguments' => static::getCollection([
$service_definition,
]),
'arguments_count' => 1,
];
$this->assertEquals(static::serializeDefinition($data), $dump['services']['foo'], 'Expected definition matches dump.');
}
public static function publicPrivateDataProvider() {
return [
[
TRUE,
],
[
FALSE,
],
];
}
/**
* Tests that getDecoratedService() is unsupported.
*
* Tests that the correct InvalidArgumentException is thrown for
* getDecoratedService().
*
* @covers ::getServiceDefinition
*/
public function testGetServiceDefinitionForDecoratedService() : void {
$bar_definition = new Definition('\\stdClass');
$bar_definition->setPublic(TRUE);
$bar_definition->setDecoratedService((string) 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() : void {
$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() : void {
$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() : void {
$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(static::serializeDefinition([
'class' => '\\stdClass',
// Legacy code takes care of converting _serviceId into this.
'arguments' => static::getCollection([
static::getServiceCall('foo'),
]),
'arguments_count' => 1,
]), $a['services']['bar']);
}
/**
* Tests that the correct RuntimeException is thrown for dumping a resource.
*
* @covers ::dumpValue
*/
public function testGetServiceDefinitionForResource() : void {
$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) : void {
$definition = new Definition('\\stdClass', [
$argument,
]);
$definition->setPublic(TRUE);
$this->containerBuilder
->getDefinitions()
->willReturn([
'test' => $definition,
]);
$dump = $this->dumper
->getArray();
$this->assertEquals(static::serializeDefinition([
'class' => '\\stdClass',
'arguments' => static::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 static function percentsEscapeProvider() {
return [
[
'%foo%',
'%%foo%%',
],
[
'foo%bar%',
'foo%%bar%%',
],
[
'%foo%bar',
'%%foo%%bar',
],
[
'%',
'%',
],
[
'%',
'%%',
],
[
'%%',
'%%%',
],
[
'%%',
'%%%%',
],
];
}
/**
* Helper function to return a private service definition.
*/
protected static 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 static function getCollection($collection) {
return (object) [
'type' => 'collection',
'value' => $collection,
];
}
/**
* Helper function to return a machine-optimized iterator.
*/
protected static function getIterator($collection) {
return (object) [
'type' => 'iterator',
'value' => $collection,
];
}
/**
* Helper function to return a parameter definition.
*/
protected static 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,
];
}
}
/**
* Defines a dummy ExpressionLanguage component.
*
* As Drupal Core does not ship with ExpressionLanguage component we need to
* define a dummy, else it cannot be tested.
*/
namespace Symfony\Component\ExpressionLanguage;
if (!class_exists('\\Symfony\\Component\\ExpressionLanguage\\Expression')) {
/**
* Dummy class to ensure non-existent Symfony component can be tested.
*/
class Expression {
public function __construct($expression) {
}
/**
* Gets the string representation of the expression.
*/
public function __toString() {
return 'dummy_expression';
}
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
OptimizedPhpArrayDumperTest | @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper @group DependencyInjection |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.