[DI] Add compiler pass to check arguments type hint

This commit is contained in:
Julien Maulny 2018-06-29 16:18:59 +02:00 committed by Nicolas Grekas
parent 211c651d2a
commit a6292b917b
11 changed files with 972 additions and 0 deletions

View File

@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CheckTypeHintsPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\Config\FileLocator;
class ContainerLintCommand extends Command
{
/**
* @var ContainerBuilder
*/
private $containerBuilder;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription('Lints container for services arguments type hints')
->setHelp('This command will parse all your defined services and check that you are injecting service without type error based on type hints.')
->addOption('only-used-services', 'o', InputOption::VALUE_NONE, 'Check only services that are used in your application')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$container = $this->getContainerBuilder();
$container->setParameter('container.build_id', 'lint_container');
$container->addCompilerPass(
new CheckTypeHintsPass(),
$input->getOption('only-used-services') ? PassConfig::TYPE_AFTER_REMOVING : PassConfig::TYPE_BEFORE_OPTIMIZATION
);
$container->compile();
}
/**
* Loads the ContainerBuilder from the cache.
*
* @return ContainerBuilder
*
* @throws \LogicException
*/
protected function getContainerBuilder()
{
if ($this->containerBuilder) {
return $this->containerBuilder;
}
$kernel = $this->getApplication()->getKernel();
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
$buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, get_class($kernel));
$container = $buildContainer();
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->compile();
} else {
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
}
return $this->containerBuilder = $container;
}
}

View File

@ -70,6 +70,10 @@
<tag name="console.command" command="debug:container" />
</service>
<service id="console.command.container_lint" class="Symfony\Bundle\FrameworkBundle\Command\ContainerLintCommand">
<tag name="console.command" command="lint:container" />
</service>
<service id="console.command.debug_autowiring" class="Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand">
<argument>null</argument>
<argument type="service" id="debug.file_link_formatter" on-invalid="null"/>

View File

@ -4,6 +4,7 @@ CHANGELOG
4.4.0
-----
* added `CheckTypeHintsPass` to check injected parameters type during compilation
* added support for opcache.preload by generating a preloading script in the cache folder
* added support for dumping the container in one file instead of many files
* deprecated support for short factories and short configurators in Yaml

View File

@ -0,0 +1,184 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeHintException;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
/**
* Checks whether injected parameters types are compatible with type hints.
* This pass should be run after all optimization passes.
* So it can be added either:
* * before removing (PassConfig::TYPE_BEFORE_REMOVING) so that it will check
* all services, even if they are not currently used,
* * after removing (PassConfig::TYPE_AFTER_REMOVING) so that it will check
* only services you are using.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Julien Maulny <jmaulny@darkmira.fr>
*/
class CheckTypeHintsPass extends AbstractRecursivePass
{
/**
* If set to true, allows to autoload classes during compilation
* in order to check type hints on parameters that are not yet loaded.
* Defaults to false to prevent code loading during compilation.
*
* @param bool
*/
private $autoload;
public function __construct(bool $autoload = false)
{
$this->autoload = $autoload;
}
/**
* {@inheritdoc}
*/
protected function processValue($value, $isRoot = false)
{
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
if (!$this->autoload && !class_exists($className = $this->getClassName($value), false) && !interface_exists($className, false)) {
return parent::processValue($value, $isRoot);
}
if (ServiceLocator::class === $value->getClass()) {
return parent::processValue($value, $isRoot);
}
if (null !== $constructor = $this->getConstructor($value, false)) {
$this->checkArgumentsTypeHints($constructor, $value->getArguments());
}
foreach ($value->getMethodCalls() as $methodCall) {
$reflectionMethod = $this->getReflectionMethod($value, $methodCall[0]);
$this->checkArgumentsTypeHints($reflectionMethod, $methodCall[1]);
}
return parent::processValue($value, $isRoot);
}
/**
* Check type hints for every parameter of a method/constructor.
*
* @throws InvalidArgumentException on type hint incompatibility
*/
private function checkArgumentsTypeHints(\ReflectionFunctionAbstract $reflectionFunction, array $configurationArguments): void
{
$numberOfRequiredParameters = $reflectionFunction->getNumberOfRequiredParameters();
if (count($configurationArguments) < $numberOfRequiredParameters) {
throw new InvalidArgumentException(sprintf(
'Invalid definition for service "%s": "%s::%s()" requires %d arguments, %d passed.', $this->currentId, $reflectionFunction->class, $reflectionFunction->name, $numberOfRequiredParameters, count($configurationArguments)));
}
$reflectionParameters = $reflectionFunction->getParameters();
$checksCount = min($reflectionFunction->getNumberOfParameters(), count($configurationArguments));
for ($i = 0; $i < $checksCount; ++$i) {
if (!$reflectionParameters[$i]->hasType() || $reflectionParameters[$i]->isVariadic()) {
continue;
}
$this->checkTypeHint($configurationArguments[$i], $reflectionParameters[$i]);
}
if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) {
$variadicParameters = array_slice($configurationArguments, $lastParameter->getPosition());
foreach ($variadicParameters as $variadicParameter) {
$this->checkTypeHint($variadicParameter, $lastParameter);
}
}
}
/**
* Check type hints compatibility between
* a definition argument and a reflection parameter.
*
* @throws InvalidArgumentException on type hint incompatibility
*/
private function checkTypeHint($configurationArgument, \ReflectionParameter $parameter): void
{
$referencedDefinition = $configurationArgument;
if ($referencedDefinition instanceof Reference) {
$referencedDefinition = $this->container->findDefinition((string) $referencedDefinition);
}
if ($referencedDefinition instanceof Definition) {
$class = $this->getClassName($referencedDefinition);
if (!$this->autoload && !class_exists($class, false)) {
return;
}
if (!is_a($class, $parameter->getType()->getName(), true)) {
throw new InvalidParameterTypeHintException($this->currentId, null === $class ? 'null' : $class, $parameter);
}
} else {
if (null === $configurationArgument && $parameter->allowsNull()) {
return;
}
if ($parameter->getType()->isBuiltin() && is_scalar($configurationArgument)) {
return;
}
if ('iterable' === $parameter->getType()->getName() && $configurationArgument instanceof IteratorArgument) {
return;
}
if ('Traversable' === $parameter->getType()->getName() && $configurationArgument instanceof IteratorArgument) {
return;
}
$checkFunction = 'is_'.$parameter->getType()->getName();
if (!$parameter->getType()->isBuiltin() || !$checkFunction($configurationArgument)) {
throw new InvalidParameterTypeHintException($this->currentId, gettype($configurationArgument), $parameter);
}
}
}
/**
* Get class name from value that can have a factory.
*
* @return string|null
*/
private function getClassName($value)
{
if (is_array($factory = $value->getFactory())) {
list($class, $method) = $factory;
if ($class instanceof Reference) {
$class = $this->container->findDefinition((string) $class)->getClass();
} elseif (null === $class) {
$class = $value->getClass();
} elseif ($class instanceof Definition) {
$class = $this->getClassName($class);
}
} else {
$class = $value->getClass();
}
return $class;
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Thrown when trying to inject a parameter into a constructor/method
* with a type that does not match type hint.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Julien Maulny <jmaulny@darkmira.fr>
*/
class InvalidParameterTypeHintException extends InvalidArgumentException
{
public function __construct(string $serviceId, string $typeHint, \ReflectionParameter $parameter)
{
parent::__construct(sprintf(
'Invalid definition for service "%s": argument %d of "%s::%s" requires a "%s", "%s" passed.', $serviceId, $parameter->getPosition(), $parameter->getDeclaringClass()->getName(), $parameter->getDeclaringFunction()->getName(), $parameter->getType()->getName(), $typeHint));
}
}

View File

@ -0,0 +1,578 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CheckTypeHintsPass;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Bar;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarOptionalArgument;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarOptionalArgumentNotNull;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarMethodCall;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo;
/**
* @author Nicolas Grekas <p@tchwork.com>
* @author Julien Maulny <jmaulny@darkmira.fr>
*/
class CheckTypeHintsPassTest extends TestCase
{
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Bar::__construct" requires a "stdClass", "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo" passed
*/
public function testProcessThrowsExceptionOnInvalidTypeHintsConstructorArguments()
{
$container = new ContainerBuilder();
$container->register('foo', Foo::class);
$container->register('bar', Bar::class)
->addArgument(new Reference('foo'));
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarMethodCall::setFoo" requires a "stdClass", "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo" passed
*/
public function testProcessThrowsExceptionOnInvalidTypeHintsMethodCallArguments()
{
$container = new ContainerBuilder();
$container->register('foo', Foo::class);
$container->register('bar', BarMethodCall::class)
->addMethodCall('setFoo', array(new Reference('foo')));
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Bar::__construct" requires a "stdClass", "NULL" passed
*/
public function testProcessFailsWhenPassingNullToRequiredArgument()
{
$container = new ContainerBuilder();
$container->register('bar', Bar::class)
->addArgument(null);
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Bar::__construct()" requires 1 arguments, 0 passed
*/
public function testProcessThrowsExceptionWhenMissingArgumentsInConstructor()
{
$container = new ContainerBuilder();
$container->register('bar', Bar::class);
(new CheckTypeHintsPass(true))->process($container);
}
public function testProcessSuccessWhenPassingTooManyArgumentInConstructor()
{
$container = new ContainerBuilder();
$container->register('foo', \stdClass::class);
$container->register('bar', Bar::class)
->addArgument(new Reference('foo'))
->addArgument(new Reference('foo'));
(new CheckTypeHintsPass(true))->process($container);
$this->addToAssertionCount(1);
}
public function testProcessRegisterWithClassName()
{
$container = new ContainerBuilder();
$container->register(Foo::class, Foo::class);
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(Foo::class, $container->get(Foo::class));
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarMethodCall::setFoo()" requires 1 arguments, 0 passed
*/
public function testProcessThrowsExceptionWhenMissingArgumentsInMethodCall()
{
$container = new ContainerBuilder();
$container->register('foo', \stdClass::class);
$container->register('bar', BarMethodCall::class)
->addArgument(new Reference('foo'))
->addMethodCall('setFoo', array());
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarMethodCall::setFoosVariadic" requires a "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo", "stdClass" passed
*/
public function testProcessVariadicFails()
{
$container = new ContainerBuilder();
$container->register('stdClass', \stdClass::class);
$container->register('foo', Foo::class);
$container->register('bar', BarMethodCall::class)
->addMethodCall('setFoosVariadic', array(
new Reference('foo'),
new Reference('foo'),
new Reference('stdClass'),
));
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarMethodCall::setFoosVariadic" requires a "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo", "stdClass" passed
*/
public function testProcessVariadicFailsOnPassingBadTypeOnAnotherArgument()
{
$container = new ContainerBuilder();
$container->register('stdClass', \stdClass::class);
$container->register('bar', BarMethodCall::class)
->addMethodCall('setFoosVariadic', array(
new Reference('stdClass'),
));
(new CheckTypeHintsPass(true))->process($container);
}
public function testProcessVariadicSuccess()
{
$container = new ContainerBuilder();
$container->register('foo', Foo::class);
$container->register('bar', BarMethodCall::class)
->addMethodCall('setFoosVariadic', array(
new Reference('foo'),
new Reference('foo'),
new Reference('foo'),
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(Foo::class, $container->get('bar')->foo);
}
public function testProcessSuccessWhenNotUsingOptionalArgument()
{
$container = new ContainerBuilder();
$container->register('foo', Foo::class);
$container->register('bar', BarMethodCall::class)
->addMethodCall('setFoosOptional', array(
new Reference('foo'),
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(Foo::class, $container->get('bar')->foo);
}
public function testProcessSuccessWhenUsingOptionalArgumentWithGoodType()
{
$container = new ContainerBuilder();
$container->register('foo', Foo::class);
$container->register('bar', BarMethodCall::class)
->addMethodCall('setFoosOptional', array(
new Reference('foo'),
new Reference('foo'),
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(Foo::class, $container->get('bar')->foo);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarMethodCall::setFoosOptional" requires a "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo", "stdClass" passed
*/
public function testProcessFailsWhenUsingOptionalArgumentWithBadType()
{
$container = new ContainerBuilder();
$container->register('stdClass', \stdClass::class);
$container->register('foo', Foo::class);
$container->register('bar', BarMethodCall::class)
->addMethodCall('setFoosOptional', array(
new Reference('foo'),
new Reference('stdClass'),
));
(new CheckTypeHintsPass(true))->process($container);
}
public function testProcessSuccessWhenPassingNullToOptional()
{
$container = new ContainerBuilder();
$container->register('bar', BarOptionalArgument::class)
->addArgument(null);
(new CheckTypeHintsPass(true))->process($container);
$this->assertNull($container->get('bar')->foo);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarOptionalArgumentNotNull::__construct" requires a "int", "NULL" passed
*/
public function testProcessSuccessWhenPassingNullToOptionalThatDoesNotAcceptNull()
{
$container = new ContainerBuilder();
$container->register('bar', BarOptionalArgumentNotNull::class)
->addArgument(null);
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarOptionalArgument::__construct" requires a "stdClass", "string" passed
*/
public function testProcessFailsWhenPassingBadTypeToOptional()
{
$container = new ContainerBuilder();
$container->register('bar', BarOptionalArgument::class)
->addArgument('string instead of stdClass');
(new CheckTypeHintsPass(true))->process($container);
$this->assertNull($container->get('bar')->foo);
}
public function testProcessSuccessScalarType()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setScalars', array(
1,
'string',
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(BarMethodCall::class, $container->get('bar'));
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Bar::__construct" requires a "stdClass", "integer" passed
*/
public function testProcessFailsOnPassingScalarTypeToConstructorTypeHintedWithClass()
{
$container = new ContainerBuilder();
$container->register('bar', Bar::class)
->addArgument(1);
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarMethodCall::setFoo" requires a "stdClass", "string" passed
*/
public function testProcessFailsOnPassingScalarTypeToMethodTypeHintedWithClass()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setFoo', array(
'builtin type instead of class',
));
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\BarMethodCall::setScalars" requires a "int", "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo" passed
*/
public function testProcessFailsOnPassingClassToScalarTypeHintedParameter()
{
$container = new ContainerBuilder();
$container->register('foo', Foo::class);
$container->register('bar', BarMethodCall::class)
->addMethodCall('setScalars', array(
new Reference('foo'),
new Reference('foo'),
));
(new CheckTypeHintsPass(true))->process($container);
}
/**
* Strict mode not yet handled.
*/
public function testProcessSuccessOnPassingBadScalarType()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setScalars', array(
1,
true,
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(BarMethodCall::class, $container->get('bar'));
}
/**
* Strict mode not yet handled.
*/
public function testProcessSuccessPassingBadScalarTypeOptionalArgument()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setScalars', array(
1,
'string',
'string instead of optional boolean',
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(BarMethodCall::class, $container->get('bar'));
}
public function testProcessSuccessWhenPassingArray()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setArray', array(
array(),
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(BarMethodCall::class, $container->get('bar'));
}
public function testProcessSuccessWhenPassingIntegerToArrayTypeHintedParameter()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setArray', array(
1,
));
(new CheckTypeHintsPass(true))->process($container);
$this->addToAssertionCount(1);
}
public function testProcessSuccessWhenPassingAnIteratorArgumentToIterable()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setIterable', array(
new IteratorArgument(array()),
));
(new CheckTypeHintsPass(true))->process($container);
$this->addToAssertionCount(1);
}
public function testProcessFactory()
{
$container = new ContainerBuilder();
$container->register('foo', Foo::class);
$container->register('bar', Bar::class)
->setFactory(array(
new Reference('foo'),
'createBar',
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(Bar::class, $container->get('bar'));
}
public function testProcessFactoryWhithClassName()
{
$container = new ContainerBuilder();
$container->register(Foo::class, Foo::class);
$container->register(Bar::class, Bar::class)
->setFactory(array(
new Reference(Foo::class),
'createBar',
));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(Bar::class, $container->get(Bar::class));
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 0 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo::createBarArguments" requires a "stdClass", "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo" passed
*/
public function testProcessFactoryFailsOnInvalidParameterType()
{
$container = new ContainerBuilder();
$container->register('foo', Foo::class);
$container->register('bar', Bar::class)
->addArgument(new Reference('foo'))
->setFactory(array(
new Reference('foo'),
'createBarArguments',
));
(new CheckTypeHintsPass(true))->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid definition for service "bar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo::createBarArguments" requires a "stdClass", "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass\Foo" passed
*/
public function testProcessFactoryFailsOnInvalidParameterTypeOptional()
{
$container = new ContainerBuilder();
$container->register('stdClass', \stdClass::class);
$container->register('foo', Foo::class);
$container->register('bar', Bar::class)
->addArgument(new Reference('stdClass'))
->addArgument(new Reference('foo'))
->setFactory(array(
new Reference('foo'),
'createBarArguments',
));
(new CheckTypeHintsPass(true))->process($container);
}
public function testProcessFactorySuccessOnValidTypes()
{
$container = new ContainerBuilder();
$container->register('stdClass', \stdClass::class);
$container->register('foo', Foo::class);
$container->register('bar', Bar::class)
->addArgument(new Reference('stdClass'))
->addArgument(new Reference('stdClass'))
->setFactory(array(
new Reference('foo'),
'createBarArguments',
));
(new CheckTypeHintsPass(true))->process($container);
$this->addToAssertionCount(1);
}
public function testProcessFactoryCallbackSuccessOnValidType()
{
$container = new ContainerBuilder();
$container->register('bar', \DateTime::class)
->setFactory('date_create');
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(\DateTime::class, $container->get('bar'));
}
public function testProcessDoesNotLoadCodeByDefault()
{
$container = new ContainerBuilder();
$container->register('foo', FooNotExisting::class);
$container->register('bar', BarNotExisting::class)
->addArgument(new Reference('foo'))
->addMethodCall('setFoo', array(
new Reference('foo'),
'string',
1,
));
(new CheckTypeHintsPass())->process($container);
$this->addToAssertionCount(1);
}
public function testProcessFactoryDoesNotLoadCodeByDefault()
{
$container = new ContainerBuilder();
$container->register('foo', FooNotExisting::class);
$container->register('bar', BarNotExisting::class)
->setFactory(array(
new Reference('foo'),
'notExistingMethod',
));
(new CheckTypeHintsPass())->process($container);
$this->addToAssertionCount(1);
}
public function testProcessPassingBuiltinTypeDoesNotLoadCodeByDefault()
{
$container = new ContainerBuilder();
$container->register('bar', BarNotExisting::class)
->addArgument(1);
(new CheckTypeHintsPass())->process($container);
$this->addToAssertionCount(1);
}
public function testProcessDoesNotThrowsExceptionOnValidTypeHints()
{
$container = new ContainerBuilder();
$container->register('foo', \stdClass::class);
$container->register('bar', Bar::class)
->addArgument(new Reference('foo'));
(new CheckTypeHintsPass(true))->process($container);
$this->assertInstanceOf(\stdClass::class, $container->get('bar')->foo);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass;
class Bar
{
public $foo;
public function __construct(\stdClass $foo)
{
$this->foo = $foo;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass;
class BarMethodCall
{
public $foo;
public function setFoo(\stdClass $foo)
{
$this->foo = $foo;
}
public function setFoosVariadic(Foo $foo, Foo ...$foos)
{
$this->foo = $foo;
}
public function setFoosOptional(Foo $foo, Foo $fooOptional = null)
{
$this->foo = $foo;
}
public function setScalars(int $int, string $string, bool $bool = false)
{
}
public function setArray(array $array)
{
}
public function setIterable(iterable $iterable)
{
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass;
class BarOptionalArgument
{
public $foo;
public function __construct(\stdClass $foo = null)
{
$this->foo = $foo;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass;
class BarOptionalArgumentNotNull
{
public $foo;
public function __construct(int $foo = 1)
{
$this->foo = $foo;
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeHintsPass;
class Foo
{
public static function createBar()
{
return new Bar(new \stdClass());
}
public static function createBarArguments(\stdClass $stdClass, \stdClass $stdClassOptional = null)
{
return new Bar($stdClass);
}
}