Skip to content
Permalink
Browse files

feature #32256 [DI] Add compiler pass and command to check that servi…

…ces 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 #27825.

It adds a `lint:container` command asserting the type hints used in your code are correct.

Commits
-------

8230a15 Make it really work on real apps
4b3e9d4 Fix comments, improve the feature
a6292b9 [DI] Add compiler pass to check arguments type hint
  • Loading branch information
nicolas-grekas committed Nov 4, 2019
2 parents 08a218c + 8230a15 commit 29fd51f2724e47a38a38cf0d66b474e7ef720577
Showing with 1,030 additions and 2 deletions.
  1. +1 −0 src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
  2. +80 −0 src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php
  3. +4 −0 src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
  4. +1 −1 src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml
  5. +12 −0 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php
  6. +38 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php
  7. +2 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/bundles.php
  8. +2 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php
  9. +1 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/bundles.php
  10. +1 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/bundles.php
  11. +1 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/bundles.php
  12. +1 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php
  13. +1 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/bundles.php
  14. +2 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php
  15. +2 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/bundles.php
  16. +2 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/bundles.php
  17. +1 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/PasswordEncode/bundles.php
  18. +2 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php
  19. +2 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/bundles.php
  20. +2 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/bundles.php
  21. +1 −1 src/Symfony/Bundle/SecurityBundle/composer.json
  22. +1 −0 src/Symfony/Component/DependencyInjection/CHANGELOG.md
  23. +3 −0 src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php
  24. +192 −0 src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
  25. +26 −0 src/Symfony/Component/DependencyInjection/Exception/InvalidParameterTypeException.php
  26. +555 −0 src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
  27. +13 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Bar.php
  28. +39 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
  29. +13 −0 ...ny/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgument.php
  30. +13 −0 ...onent/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgumentNotNull.php
  31. +16 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php
@@ -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 @@
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);
}
}
@@ -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 @@
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 @@
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 @@
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 @@
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,11 +12,13 @@
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 [
new FrameworkBundle(),
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 @@ 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));
}

0 comments on commit 29fd51f

Please sign in to comment.
You can’t perform that action at this time.