psalm/plugin-symfony
Psalm plugin for Symfony: improves static analysis with accurate container service/parameter typing, service subscriber support, console input types, Doctrine repository mapping, Symfony-specific fixes, naming convention checks, DI warnings, and taint analysis.
Start by installing the plugin as a dev dependency and enabling it in your project. Run:
composer require --dev psalm/plugin-symfony
vendor/bin/psalm --init
vendor/bin/psalm-plugin enable psalm/plugin-symfony
The --init command will generate a psalm.xml file, and the enable command will register the plugin and add basic configuration. The first thing to do after installation is to generate and configure a compiled container XML (App_KernelDevDebugContainer.xml for Symfony 5+) — this unlocks accurate type inference for services and parameters:
<pluginClass class="Psalm\SymfonyPsalmPlugin\Plugin">
<containerXml>var/cache/dev/App_KernelDevDebugContainer.xml</containerXml>
</pluginClass>
Your first use case will be catching correct and incorrect service usage — e.g., Psalm will now know @get('logger') returns a LoggerInterface, and warn if you call non-existent services. Run vendor/bin/psalm to see the improvement.
Service & Parameter Types: With containerXml configured, Psalm infers exact types for service IDs (get('app.service.foo')) and parameters (getParameter('kernel.debug')). Use DI-only injection instead of auto-wiring for maximal precision: type-hint services explicitly on constructors.
Console Commands: Psalm now knows InputInterface::getArgument('id') returns the correct type (e.g., int if InputArgument::REQUIRED + typecast or if used with IntegerInputArgument). Enforce best practice by using constants like InputArgument::REQUIRED (not hardcoded strings).
Doctrine Repositories: If your entity uses #[ORM\Entity(repositoryClass: App\Repository\UserRepository::class)] or annotations, entityManager->getRepository(User::class) resolves to UserRepository, enabling IDE and Psalm autocompletion.
Twig Tainting (Security): For apps concerned about XSS, enable taint analysis by configuring either:
twigCachePath (simpler, uses compiled templates), orfileExtensions with the TemplateFileAnalyzer (more robust, uses AST).
This catches unsanitized variables passed to render().Request Handling: Request::getContent() now correctly typed as string|resource depending on context. This avoids PossiblyInvalidArgument false positives when passing to json_decode().
Suppressing Noise: In psalm.xml, suppress common false positives like missing preload files (config/preload.php) using <issueHandlers><MissingFile>...</MissingFile></issueHandlers>.
Container Cache Must Be Warm: Psalm needs a compiled container XML — ensure you run bin/console c:c --warm or at least bin/console cache:warm before running Psalm in CI or locally. The cache file is often excluded from .gitignore, so don’t assume it’s present on first checkout.
Service Naming Matters: The plugin enforces Symfony naming conventions (e.g., snake_case for parameters, PascalCase for services). If you use non-conventional IDs, either rename or temporarily suppress via @psalm-suppress or config.
Explicit Symfony Version: If Psalm is run globally or your Kernel::MAJOR_VERSION is inaccurate (e.g., in polyglot tooling environments), set symfonyMajorVersion explicitly — especially important for pre-release Symfony versions (7, 8).
Env/Param Stubs Required for PHP Config: If you use env() or param() in config/packages/*.php or config/services.php, add the ContainerConfigurator.php stub and ensure var/cache/dev/Symfony/Config is added to extraFiles. Without this, config loading may fail silently.
Twig Analyzer vs Cache Analyzer: Use Twig Analyzer for cleaner analysis (it parses .twig directly), but use Cache Analyzer if you rely heavily on template inheritance or need broader coverage without reconfiguring .twig file extensions.
Dependency Injection Warning: The plugin flags direct use of ContainerInterface injection (especially AbstractController::$container) — use typed service injection instead. When refactoring, use Psalm to surface remaining container->get() calls quickly.
Minor Version Mismatch: If you get ServiceNotFoundException or incorrect types, double-check the containerXml file matches your current environment (dev, test, prod) — mixing App_KernelTestDebugContainer.xml with a prod config causes subtle bugs.
How can I help you explore Laravel packages today?