feature #32256 [DI] Add compiler pass and command to check that services wiring matches type declarations (alcalyn, GuilhemN, nicolas-grekas)
This PR was merged into the 4.4 branch. Discussion ---------- [DI] Add compiler pass and command to check that services wiring matches type declarations | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #27744 | License | MIT | Doc PR | PR replacing https://github.com/symfony/symfony/pull/27825. It adds a `lint:container` command asserting the type hints used in your code are correct. Commits -------8230a1543e
Make it really work on real apps4b3e9d4c96
Fix comments, improve the featurea6292b917b
[DI] Add compiler pass to check arguments type hint
This commit is contained in:
commit
29fd51f272
|
@ -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()`
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Command;
|
||||
|
||||
use Symfony\Component\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;
|
||||
}
|
||||
}
|
|
@ -70,6 +70,10 @@
|
|||
<tag name="console.command" command="debug:container" />
|
||||
</service>
|
||||
|
||||
<service id="console.command.container_lint" class="Symfony\Bundle\FrameworkBundle\Command\ContainerLintCommand">
|
||||
<tag name="console.command" command="lint:container" />
|
||||
</service>
|
||||
|
||||
<service id="console.command.debug_autowiring" class="Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand">
|
||||
<argument>null</argument>
|
||||
<argument type="service" id="debug.file_link_formatter" on-invalid="null"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</service>
|
||||
<service id="Symfony\Component\Validator\Validator\ValidatorInterface" alias="validator" />
|
||||
|
||||
<service id="validator.builder" class="Symfony\Component\Validator\ValidatorBuilderInterface">
|
||||
<service id="validator.builder" class="Symfony\Component\Validator\ValidatorBuilder">
|
||||
<factory class="Symfony\Component\Validator\Validation" method="createValidatorBuilder" />
|
||||
<call method="setConstraintValidatorFactory">
|
||||
<argument type="service" id="validator.validator_factory" />
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\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);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -12,4 +12,5 @@
|
|||
return [
|
||||
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
|
||||
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
|
||||
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -12,4 +12,5 @@
|
|||
return [
|
||||
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
|
||||
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
|
||||
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\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 <p@tchwork.com>
|
||||
* @author Julien Maulny <jmaulny@darkmira.fr>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Exception;
|
||||
|
||||
/**
|
||||
* Thrown when trying to inject a parameter into a constructor/method with an incompatible type.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author Julien Maulny <jmaulny@darkmira.fr>
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,555 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\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 <p@tchwork.com>
|
||||
* @author Julien Maulny <jmaulny@darkmira.fr>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
|
||||
|
||||
class Bar
|
||||
{
|
||||
public $foo;
|
||||
|
||||
public function __construct(\stdClass $foo)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
|
||||
|
||||
class BarMethodCall
|
||||
{
|
||||
public $foo;
|
||||
|
||||
public function setBar(Bar $bar)
|
||||
{
|
||||
}
|
||||
|
||||
public function setFoo(\stdClass $foo)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
|
||||
public function setFoosVariadic(Foo $foo, Foo ...$foos)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
|
||||
public function setFoosOptional(Foo $foo, Foo $fooOptional = null)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
|
||||
public function setScalars(int $int, string $string, bool $bool = false)
|
||||
{
|
||||
}
|
||||
|
||||
public function setArray(array $array)
|
||||
{
|
||||
}
|
||||
|
||||
public function setIterable(iterable $iterable)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
|
||||
|
||||
class BarOptionalArgument
|
||||
{
|
||||
public $foo;
|
||||
|
||||
public function __construct(\stdClass $foo = null)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
|
||||
|
||||
class BarOptionalArgumentNotNull
|
||||
{
|
||||
public $foo;
|
||||
|
||||
public function __construct(int $foo = 1)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
|
||||
|
||||
class Foo
|
||||
{
|
||||
public static function createBar()
|
||||
{
|
||||
return new Bar(new \stdClass());
|
||||
}
|
||||
|
||||
public static function createBarArguments(\stdClass $stdClass, \stdClass $stdClassOptional = null)
|
||||
{
|
||||
return new Bar($stdClass);
|
||||
}
|
||||
}
|
Reference in New Issue