Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Generator Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it globally, not in the app.

composer global require php-stubs/generator
generate-stubs --help

Global install matters for Laravel work: the package depends on nikic/php-parser and several Symfony components, and your application (or larastan, or a package that pins php-parser v4) will fight over those constraints. Keep the generator out of composer.json unless you actually need the PHP API.

First use case: a legacy or vendor package your IDE can't see. A typical Laravel trigger is an old internal SDK, a WordPress-style plugin, or a helper file that declares functions and classes and executes side effects in the same file. Composer autoloads it fine at runtime, but PhpStorm/Intelephense and PHPStan see nothing useful.

generate-stubs vendor/acme/legacy-sdk/src --out=stubs/acme-legacy.stub.php

Open the generated file before wiring it up. You're checking three things: (1) are the symbols you care about actually there, (2) do they have types/docblocks, and (3) is anything in there that would explode if executed (global variable assignments).

Where to look first

  • generate-stubs --help — the real option list beats any blog post. Pay attention to --out, --nullify-globals, --constants, --visitor, and --include-inaccessible-class-nodes.
  • The README section Additional Features — the symbol-type bitmask is the single most useful knob and is documented nowhere else.
  • changelog/ — behavior shifts that bite people: conditional declarations extracted from if blocks (0.3), declaration ordering fixed (0.3), --constants (0.7.0), --visitor (0.8.0), --nullify-globals (0.5), duplicate constant/global counting fixed (0.8.6).

The PHP API, if you need to script it:

require 'vendor/autoload.php';

use StubsGenerator\{StubsGenerator, Finder};

$generator = new StubsGenerator();               // or a bitmask, see below
$finder    = Finder::create()->in(base_path('app/Legacy'));

$result = $generator->generate($finder);
echo $result->prettyPrint();                     // string of stub PHP
$stmts  = $result->getStubStmts();               // PhpParser nodes, if you want to post-process

Implementation Patterns

1. Commit the stub, regenerate via Composer script. Stubs are a build artifact that you want under version control — CI and every developer's IDE need them without running Composer first.

"scripts": {
    "stubs": "generate-stubs app/Support/legacy-helpers.php app/Legacy --out=stubs/legacy.stub.php"
}

Multiple paths are space-separated and get concatenated. Add a CI job that runs composer stubs and fails on git diff --quiet stubs/ so stubs never drift from source.

2. Narrow the symbol types with the bitmask. The default (StubsGenerator::DEFAULT) includes everything except undocumented globals, which is usually right. But when stubbing a file that is 90% procedural helpers, FUNCTIONS alone produces a much cleaner result:

use StubsGenerator\StubsGenerator;

$generator = new StubsGenerator(StubsGenerator::FUNCTIONS);
$generator = new StubsGenerator(StubsGenerator::CLASSES | StubsGenerator::INTERFACES);
$generator = new StubsGenerator(StubsGenerator::ALL); // includes undocumented globals

Available flags: FUNCTIONS, CLASSES, TRAITS, INTERFACES, DOCUMENTED_GLOBALS, UNDOCUMENTED_GLOBALS, GLOBALS, CONSTANTS, DEFAULT, ALL.

3. Filter the inputs, don't hand-edit the output. StubsGenerator\Finder wraps Symfony Finder, so the familiar chain works:

$finder = Finder::create()
    ->in([base_path('app/Legacy'), base_path('vendor/acme/sdk/src')])
    ->exclude('Tests')
    ->name('*.php');

If you catch yourself editing stubs/*.stub.php by hand, fix the Finder config or the source docblocks instead — the next composer stubs will erase your edits.

4. Wire the result into PHPStan — and pick the right directive. These are not interchangeable:

  • scanFiles — for symbols PHPStan cannot discover on its own (the class/function exists at runtime but isn't autoloadable, or lives in a path you exclude from analysis). This is what you want 90% of the time.
  • stubFiles — for overriding the signature of a symbol PHPStan already knows about. Use it sparingly; it's the wrong tool for "make this unknown class known".
parameters:
    scanFiles:
        - stubs/legacy.stub.php

5. Post-process the AST instead of post-processing text. Result::getStubStmts() returns PhpParser nodes, so you can traverse them with your own visitor — strip @internal, prepend @deprecated, rewrite namespaces, or inject a @method block for a magic-method-heavy class. The CLI equivalent added in 0.8.0 is --visitor; check --help for the exact argument shape and pass a fully-qualified visitor class name.

6. Package authors: ship stubs in dist. If you maintain a Laravel package with a procedural API, generate stubs as part of your release build, commit them under stubs/, and document the scanFiles snippet for consumers. They get completion without you hand-maintaining a parallel declaration file.

7. Understand what this does not replace. barryvdh/laravel-ide-helper reads Laravel's runtime — it knows what a Facade proxies to, what a __call resolves to, what an Eloquent relation returns. php-stubs/generator reads source AST — it can only see what's literally declared. For Facades, Eloquent magic properties, and container-driven methods, use both tools side by side; they solve different halves of the problem.

Gotchas and Tips

Never autoload a stub file at runtime. Method bodies are emptied and class X declarations are real. Autoloading a stub that shadows a loaded class is a fatal Cannot declare class error. Stubs belong in PHPStan/Psalm config and IDE include paths — nowhere else.

Global variables are emitted verbatim. A source file with $something = '123abc'; becomes a stub line that assigns at include time if the file is ever loaded. Fix it at generation time with --nullify-globals (0.5), which rewrites every global assignment to null. Also note DEFAULT silently drops undocumented globals — a $config = [...] with no /** @var */ block simply vanishes from the output. If downstream analysis depends on it, use GLOBALS or ALL, or add the docblock to the source.

No type inference, so no magic. If the source has no PHPDoc and no native types, the stub has no types either. Generating stubs from untyped code gives you typed-looking symbols that are still mixed. Often the higher-ROI move is adding docblocks to the source file — which the generator then carries through verbatim, @param/@return and all.

Conditional declarations leak. Since 0.3, declarations inside if blocks are all extracted. That's correct for polyfills but means you can end up with two stub definitions of the same function (one per polyfill branch), which PHPStan will flag. Watch for this in vendored libraries with function_exists() guards.

Missing parents break analysis. The generator faithfully writes class MyClass extends MyParentClass — it doesn't resolve or inline the parent. If MyParentClass isn't in your stubs, your autoloads, or scanFiles, PHPStan reports "class not found" and you'll blame the stub file. Add the parent's source directory to the Finder, or add its stubs to scanFiles.

Finder is StubsGenerator\Finder, not Symfony\Component\Finder\Finder. Older README revisions got this wrong (fixed in 0.8.6). The class proxies to Symfony Finder so most of the fluent API works, but don't type-hint against the Symfony class and don't assume every Finder method is exposed.

Constants need explicit opt-in. --constants / StubsGenerator::CONSTANTS (0.7.0) is not part of DEFAULT's mental model the way you might expect — if your legacy code defines constants and PHPStan says they're undefined, that flag is your first stop. Duplicate constant/global counting was fixed in 0.8.6, so pin to ^0.8.6 if you're stubbing files with shared define() calls.

Debugging "the symbol isn't in my stubs" — check in this order: (1) is the symbol type enabled in your bitmask? (2) is it declared inside a function body or closure? (declarations there are not top-level and won't be extracted) (3) is it a global variable without a docblock while you're on DEFAULT? (4) is the file actually matched by your Finder (->name('*.php'), ->exclude())?

--out is optional, and the fallback is stdout. Forgetting --out dumps the whole stub file to the terminal — annoying on a large vendor directory, and actively dangerous if you pipe it anywhere. Always pass --out in scripts.

Private/protected members. Released in 0.8.3, --include-inaccessible-class-nodes controls whether non-public class members are represented. Run --help to confirm the semantics for your version before relying on it; the default output is usually what you want for IDE completion, and the flag matters when you're feeding a reflection-style consumer.

Regenerate after every upstream bump. Stubs are a cache of someone else's API. A vendor update that changes a signature but a stale stub file that keeps the old one is worse than no stub — your IDE will confidently autocomplete a method that no longer exists. Tie regeneration to composer update in CI, not to human memory.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin