diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 093f4bb1da1a..2e4ae15a4e9d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.4.0 ----- + * Added `lint:container` command to check that services wiring matches type declarations * Added `MailerAssertionsTrait` * Deprecated support for `templating` engine in `TemplateController`, use Twig instead * Deprecated the `$parser` argument of `ControllerResolver::__construct()` and `DelegatingLoader::__construct()` diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php new file mode 100644 index 000000000000..c5cba6c9a528 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php @@ -0,0 +1,80 @@ + + * + * 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\Config\ConfigCache; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; + +final class ContainerLintCommand extends Command +{ + protected static $defaultName = 'lint:container'; + + /** + * @var ContainerBuilder + */ + private $containerBuilder; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription('Ensures that arguments injected into services match type declarations') + ->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $container = $this->getContainerBuilder(); + + $container->setParameter('container.build_hash', 'lint_container'); + $container->setParameter('container.build_time', time()); + $container->setParameter('container.build_id', 'lint_container'); + + $container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100); + + $container->compile(); + + return 0; + } + + private function getContainerBuilder(): ContainerBuilder + { + 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([]); + } else { + (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); + } + + return $this->containerBuilder = $container; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index f13aa759d31c..eff7ec6694c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -70,6 +70,10 @@ + + + + null diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index f3f73a9c2a6b..f2d0e9e2a603 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -17,7 +17,7 @@ - + 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 d90041213ce3..ab0b06ffb370 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 @@ 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 @@ public function build(ContainerBuilder $container) $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 000000000000..5197a16195e2 --- /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 bedfbb1bd82a..054405274e83 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 d7b7c498f84a..115dd2c357e8 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 535a4bf517b8..794461855cb8 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 @@ 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 65a38200e759..81f9c48b64ca 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 @@ 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 7928a468da7f..b77f03be2703 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 @@ 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 cd367a95b477..bbb9107456b9 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 @@ 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 bcfd17425cfd..edf6dae14c06 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 9a26fb163a77..a52ae15f6d9b 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 9a26fb163a77..a52ae15f6d9b 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 ccff0d356cab..0e34621a35cc 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 bcfd17425cfd..edf6dae14c06 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 9a26fb163a77..a52ae15f6d9b 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 9a26fb163a77..a52ae15f6d9b 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 95041e7ad465..cef48bfcc4b4 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 @@ 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 77bd4a0cfb76..7ecd3a12c13e 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 c90cfa747128..d1771da84656 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 8453e4e62ada..ad3cb5295cc7 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -133,9 +133,12 @@ protected function getConstructor(Definition $definition, $required) 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 000000000000..9bc57539f412 --- /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 000000000000..206561fa95a8 --- /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 000000000000..51bc7c6779d2 --- /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 000000000000..403841ce88df --- /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 000000000000..c308ef954571 --- /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 000000000000..4f348895132c --- /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 000000000000..07f27817c031 --- /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 000000000000..dde7afce91fd --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php @@ -0,0 +1,16 @@ + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy