Static analysis
Gacela ships configs for PHPStan and Psalm that suppress false positives from dynamic resolution via #[ServiceMap] attributes and the magic getFacade() / getFactory() / getConfig() dispatch.
PHPStan
Include in your phpstan.neon:
includes:
- vendor/gacela-project/gacela/phpstan-gacela.neonBeyond the suppressions, phpstan-gacela.neon also enables architectural rules:
- Naming conventions — a
Facade/Factory/Provider/Configclass must extend the matching Gacela abstract (SuffixExtendsRule). FacadeOnlyDelegatesRule— a Facade only delegates to its Factory instead of holding business logic.FactoryDoesNotCallFacadeRule— a Factory never calls back into a Facade.CrossModuleViaFacadeRule— opt-in (commented out in the shipped config): enforces that modules communicate only through Facades. See Enforcing module boundaries.
Enforcing module boundaries
CrossModuleViaFacadeRule ships commented out in phpstan-gacela.neon. Uncomment it and pass your namespaces to enable it:
services:
-
class: Gacela\PHPStan\Rules\CrossModuleViaFacadeRule
tags: [phpstan.rules.rule]
arguments:
rootNamespace: App\Modules
modulePathSegments: 1
sharedNamespaces:
- App\Modules\SharedrootNamespace(string, required) — your project's module root, e.g.App\Modules.modulePathSegments(int, default1) — how many namespace segments beneath the root identify a single module.sharedNamespaces(list of strings, default[], since 1.18.0) — shared kernels exempt from the boundary: references into them are always allowed, and classes inside them aren't checked.
Accurate module return types across getFactory(), getConfig() and getProvidedDependency() come from the @template annotations on Gacela's abstract classes plus the @extends on your concrete module classes — independent of this config.
Psalm
<?xml version="1.0"?>
<psalm
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns="https://getpsalm.org/schema/config"
>
<projectFiles>
<directory name="src"/>
</projectFiles>
<xi:include href="vendor/gacela-project/gacela/psalm-gacela.xml"/>
<issueHandlers>
<InvalidArgument>
<errorLevel type="suppress">
<directory name="src" />
</errorLevel>
</InvalidArgument>
</issueHandlers>
</psalm>The InvalidArgument suppression is required because Gacela resolves concrete types at runtime that Psalm can't infer statically. Suppress inline if you prefer narrower scope:
/** @psalm-suppress InvalidArgument */
return new YourService($this->getConfig());Troubleshooting
- PHPStan can't find the file: verify the include path resolves relative to your
phpstan.neon. - Psalm ignores the include: ensure
xmlns:xi="http://www.w3.org/2001/XInclude"is declared, thenvendor/bin/psalm --clear-cache.