diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php
index d90041213c..ab0b06ffb3 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php
@@ -14,6 +14,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\DependencyInjection\AnnotationReaderPass;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\DependencyInjection\Config\CustomConfig;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\DependencyInjection\TranslationDebugPass;
+use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
@@ -31,5 +33,15 @@ class TestBundle extends Bundle
$container->addCompilerPass(new AnnotationReaderPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new TranslationDebugPass());
+
+ $container->addCompilerPass(new class() implements CompilerPassInterface {
+ public function process(ContainerBuilder $container)
+ {
+ $container->removeDefinition('twig.controller.exception');
+ $container->removeDefinition('twig.controller.preview_error');
+ }
+ });
+
+ $container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100);
}
}
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php
new file mode 100644
index 0000000000..5197a16195
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle;
+
+use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\PassConfig;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+class TestBundle extends Bundle
+{
+ public function build(ContainerBuilder $container)
+ {
+ $container->setParameter('container.build_hash', 'test_bundle');
+ $container->setParameter('container.build_time', time());
+ $container->setParameter('container.build_id', 'test_bundle');
+
+ $container->addCompilerPass(new class() implements CompilerPassInterface {
+ public function process(ContainerBuilder $container)
+ {
+ $container->removeDefinition('twig.controller.exception');
+ $container->removeDefinition('twig.controller.preview_error');
+ }
+ });
+
+ $container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100);
+ }
+}
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/bundles.php
index bedfbb1bd8..054405274e 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/bundles.php
@@ -12,9 +12,11 @@
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle\SecuredPageBundle;
+use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
new SecuredPageBundle(),
+ new TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php
index d7b7c498f8..115dd2c357 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php
@@ -12,9 +12,11 @@
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\EventBundle\EventBundle;
+use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
new EventBundle(),
+ new TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/bundles.php
index 535a4bf517..794461855c 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/bundles.php
@@ -13,4 +13,5 @@ return [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AutowiringBundle\AutowiringBundle(),
+ new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/bundles.php
index 65a38200e7..81f9c48b64 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/bundles.php
@@ -14,4 +14,5 @@ return [
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\CsrfFormLoginBundle(),
+ new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/bundles.php
index 7928a468da..b77f03be27 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/bundles.php
@@ -13,4 +13,5 @@ return [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FirewallEntryPointBundle\FirewallEntryPointBundle(),
+ new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php
index cd367a95b4..bbb9107456 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php
@@ -13,4 +13,5 @@ return [
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLoginBundle\JsonLoginBundle(),
+ new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/bundles.php
index bcfd17425c..edf6dae14c 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/bundles.php
@@ -12,4 +12,5 @@
return [
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
+ new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php
index 9a26fb163a..a52ae15f6d 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php
@@ -11,8 +11,10 @@
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
+use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
+ new TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/bundles.php
index 9a26fb163a..a52ae15f6d 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/bundles.php
@@ -11,8 +11,10 @@
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
+use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
+ new TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/bundles.php
index ccff0d356c..0e34621a35 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/bundles.php
@@ -12,9 +12,11 @@
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\MissingUserProviderBundle\MissingUserProviderBundle;
+use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
new MissingUserProviderBundle(),
+ new TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/PasswordEncode/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/PasswordEncode/bundles.php
index bcfd17425c..edf6dae14c 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/PasswordEncode/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/PasswordEncode/bundles.php
@@ -12,4 +12,5 @@
return [
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
+ new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php
index 9a26fb163a..a52ae15f6d 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php
@@ -11,8 +11,10 @@
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
+use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
+ new TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php
index 9a26fb163a..a52ae15f6d 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php
@@ -11,8 +11,10 @@
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
+use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
+ new TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/bundles.php
index 95041e7ad4..cef48bfcc4 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/bundles.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/bundles.php
@@ -12,6 +12,7 @@
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\FormLoginBundle;
+use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;
return [
@@ -19,4 +20,5 @@ return [
new SecurityBundle(),
new TwigBundle(),
new FormLoginBundle(),
+ new TestBundle(),
];
diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json
index cd21fafab0..7f3a55477b 100644
--- a/src/Symfony/Bundle/SecurityBundle/composer.json
+++ b/src/Symfony/Bundle/SecurityBundle/composer.json
@@ -19,7 +19,7 @@
"php": "^7.1.3",
"ext-xml": "*",
"symfony/config": "^4.2|^5.0",
- "symfony/dependency-injection": "^4.2|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
"symfony/http-kernel": "^4.4",
"symfony/security-core": "^4.4",
"symfony/security-csrf": "^4.2|^5.0",
diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
index 432a339d71..0b93e05ce0 100644
--- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md
+++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -4,6 +4,7 @@ CHANGELOG
4.4.0
-----
+ * added `CheckTypeDeclarationsPass` 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/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php
index 8453e4e62a..ad3cb5295c 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php
@@ -133,9 +133,12 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
list($class, $method) = $factory;
if ($class instanceof Reference) {
$class = $this->container->findDefinition((string) $class)->getClass();
+ } elseif ($class instanceof Definition) {
+ $class = $class->getClass();
} elseif (null === $class) {
$class = $definition->getClass();
}
+
if ('__construct' === $method) {
throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId));
}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
new file mode 100644
index 0000000000..9bc57539f4
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
@@ -0,0 +1,192 @@
+
+ *
+ * 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\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException;
+use Symfony\Component\DependencyInjection\Parameter;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+
+/**
+ * Checks whether injected parameters are compatible with type declarations.
+ *
+ * This pass should be run after all optimization passes.
+ *
+ * It can be added either:
+ * * before removing passes to check all services even if they are not currently used,
+ * * after removing passes to check only services are used in the app.
+ *
+ * @author Nicolas Grekas
+ * @author Julien Maulny
+ */
+final class CheckTypeDeclarationsPass extends AbstractRecursivePass
+{
+ private const SCALAR_TYPES = ['int', 'float', 'bool', 'string'];
+
+ private $autoload;
+
+ /**
+ * @param bool $autoload Whether services who's class in not loaded should be checked or not.
+ * Defaults to false to save loading code during compilation.
+ */
+ 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($class = $value->getClass(), false) && !interface_exists($class, false)) {
+ return parent::processValue($value, $isRoot);
+ }
+
+ if (ServiceLocator::class === $value->getClass()) {
+ return parent::processValue($value, $isRoot);
+ }
+
+ if ($constructor = $this->getConstructor($value, false)) {
+ $this->checkTypeDeclarations($value, $constructor, $value->getArguments());
+ }
+
+ foreach ($value->getMethodCalls() as $methodCall) {
+ $reflectionMethod = $this->getReflectionMethod($value, $methodCall[0]);
+
+ $this->checkTypeDeclarations($value, $reflectionMethod, $methodCall[1]);
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+
+ /**
+ * @throws InvalidArgumentException When not enough parameters are defined for the method
+ */
+ private function checkTypeDeclarations(Definition $checkedDefinition, \ReflectionFunctionAbstract $reflectionFunction, array $values): void
+ {
+ $numberOfRequiredParameters = $reflectionFunction->getNumberOfRequiredParameters();
+
+ if (\count($values) < $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($values)));
+ }
+
+ $reflectionParameters = $reflectionFunction->getParameters();
+ $checksCount = min($reflectionFunction->getNumberOfParameters(), \count($values));
+
+ for ($i = 0; $i < $checksCount; ++$i) {
+ if (!$reflectionParameters[$i]->hasType() || $reflectionParameters[$i]->isVariadic()) {
+ continue;
+ }
+
+ $this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i]);
+ }
+
+ if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) {
+ $variadicParameters = \array_slice($values, $lastParameter->getPosition());
+
+ foreach ($variadicParameters as $variadicParameter) {
+ $this->checkType($checkedDefinition, $variadicParameter, $lastParameter);
+ }
+ }
+ }
+
+ /**
+ * @throws InvalidParameterTypeException When a parameter is not compatible with the declared type
+ */
+ private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter): void
+ {
+ $type = $parameter->getType()->getName();
+
+ if ($value instanceof Reference) {
+ if (!$this->container->has($value = (string) $value)) {
+ return;
+ }
+
+ if ('service_container' === $value && is_a($type, Container::class, true)) {
+ return;
+ }
+
+ $value = $this->container->findDefinition($value);
+ }
+
+ if ('self' === $type) {
+ $type = $parameter->getDeclaringClass()->getName();
+ }
+
+ if ('static' === $type) {
+ $type = $checkedDefinition->getClass();
+ }
+
+ if ($value instanceof Definition) {
+ $class = $value->getClass();
+
+ if (!$class || (!$this->autoload && !class_exists($class, false) && !interface_exists($class, false))) {
+ return;
+ }
+
+ if ('callable' === $type && method_exists($class, '__invoke')) {
+ return;
+ }
+
+ if ('iterable' === $type && is_subclass_of($class, 'Traversable')) {
+ return;
+ }
+
+ if (is_a($class, $type, true)) {
+ return;
+ }
+
+ throw new InvalidParameterTypeException($this->currentId, $class, $parameter);
+ }
+
+ if ($value instanceof Parameter) {
+ $value = $this->container->getParameter($value);
+ } elseif (\is_string($value) && '%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) {
+ $value = $this->container->getParameter($match[1]);
+ }
+
+ if (null === $value && $parameter->allowsNull()) {
+ return;
+ }
+
+ if (\in_array($type, self::SCALAR_TYPES, true) && is_scalar($value)) {
+ return;
+ }
+
+ if ('callable' === $type && \is_array($value) && isset($value[0]) && ($value[0] instanceof Reference || $value[0] instanceof Definition)) {
+ return;
+ }
+
+ if ('iterable' === $type && (\is_array($value) || $value instanceof \Traversable || $value instanceof IteratorArgument)) {
+ return;
+ }
+
+ if ('Traversable' === $type && ($value instanceof \Traversable || $value instanceof IteratorArgument)) {
+ return;
+ }
+
+ $checkFunction = sprintf('is_%s', $parameter->getType()->getName());
+
+ if (!$parameter->getType()->isBuiltin() || !$checkFunction($value)) {
+ throw new InvalidParameterTypeException($this->currentId, \gettype($value), $parameter);
+ }
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Exception/InvalidParameterTypeException.php b/src/Symfony/Component/DependencyInjection/Exception/InvalidParameterTypeException.php
new file mode 100644
index 0000000000..206561fa95
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Exception/InvalidParameterTypeException.php
@@ -0,0 +1,26 @@
+
+ *
+ * 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 an incompatible type.
+ *
+ * @author Nicolas Grekas
+ * @author Julien Maulny
+ */
+class InvalidParameterTypeException extends InvalidArgumentException
+{
+ public function __construct(string $serviceId, string $type, \ReflectionParameter $parameter)
+ {
+ parent::__construct(sprintf('Invalid definition for service "%s": argument %d of "%s::%s" accepts "%s", "%s" passed.', $serviceId, 1 + $parameter->getPosition(), $parameter->getDeclaringClass()->getName(), $parameter->getDeclaringFunction()->getName(), $parameter->getType()->getName(), $type));
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
new file mode 100644
index 0000000000..51bc7c6779
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
@@ -0,0 +1,555 @@
+
+ *
+ * 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\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Bar;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgument;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgumentNotNull;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo;
+
+/**
+ * @author Nicolas Grekas
+ * @author Julien Maulny
+ */
+class CheckTypeDeclarationsPassTest extends TestCase
+{
+ public function testProcessThrowsExceptionOnInvalidTypesConstructorArguments()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Bar::__construct" accepts "stdClass", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('foo', Foo::class);
+ $container->register('bar', Bar::class)
+ ->addArgument(new Reference('foo'));
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessThrowsExceptionOnInvalidTypesMethodCallArguments()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoo" accepts "stdClass", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('foo', Foo::class);
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setFoo', [new Reference('foo')]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessFailsWhenPassingNullToRequiredArgument()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Bar::__construct" accepts "stdClass", "NULL" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('bar', Bar::class)
+ ->addArgument(null);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessThrowsExceptionWhenMissingArgumentsInConstructor()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Bar::__construct()" requires 1 arguments, 0 passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('bar', Bar::class);
+
+ (new CheckTypeDeclarationsPass(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 CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testProcessRegisterWithClassName()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register(Foo::class, Foo::class);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(Foo::class, $container->get(Foo::class));
+ }
+
+ public function testProcessThrowsExceptionWhenMissingArgumentsInMethodCall()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoo()" requires 1 arguments, 0 passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('foo', \stdClass::class);
+ $container->register('bar', BarMethodCall::class)
+ ->addArgument(new Reference('foo'))
+ ->addMethodCall('setFoo', []);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessVariadicFails()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 2 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoosVariadic" accepts "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo", "stdClass" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('stdClass', \stdClass::class);
+ $container->register('foo', Foo::class);
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setFoosVariadic', [
+ new Reference('foo'),
+ new Reference('foo'),
+ new Reference('stdClass'),
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessVariadicFailsOnPassingBadTypeOnAnotherArgument()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoosVariadic" accepts "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo", "stdClass" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('stdClass', \stdClass::class);
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setFoosVariadic', [
+ new Reference('stdClass'),
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessVariadicSuccess()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('foo', Foo::class);
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setFoosVariadic', [
+ new Reference('foo'),
+ new Reference('foo'),
+ new Reference('foo'),
+ ]);
+
+ (new CheckTypeDeclarationsPass(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', [
+ new Reference('foo'),
+ ]);
+
+ (new CheckTypeDeclarationsPass(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', [
+ new Reference('foo'),
+ new Reference('foo'),
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(Foo::class, $container->get('bar')->foo);
+ }
+
+ public function testProcessFailsWhenUsingOptionalArgumentWithBadType()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 2 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoosOptional" accepts "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo", "stdClass" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('stdClass', \stdClass::class);
+ $container->register('foo', Foo::class);
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setFoosOptional', [
+ new Reference('foo'),
+ new Reference('stdClass'),
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessSuccessWhenPassingNullToOptional()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarOptionalArgument::class)
+ ->addArgument(null);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertNull($container->get('bar')->foo);
+ }
+
+ public function testProcessSuccessWhenPassingNullToOptionalThatDoesNotAcceptNull()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarOptionalArgumentNotNull::__construct" accepts "int", "NULL" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarOptionalArgumentNotNull::class)
+ ->addArgument(null);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessFailsWhenPassingBadTypeToOptional()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarOptionalArgument::__construct" accepts "stdClass", "string" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarOptionalArgument::class)
+ ->addArgument('string instead of stdClass');
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertNull($container->get('bar')->foo);
+ }
+
+ public function testProcessSuccessScalarType()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setScalars', [
+ 1,
+ 'string',
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(BarMethodCall::class, $container->get('bar'));
+ }
+
+ public function testProcessFailsOnPassingScalarTypeToConstructorTypedWithClass()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Bar::__construct" accepts "stdClass", "integer" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('bar', Bar::class)
+ ->addArgument(1);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessFailsOnPassingScalarTypeToMethodTypedWithClass()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoo" accepts "stdClass", "string" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setFoo', [
+ 'builtin type instead of class',
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessFailsOnPassingClassToScalarTypedParameter()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setScalars" accepts "int", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('foo', Foo::class);
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setScalars', [
+ new Reference('foo'),
+ new Reference('foo'),
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessSuccessOnPassingBadScalarType()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setScalars', [
+ 1,
+ true,
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(BarMethodCall::class, $container->get('bar'));
+ }
+
+ public function testProcessSuccessPassingBadScalarTypeOptionalArgument()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setScalars', [
+ 1,
+ 'string',
+ 'string instead of optional boolean',
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(BarMethodCall::class, $container->get('bar'));
+ }
+
+ public function testProcessSuccessWhenPassingArray()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setArray', [[]]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(BarMethodCall::class, $container->get('bar'));
+ }
+
+ public function testProcessSuccessWhenPassingIntegerToArrayTypedParameter()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall::setArray" accepts "array", "integer" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setArray', [1]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessSuccessWhenPassingAnIteratorArgumentToIterable()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarMethodCall::class)
+ ->addMethodCall('setIterable', [new IteratorArgument([])]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testProcessFactory()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('foo', Foo::class);
+ $container->register('bar', Bar::class)
+ ->setFactory([
+ new Reference('foo'),
+ 'createBar',
+ ]);
+
+ /* Asserts that the class of Bar is well detected */
+ $container->register('bar_call', BarMethodCall::class)
+ ->addMethodCall('setBar', [new Reference('bar')]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(Bar::class, $container->get('bar'));
+ }
+
+ public function testProcessFactoryFailsOnInvalidParameterType()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo::createBarArguments" accepts "stdClass", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('foo', Foo::class);
+ $container->register('bar', Bar::class)
+ ->addArgument(new Reference('foo'))
+ ->setFactory([
+ new Reference('foo'),
+ 'createBarArguments',
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+ }
+
+ public function testProcessFactoryFailsOnInvalidParameterTypeOptional()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar": argument 2 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo::createBarArguments" accepts "stdClass", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
+
+ $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([
+ new Reference('foo'),
+ 'createBarArguments',
+ ]);
+
+ (new CheckTypeDeclarationsPass(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([
+ new Reference('foo'),
+ 'createBarArguments',
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testProcessFactoryCallbackSuccessOnValidType()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('bar', \DateTime::class)
+ ->setFactory('date_create');
+
+ (new CheckTypeDeclarationsPass(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', [
+ new Reference('foo'),
+ 'string',
+ 1,
+ ]);
+
+ (new CheckTypeDeclarationsPass())->process($container);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testProcessFactoryDoesNotLoadCodeByDefault()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('foo', FooNotExisting::class);
+ $container->register('bar', BarNotExisting::class)
+ ->setFactory([
+ new Reference('foo'),
+ 'notExistingMethod',
+ ]);
+
+ (new CheckTypeDeclarationsPass())->process($container);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testProcessPassingBuiltinTypeDoesNotLoadCodeByDefault()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('bar', BarNotExisting::class)
+ ->addArgument(1);
+
+ (new CheckTypeDeclarationsPass())->process($container);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testProcessDoesNotThrowsExceptionOnValidTypes()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('foo', \stdClass::class);
+ $container->register('bar', Bar::class)
+ ->addArgument(new Reference('foo'));
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(\stdClass::class, $container->get('bar')->foo);
+ }
+
+ public function testProcessThrowsOnIterableTypeWhenScalarPassed()
+ {
+ $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid definition for service "bar_call": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setIterable" accepts "iterable", "integer" passed.');
+
+ $container = new ContainerBuilder();
+
+ $container->register('bar_call', BarMethodCall::class)
+ ->addMethodCall('setIterable', [2]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->assertInstanceOf(\stdClass::class, $container->get('bar')->foo);
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Bar.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Bar.php
new file mode 100644
index 0000000000..403841ce88
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Bar.php
@@ -0,0 +1,13 @@
+foo = $foo;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
new file mode 100644
index 0000000000..c308ef9545
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
@@ -0,0 +1,39 @@
+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/CheckTypeDeclarationsPass/BarOptionalArgument.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgument.php
new file mode 100644
index 0000000000..4f34889513
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgument.php
@@ -0,0 +1,13 @@
+foo = $foo;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgumentNotNull.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgumentNotNull.php
new file mode 100644
index 0000000000..07f27817c0
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgumentNotNull.php
@@ -0,0 +1,13 @@
+foo = $foo;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php
new file mode 100644
index 0000000000..dde7afce91
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php
@@ -0,0 +1,16 @@
+