php-stubs/generator
Generate PHP stub files from your codebase for better IDE autocomplete, static analysis, and documentation. php-stubs/generator scans sources and produces lightweight stubs suitable for packages and frameworks, helping consumers get accurate type hints without shipping full sources.
Architecture fit. php-stubs/generator is a dev-time code generation tool, not a runtime library. It parses PHP source with nikic/php-parser and emits declaration-only stubs (functions, classes, traits, interfaces, constants, documented/undocumented globals). That places it firmly in the build/developer-tooling lane of a Laravel codebase — it should never appear in require or on a request path. The most defensible fit is as a normalization layer for code we don't control or can't type: legacy modules, vendored third-party packages, internal libraries published without PHPDoc, and WordPress-style mixed declarations-and-side-effects files. Its distinguishing technical property is that it works purely statically — no autoloading, no instantiation, no execution — so it can stub code that a reflection-based tool (e.g., laravel-ide-helper model/facade generation) cannot load safely. That is the single strongest argument for adoption, and the one a TPM should lead with.
Integration feasibility. Straightforward. Composer install as --dev, PHP 7.4+ (aligned with a Laravel 9+ runtime), PHP-Parser v5 support since 0.8.5, and a two-line programmatic API (StubsGenerator + Finder + Result::prettyPrint()) alongside the generate-stubs binary. Integration work is not in wiring — it is in distribution and gating: deciding whether stubs are committed artifacts or CI-generated, and registering them with PHPStan (stubFiles / scanFiles) or Psalm so they actually influence analysis. Expect the real effort to be in configuring the analyzers around stub-inherited false positives, not in the generator itself.
Technical risk. Ranked:
| Risk | Severity | Notes |
|---|---|---|
| False confidence in type safety | High | Stubs only restate what docblocks already say. Untyped input produces untyped stubs; they do not add runtime or analysis-time guarantees the source lacks. Must be positioned as DX/debt-visibility, not correctness. |
| Stale-stub drift | High | Stubs are a parallel artifact of the same truth. Without a regeneration trigger they silently diverge and static analysis starts validating against fiction. |
| Dynamic Laravel constructs are out of scope | High | Facades, __call/__get, Eloquent attribute magic, container-resolved contracts, and macros produce no declarations to extract. Do not promise coverage here; that gap belongs to laravel-ide-helper or explicit @method/@property annotations. |
| Single-file static resolution limits | Medium | Without reflection, inherited members, real constant values, and cross-file type relationships are resolved heuristically. Changelog history (0.2 FQCN fixes, 0.3 declaration ordering, 0.8.6 duplicate constants/globals) shows this is an active correctness surface. |
| Low adoption / bus factor | Medium | 83 stars, no listed dependents, a small contributor set, and a pre-1.0 version line. Treat as a utility you can fork or replace, never a load-bearing dependency. |
| Upstream PHP syntax churn | Medium | PHP 8.4/8.5 support has required explicit patches (nullable type hints, test workflows). Parser coupling means new language syntax can break generation until upstream releases. |
| Security | Low | Parser-only; untrusted source is never executed. Minimal supply-chain surface for a dev dependency. |
Key questions before committing.
laravel-ide-helper? If so, scope this tool strictly to non-Laravel/vendor code to avoid two competing stub sources for the same symbols.Stack fit. Clean for Laravel + Composer + PSR-4. It is a CLI/dev dependency with no framework coupling and no ServiceProvider — deliberately framework-agnostic, which is appropriate: it should sit in require-dev and a Composer script, not in the application's service container. Consumers are the IDE index, PHPStan/Psalm, and optionally a PHPUnit assertion over generated output.
Migration path. There is no migration — it is additive and removable. Nothing at runtime references the stubs. The practical path is: add as dev dependency → point Finder::create()->in(...) (or the CLI with --out) at one legacy package → commit or artifact the output → register it with one analyzer → measure. Rollback is deleting a file and a config entry. This is a genuinely low-regret adoption decision; the cost is ongoing maintenance, not reversibility.
Compatibility. Confirm PHP-Parser v5 alignment with any other tooling in the graph (PHPStan bundles its own parser and does not conflict). Verify generated stubs against the Laravel version in use — a stub file generated from a package version we no longer ship is worse than no stub. Flag the pre-1.0 version line: pin to a minor (^0.8) and treat minor bumps as review-worthy, given the changelog pattern of behavior-affecting fixes.
Sequencing.
stubFiles; triage false positives into ignoreErrors with justifications, not blanket silencing.composer stubs) plus a CI job that regenerates and fails on diff, making drift a build failure rather than a discovery.Explicitly out of sequence: applying this to Laravel framework internals or generated code. Those are already covered by bundled IDE stubs; generating our own creates competing definitions.
Maintenance. The ongoing cost is drift management, not tool maintenance. Budget for: a regeneration trigger tied to dependency bumps, a committed stub file (or CI diff check), and an owner for the ignore-list that will inevitably accrue. The generator's own release cadence is modest but responsive; assume you may need to fork or pin if PHP syntax support lags a framework upgrade — that is the fork-and-forget contingency to note in the ADR.
Support. No commercial support; community issues only, small maintainer pool. Internal support burden is low for CLI usage and moderate once stubs feed a static analyzer, because "PHPStan is now complaining about a stub" becomes an in-team ticket. Assign a single owner (platform/DX engineer) rather than distributing it across teams.
Scaling. Generation is linear in parsed bytes and memory-bound on very large trees. For a monorepo, generate per-package rather than monolithically — smaller outputs, clearer ownership, incremental CI. Static-analysis time will grow with stub volume, so watch PHPStan wall-clock as a secondary metric; if it regresses, narrow stubFiles to the packages that demonstrably benefit.
Failure modes to instrument.
laravel-ide-helper + PHPStan's own) for one symbol. Mitigation: strict, documented source-of-truth rule per symbol class.Ramp-up. Low for engineers (two commands), moderate for the team's process (stub location, regeneration trigger, analyzer config ownership). Realistically half a day to first working output, one to two sprints to a trustworthy automated pipeline. The trap is treating ramp-up as the hard part: the tool is easy, the policy around generated artifacts is the actual deliverable.
TPM recommendation. Adopt as a scoped dev-tooling pilot on untyped/vendored code, gated on a measured IDE-or-analyzer delta and paired with a CI drift check. Do not adopt as a general type-safety initiative, and do not position it as covering Laravel's dynamic surfaces. Publish the adoption criteria and the regeneration trigger before the first commit, or this quietly becomes unowned generated code — which is exactly the debt it was meant to reduce.
How can I help you explore Laravel packages today?