jetbrains/phpstorm-stubs
Official JetBrains stubs provide syntactically correct PHP function and class signatures, constants, and detailed PHPDoc for built-in PHP and most standard extensions. They power IDE completion, code inspection, type inference, and documentation popups in PhpStorm and other tools.
jetbrains/phpstorm-stubs is not application code — it is a corpus of syntactically valid PHP files that declare signatures, constants, and PHPDoc for PHP core plus bundled and PECL extensions. For a Laravel developer it is a dev-only toolchain artifact: it never enters require, never ships in the image, and never touches Composer autoloading of your app. Its only job is to feed an IDE index or a static analyzer's stub resolution.
1. Install as a dev dependency, pinned.
composer require --dev jetbrains/phpstorm-stubs
Tags mirror PhpStorm builds (v2024.2, v2022.1, …), not PHP or Laravel versions. Pin deliberately ("jetbrains/phpstorm-stubs": "2024.2.*" or your org's exact pin) and write down who bumps it.
2. Decide whether you actually need it. PhpStorm already bundles stubs generated from this repo. Adding the package without wiring it up yields zero benefit. The realized value is narrow: signatures your IDE build or your analyzer does not yet know — a newer PECL extension (Swoole, mongodb, rdkafka, amqp, excimer, igbinary), a PHP version ahead of your IDE release, or a specific function whose PHPDoc is wrong for your runtime.
3. Wire it up in the IDE. Settings | Languages & Frameworks | PHP | PHP Runtime | Advanced settings | Default stubs path → point at vendor/jetbrains/phpstorm-stubs (or a full git clone if you want two-way updates). In a Docker/WSL setup make sure the path resolves from the IDE's perspective, not the container's.
4. Pick a real first use case. Don't pilot on a plain Eloquent app — the built-in stubs already cover it. Pilot on something where you've actually seen an "Undefined class" or a false positive: a queue worker calling pcntl_fork, a cache layer on Redis, an activity log writing through MongoDB\Driver\Manager. Capture the "before" inspection count so you can prove the change did something.
Where to look first: the directory listing — one folder per extension (Core/, standard/, redis/, mongodb/, …) — and PhpStormStubsMap.php, the symbol→file map the project itself generates. Grepping that map is the fastest way to find which file declares a symbol you care about.
IDE-only, zero-config-per-repo. The dominant pattern. Pin the package, add the stubs path once in PhpStorm, done. Commit a .idea/php.xml snippet or document the setting so onboarding doesn't depend on tribal knowledge. Note that PhpStorm only stubs enabled extensions, so a missing completion is sometimes an extension-toggle problem, not a stub problem.
Targeted analyzer stubs (PHPStan / Larastan). Do not add the whole tree. PHPStan ships its own stubs (phpstan/php-8-stubs), and dumping 8,000+ files on top produces conflicting docs and occasional duplicate-symbol noise. Add only the files for extensions the built-ins miss:
# phpstan.neon
parameters:
stubFiles:
- vendor/jetbrains/phpstorm-stubs/redis/Redis.php
- vendor/jetbrains/phpstorm-stubs/mongodb/BSON.php
excludePaths:
analyse:
- vendor/jetbrains/phpstorm-stubs/*
(In newer releases the files carry a .stub.php suffix — check the directory before copying a path; a wrong path fails silently in some configurations.)
Psalm. Same idea via <stubs><file name="…"/></stubs> in psalm.xml, again scoped to specific files.
Stubs as a reference manual. Because they're plain readable PHP, this is the fastest offline way to answer "what's the exact signature of array_is_list and when was it added?" Grep for the function, read the @since, the version-conditional attributes, and the @param/@return. Faster and more precise than the online manual for type questions, and it works on a plane.
Contribution loop as an extension point. When a PECL stub is wrong for the version you run, the fix path is local-first, upstream-second: edit the stub, regenerate the stubs cache (php tests/run-stubs-parser.php), then run the suites — ./runTests.sh --php-version 8.3 for a fast single-version loop, full ./runTests.sh before opening a PR. If you add or remove symbols, regenerate PhpStormStubsMap.php. Inside stub files the version machinery is attributes, not prose:
#[LanguageLevelTypeAware(['8.0' => 'string'], default: 'array|false')]
#[PhpStormStubsElementAvailable(from: '8.0')]
function example(string $s): string {}
Matching that convention is what makes a local patch acceptable upstream.
Boundary with laravel-ide-helper and .phpstorm.meta.php. These solve different problems: phpstorm-stubs describes native symbols; barryvdh/laravel-ide-helper and .phpstorm.meta.php describe your dynamic Laravel surface (facades, container bindings, Model::query()). If a completion is missing for Redis::lpush, that's this package. If it's missing for MyFacade::something(), it's ide-helper. Don't try to fix Laravel dynamic types by editing vendor stubs — the change dies on the next composer update.
Team rollout. Inventory editors first (PhpStorm vs VS Code/Intelephense vs Phpactor), then PHP versions, then PECL extensions in use. Standardize on one pin, one documented stubs path, and one bump cadence tied to PhpStorm upgrades. Sequence it after you have a PHPStan baseline, or neither initiative will be attributable.
Never let the stubs autoload. They declare functions and classes that already exist natively. Any path that lands them in autoload.files, a broad classmap, or an analyzer's analysed paths invites duplicate-declaration fatals or a wall of "already defined" findings. Keep them out of require, out of the production image, and out of CI build stages that don't run static analysis.
PhpStorm already has these. Measure before/after or you will "adopt" a package that changed nothing. If a completion is still missing after wiring it up, the usual culprits are: wrong stubs path, a path that doesn't resolve in a remote/WSL interpreter, the extension not enabled for that interpreter, or a stale index (Invalidate Caches / Restart).
Non-standard extension stubs are "as is". The README is explicit: community-driven, PHPDoc-validated only, @throws tags untested, and accuracy against the real extension is not verified. An IDE confidently autocompleting a wrong Redis option signature is worse than silence — treat these stubs as hints for non-core extensions, and verify against the extension's docs when behavior matters.
Precedence is undefined in practice. When phpstorm-stubs and phpstan/php-8-stubs describe the same symbol, which wins depends on load order and analyzer internals. The failure mode is the worst one: IDE and CI disagreeing about the same type. Scope stubFiles to the specific gaps and re-check after every PHPStan or stub bump.
Version mismatch is silent drift. Releases track PhpStorm builds, not PHP. A pin that's behind your IDE build or ahead of your PHP version produces subtly wrong signatures with no error anywhere. Two or three bumps a year, owned by someone, reviewed when the team's PHP version moves.
Non-PhpStorm editors get little from it. Intelephense bundles a fixed stub list and does not consume this package; Phpactor has its own mechanism. If half the team is on VS Code, the ROI conversation is really about PHPStan/Psalm stub coverage, not about the Composer artifact.
Indexing cost on large repos. Thousands of extra PHP files in the project view slow initial indexing and pollute search results. Mark the vendor stub directory as excluded from search if it becomes noisy, but keep it indexed if the IDE needs it for resolution.
License hygiene. Apache-2.0 plus CC-BY 3.0 material from the PHP Documentation Group. Fine for internal developer use; if you vendor it into a base image or shared golden repo, the attribution obligation needs a written answer.
Don't "fix" the legacy links. Some @link tags point at php-legacy-docs.zend.com on purpose — retired extensions and removed functions whose php.net pages 404. If you contribute upstream, repointing them at php.net gets your PR rejected.
How can I help you explore Laravel packages today?