PHPStan extension for Respect/Fluent builders.
Provides method resolution, parameter validation, tuple-typed getNodes(), and
type narrowing through assertion methods.
Fluent builders use __call to resolve method names to class instances. Since
those methods don't exist as real declarations, PHPStan reports errors and can't
validate arguments. This extension teaches PHPStan what each method does: its
parameters, return type, and the exact tuple of accumulated nodes.
$stack = Middleware::cors('*')->rateLimit(100);
// PHPStan knows:
// cors() accepts (string $origin = '*')
// rateLimit() accepts (int $maxRequests = 60)
// getNodes() returns array{Cors, RateLimit}
// $stack is Middleware<array{Cors, RateLimit}>
$stack->getNodes()[0]; // PHPStan infers: Cors
$stack->typo(); // PHPStan error: method not found
$stack->cors(42); // PHPStan error: int given, string expectedcomposer require --dev respect/fluent-analysisRequires PHP 8.5+ and PHPStan 2.1+.
Libraries that ship a Fluent builder declare it in their fluent.neon:
parameters:
fluent:
builders:
- builder: App\MiddlewareStackThe extension loads automatically via
phpstan/extension-installer.
Method maps are built from #[FluentNamespace] attributes at PHPStan boot.
To add extra node namespaces to an existing builder (e.g. custom validators):
parameters:
fluent:
builders:
- builder: Respect\Validation\ValidatorBuilder
namespace: App\ValidatorsEntries from multiple neon files are merged automatically. Each package, extension, or user project can append entries independently.
For projects that define their own #[FluentNamespace] builders:
vendor/bin/fluent-analysis generateThis scans your composer.json autoload entries for builder classes and writes
a fluent.neon with the builder list and service registrations.
Every method on your builder is resolved to its target class. PHPStan reports unknown methods as errors: typos are caught at analysis time.
Method parameters come from the target class constructor. If Cors has
__construct(string $origin = '*'), then ->cors() accepts the same
signature. Type mismatches are reported.
The extension tracks which node types are accumulated through the chain.
getNodes() returns a precise tuple instead of array<int, object>:
$builder = new MiddlewareStack();
$chain = $builder->cors('*')->rateLimit(100)->auth('bearer');
// PHPStan knows: array{Cors, RateLimit, Auth}
$nodes = $chain->getNodes();
// Individual elements are typed
$nodes[0]; // Cors
$nodes[1]; // RateLimit
$nodes[2]; // AuthTuple tracking works through variable assignments and static calls:
$a = MiddlewareStack::cors('*');
$b = $a->rateLimit(100);
$b->getNodes(); // array{Cors, RateLimit}If a target class is marked @deprecated, the fluent method inherits the
deprecation. PHPStan reports it wherever the method is called.
For builders using Respect/Fluent's composable prefixes (like Validation's
notEmail(), nullOrStringType()), the extension resolves composed methods
with correct parameter signatures.
Builders can narrow the type of a variable through assertion methods. Node
classes declare their assurance via the #[Assurance] attribute, assertion
methods are marked with #[AssuranceAssertion], and #[AssuranceParameter]
identifies the validated parameter and constructor parameters used for type
resolution.
Void assertion methods narrow unconditionally:
$builder->intNode()->doAssert($x);
// PHPStan now knows $x is intBool assertion methods work as type guards:
if ($builder->intNode()->isOk($x)) {
// $x is int here
}
// $x is not int hereChained nodes intersect their assurances:
$builder->intNode()->numericNode()->doAssert($x);
// int ∩ (int|float|numeric-string) = intThe extension supports several assurance modes through the #[Assurance]
attribute:
type— a fixed type string (e.g.int,float|int|numeric-string)#[AssuranceParameter]— the type is taken from a constructor parameter annotated with the attribute (e.g. a class-string parameter)from: value— narrows to the argument's literal typefrom: member— narrows to the iterable value type of the argumentfrom: elements— narrows to an array of the inner assurance typecompose: union|intersect— combines assurances from multiple builder arguments
The extension registers three PHPStan hooks:
-
FluentMethodsExtension(MethodsClassReflectionExtension) — tells PHPStan which methods exist on each builder, with parameters extracted from the target class constructor. -
FluentDynamicReturnTypeExtension(DynamicMethodReturnTypeExtension+DynamicStaticMethodReturnTypeExtension) — intercepts each method call to track accumulated node types as aGenericObjectTypewrapping aConstantArrayTypetuple. WhengetNodes()is called, the tuple is returned directly. Also accumulates assurance types through the chain. -
FluentTypeSpecifyingExtension(MethodTypeSpecifyingExtension) — enables type narrowing in control flow. When a builder's assertion method is called, accumulated assurances are applied to narrow the input variable's type. Supports void assertions (unconditional) and bool guards (conditional).
The extensions share a MethodMap for method resolution and an AssuranceMap
for type narrowing configuration, both with parent-class fallback for builder
inheritance.
At PHPStan boot, MethodMapFactory reads the builders parameter, reflects
each builder's #[FluentNamespace] attribute, discovers classes in the
declared namespaces, and builds the method/assurance maps. Extra namespaces
from user config are merged via withNamespace().
Another similar project is FluentGen.
Both are complementary, offering IDE support and type inference as separate packages.
| FluentAnalysis | FluentGen | |
|---|---|---|
| Generated files | None | Interface files per builder + prefix |
| Return type | Builder<array{A, B, C}> |
Builder (via @mixin) |
getNodes() type |
array{A, B, C} (exact tuple) |
array<int, Node> (generic) |
| Element access | $nodes[0] typed as A |
mixed |
| Deprecation | Forwarded automatically | Must regenerate |
| Composable prefixes | Resolved from attribute | Full method signatures |
| Type narrowing | Assertion methods narrow input types | Not supported |
| IDE support | PHPStan-powered (PhpStorm, VS Code) | Direct IDE autocomplete |