null
diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
index c90cfa7471..7da5b6b534 100644
--- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md
+++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -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
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeHintsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeHintsPass.php
new file mode 100644
index 0000000000..41085b8e68
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeHintsPass.php
@@ -0,0 +1,184 @@
+
+ *
+ * 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
+ * @author Julien Maulny
+ */
+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;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Exception/InvalidParameterTypeHintException.php b/src/Symfony/Component/DependencyInjection/Exception/InvalidParameterTypeHintException.php
new file mode 100644
index 0000000000..a04d071972
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Exception/InvalidParameterTypeHintException.php
@@ -0,0 +1,28 @@
+
+ *
+ * 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
+ * @author Julien Maulny
+ */
+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));
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeHintsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeHintsPassTest.php
new file mode 100644
index 0000000000..690c628cb6
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeHintsPassTest.php
@@ -0,0 +1,578 @@
+
+ *
+ * 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
+ * @author Julien Maulny
+ */
+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);
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/Bar.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/Bar.php
new file mode 100644
index 0000000000..85a1289815
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/Bar.php
@@ -0,0 +1,13 @@
+foo = $foo;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarMethodCall.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarMethodCall.php
new file mode 100644
index 0000000000..076c219d3a
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarMethodCall.php
@@ -0,0 +1,35 @@
+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)
+ {
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarOptionalArgument.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarOptionalArgument.php
new file mode 100644
index 0000000000..3b6daa77f8
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarOptionalArgument.php
@@ -0,0 +1,13 @@
+foo = $foo;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarOptionalArgumentNotNull.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarOptionalArgumentNotNull.php
new file mode 100644
index 0000000000..5e54114aef
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/BarOptionalArgumentNotNull.php
@@ -0,0 +1,13 @@
+foo = $foo;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/Foo.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/Foo.php
new file mode 100644
index 0000000000..9132d6a246
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeHintsPass/Foo.php
@@ -0,0 +1,16 @@
+