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

Wp Cli Stubs Laravel Package

php-stubs/wp-cli-stubs

PHP stub definitions for WP-CLI, providing classes, functions, and constants for IDE autocompletion and static analysis. Helps PHPStan, Psalm, and editors understand WP-CLI APIs without running the command-line tool.

View on GitHub
Deep Wiki
Context7

Getting Started

php-stubs/wp-cli-stubs is a declaration-only package: it ships .php files that define WP_CLI, WP_CLI_Command, WP_CLI\Utils\*, WP_CLI\Iterators\*, WP_CLI\Dispatcher\* and the other globals WP-CLI exposes without any bodies. It exists so PHPStan, Psalm, and PhpStorm can resolve WP-CLI symbols in projects that never load the WP-CLI runtime themselves (plugins, themes, WP-CLI packages, test suites). It changes nothing at runtime — if you need behaviour, install wp-cli/wp-cli or use the wp binary.

1. Install as a dev dependency

composer require --dev php-stubs/wp-cli-stubs

It belongs in require-dev. It is never a production dependency.

2. Look at what you got first

ls vendor/php-stubs/wp-cli-stubs/
# typically: wp-cli-stubs.php  composer.json  README.md  LICENSE

Open the root stub file and skim it — that is authoritative. It tells you exactly which methods exist on WP_CLI and which WP_CLI\Utils\* functions are declared in this version. If the file name differs, use whatever the package README shows.

3. Wire it into PHPStan

# phpstan.neon
parameters:
    level: 6
    paths:
        - src
        - tests
    scanFiles:
        - vendor/php-stubs/wp-cli-stubs/wp-cli-stubs.php

scanFiles is the right key here: the symbols are simply "made known" to PHPStan. (Use stubFiles only when you need to override a definition PHPStan already sees — see Gotchas.)

4. First use case: a WP-CLI command in a plugin

Without the stubs, this file produces "Class WP_CLI_Command not found" and "Call to undefined method WP_CLI::success()":

<?php
declare(strict_types=1);

namespace Acme\Plugin\Cli;

use WP_CLI;
use WP_CLI_Command;
use WP_CLI\Utils;

final class Cache_Command extends WP_CLI_Command
{
    /**
     * Flush the Acme cache.
     *
     * ## OPTIONS
     *
     * [--store=<store>]
     * : Cache store to flush.
     *
     * ## EXAMPLES
     *
     *     wp acme cache flush --store=object
     *
     * @param array<int, string>    $args
     * @param array<string, string> $assoc_args
     */
    public function flush( array $args, array $assoc_args ): void
    {
        $store = Utils\get_flag_value( $assoc_args, 'store', 'object' );

        // ... actual work ...

        WP_CLI::success( "Flushed {$store}." );
    }
}

Registration, guarded so the plugin still loads under normal WordPress:

if ( defined( 'WP_CLI' ) && WP_CLI ) {
    WP_CLI::add_command( 'acme cache', \Acme\Plugin\Cli\Cache_Command::class );
}

Re-run PHPStan and the three errors — undefined class, undefined method, undefined function — disappear. That is the entire value proposition: you get static analysis on the WP-CLI surface area of your own code.


Implementation Patterns

Stack the stub packages, don't pick one

Most projects that use this also need WordPress core symbols. The idiomatic combination is:

composer require --dev \
    szepeviktor/phpstan-wordpress \
    php-stubs/wp-cli-stubs

szepeviktor/phpstan-wordpress brings php-stubs/wordpress-stubs and a .neon extension for core; it does not cover WP-CLI. Add wp-cli-stubs to scanFiles yourself and you get full coverage of WP_User, $wpdb, WP_CLI, and WP_CLI\Utils\* in one analysis run.

Keep the command class thin, test the logic elsewhere

WP_CLI is entirely static — it is not injectable and PHPUnit cannot meaningfully mock it. The pattern that survives both static analysis and testing is:

final class Cache_Command extends WP_CLI_Command
{
    public function __construct( private Cache_Flusher $flusher ) {}

    public function flush( array $args, array $assoc_args ): void
    {
        $store = Utils\get_flag_value( $assoc_args, 'store', 'object' );
        $this->flusher->flush( $store );
        WP_CLI::success( 'Flushed ' . $store . '.' );
    }
}

Now unit tests exercise Cache_Flusher with zero WP-CLI involvement, and integration tests exercise the command via the real binary:

$out = WP_CLI::runcommand( 'acme cache flush --store=object', [ 'return' => true ] );

Both forms — the class and runcommand — type-check because the stubs declare them.

Use the WP_CLI\Utils namespace explicitly

Everything useful lives under WP_CLI\Utils\: get_flag_value(), format_items(), report_batch_operation_results(), launch_editor_for_input(), http_request(), get_sql_identifier(). Import the namespace and call Utils\foo(...) rather than aliasing individual functions — it stays readable and matches how WP-CLI's own docs read.

Override never returns with a local stub

Real WP_CLI::error() and WP_CLI::halt() terminate the process, but older stub revisions may type them as void. If PHPStan then misses unreachable code or asks for a return after an error branch, add a small local stub and point stubFiles at it:

parameters:
    stubFiles:
        - stubs/wp-cli-overrides.stubphp
<?php
namespace WP_CLI {
    class_alias( \WP_CLI::class, \WP_CLI::class ); // no-op guard
}

In practice the cleaner route is a one-line docblock on your own wrapper, or simply throw in the branch PHPStan complains about — do not try to "fix" the vendor stub in place.

Psalm

<stubs>
    <file name="vendor/php-stubs/wp-cli-stubs/wp-cli-stubs.php"/>
</stubs>

Psalm's <stubs> behaves like PHPStan's scanFiles for this purpose.

CI

- run: composer install --no-interaction
- run: vendor/bin/phpstan analyse --no-progress

No extra step. The stubs are just files on disk.

IDE

PhpStorm indexes vendor/ by default, so composer require --dev alone gives you completion inside WP_CLI_Command subclasses. If your IDE skips non-autoloaded files, add the stub file as an Include Path (Settings → PHP → Include Path) rather than adding it to Composer's autoload map.


Gotchas and Tips

Stubs are declarations only — never let them load at runtime. If the stub file is ever required inside a real WP-CLI process, PHP fatals on "Cannot declare class WP_CLI, because the name is already in use." Keep the package in require-dev, do not add it to autoload.files, and do not copy stub files into a plugin's includes.

scanFiles vs stubFiles is the most common confusion. scanFiles adds unknown symbols; stubFiles replaces known ones. If PHPStan says "already defined" — usually because you also installed wp-cli/wp-cli or pulled the same file in twice via a glob — move the path from scanFiles to stubFiles, or drop the duplicate.

Never glob. scanFiles: vendor/php-stubs/*/*.php silently drags in WooCommerce, WordPress, and anything else installed. List each stub file explicitly.

Version skew is real. The stubs are generated from a specific WP-CLI release. If you use a WP_CLI\Utils\* function added in a recent WP-CLI but pin an old stub package, PHPStan reports it as undefined. Bump php-stubs/wp-cli-stubs when you bump WP-CLI; conversely, when generated stubs lag a WP-CLI release, write the call behind your own small wrapper and analyse the wrapper.

WP_CLI_Command is a thin base class. The stubs declare it but obviously cannot model magic dispatch. Everything the wp binary does — routing wp acme cache flush to flush(), parsing ## OPTIONS, hydrating $assoc_args — is invisible to static analysis. That means: PHPStan will never catch a typo in your ## OPTIONS block, and it will not know that $assoc_args['store'] exists. Keep the docblock accurate by hand and type $args/$assoc_args yourself.

The WP_CLI constant. Real WP-CLI defines define( 'WP_CLI', true ); the stubs may or may not. If PHPStan flags WP_CLI as an undefined constant inside your if ( defined( 'WP_CLI' ) && WP_CLI ) guard, add a bootstrapFiles entry:

<?php // stubs/bootstrap.php
define( 'WP_CLI', true );

Do not define it in shipped plugin code.

If you already have wp-cli/wp-cli installed (common for wp package development or phar builds), the real classes are in vendor/. Adding the stubs on top is harmless for analysis but redundant — and occasionally produces duplicate-symbol warnings. Check composer show wp-cli/wp-cli before adding this package.

No generics, no array shapes. Utils\format_items() takes an untyped array and echoes; Utils\get_flag_value() returns mixed. In a strict project you will end up casting. Wrap them once:

/** @param array<string, string> $assoc_args */
function acme_flag( array $assoc_args, string $key, string $default = '' ): string {
    return (string) \WP_CLI\Utils\get_flag_value( $assoc_args, $key, $default );
}

"0 dependents" is misleading. Stub packages are almost always require-dev, and dev dependencies are excluded from Packagist's dependent counts. Read "0 dependents" as "not counted", not "unused".

WP_CLI::add_hook() / do_hook() are typed loosely. Hooks are the extension point for WP-CLI internals; the stub signatures are string + callable, so PHPStan cannot check your callback's arity. Annotate the callback explicitly or accept the gap.

Extending: if you maintain multiple packages needing the same setup, publish a tiny internal phpstan-extension package with an extension.neon that scanFiles both the WordPress and WP-CLI stubs, and register it via phpstan/extension-installer. That is exactly what szepeviktor/phpstan-wordpress does for core; there is no equivalent wrapper for WP-CLI yet, which is the obvious gap in this package's ecosystem.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib