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

Symfony Console Completion Laravel Package

stecman/symfony-console-completion

Adds BASH and ZSH tab completion to Symfony Console applications. With zero configuration it completes command names and options; user code can define custom completion for argument and option values. Install via Composer and register the CompletionCommand.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register the command. The package ships a single console command (_completion) — your app needs to know about it before anything else works.

composer require stecman/symfony-console-completion

Where you register it depends on your console stack:

  • Standalone Symfony Console app — add it in Application::getDefaultCommands():
    $commands[] = new \Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand();
    
  • Symfony Framework — tag it console.command in services.yaml (the README's services.yml example).
  • Laravel — the console.command tag is a FrameworkBundle concept and is not honoured by Artisan. Register it as an Artisan command instead (->withCommands([CompletionCommand::class]) in bootstrap/app.php on Laravel 11+, or Artisan::starting(fn ($artisan) => $artisan->add(new CompletionCommand())) in a service provider). Note that php artisan completion already exists in modern Laravel — only reach for this package for your own console binary, a packaged CLI, or when you need value completion for arguments Artisan's built-in doesn't cover.

Wire the shell hook. In a terminal, generate and source the bridge function:

# BASH 4+/ZSH
source <(myapp _completion --generate-hook)

# BASH 3.x
myapp _completion --generate-hook | source /dev/stdin

# any BASH
eval $(myapp _completion --generate-hook)

Pass --program=myapp if you invoke the app through an alias, wrapper script, or a path that isn't on $PATH. Persist the line in ~/.bash_profile / ~/.zshrc so new sessions get it.

Verify the zero-config baseline first. Before writing a single handler, confirm you get command names and option names for free:

myapp <TAB>            # command list
myapp mycommand -<TAB> # options for that command

If that works, the plumbing is sound and everything after this is just value completion.

Where to look first, in order: CompletionCommand (the _completion entry point and configureCompletion() override) → CompletionHandler (how handlers are matched and dispatched) → Completion (the handler value object: command, name, type, results) → CompletionContext (current word, full command line, cursor position). If you only ever do one thing with this library, that thing is implementing CompletionAwareInterface on a command you already own.

Version gate. Current releases target Symfony 6 and PHP 8.x only (the Symfony 6 API breaks made older Symfony unsupportable). On Symfony ≤4 / PHP 7 you must pin 0.11.0, which is a different era of the package.

Implementation Patterns

Pattern 1 — zero-config, no code

Register the command, generate the hook, ship. You immediately get command names, namespaces, abbreviations, and each command's option/argument names. This is the highest value-to-effort ratio and is worth doing even if you never implement a single value completion. Use it as the default for internal tools where discoverability matters more than polish.

Pattern 2 — CompletionAwareInterface for self-contained values

Best when completion logic belongs to one command and depends on that command's own services:

class DeployCommand extends Command implements CompletionAwareInterface
{
    public function completeArgumentValues($argumentName, CompletionContext $context)
    {
        if ($argumentName === 'environment') {
            return $this->config->getEnvironmentNames(); // already in scope
        }
    }

    public function completeOptionValues($optionName, CompletionContext $context)
    {
        if ($optionName === 'ref') {
            return $this->git->branches($context->getCurrentWord());
        }
    }
}

The dispatcher calls these automatically once the command name is on the command line. This is the pattern to reach for day to day — it keeps git log-style lookups next to the command that needs them rather than in a growing god-object.

Pattern 3 — extend CompletionCommand for shared vocabulary

When the same value set is used across commands (--env, --package, --ref, node names, tenant slugs), CompletionAwareInterface forces duplication because it can't delegate to CompletionInterface implementations. Override configureCompletion() instead and register handlers once:

class MyCompletionCommand extends CompletionCommand
{
    protected function configureCompletion(CompletionHandler $handler)
    {
        // one handler, every command, both option and argument positions
        $handler->addHandler(new Completion(
            Completion::ALL_COMMANDS,
            'environment',
            Completion::ALL_TYPES,
            fn () => $this->container->get(Environments::class)->names()
        ));

        // defer path completion to the shell — fast, and handles spaces/quotes correctly
        $handler->addHandler(new Completion\ShellPathCompletion(
            Completion::ALL_COMMANDS, 'path', Completion::TYPE_OPTION
        ));

        // command-scoped, array-based
        $handler->addHandler(new Completion(
            'walk', 'direction', Completion::TYPE_ARGUMENT,
            ['north', 'east', 'south', 'west']
        ));
    }
}

The ALL_COMMANDS + ALL_TYPES combination is the workhorse: adopt a consistent naming convention (--package / package) across your CLI and a single handler lights up everywhere.

Pattern 4 — cheap, cached, side-effect-free providers

A tab press boots your whole application and runs your callbacks, synchronously, at the user's keyboard. Treat completion providers as latency-critical:

  • Prefer in-memory/static caches and short-TTL files over DB queries.
  • Never hit a remote API on the completion path; if you must, wrap in a timeout and return [] on failure.
  • Shell out lazily and cheaply (git show-ref --abbr style) rather than building objects.
  • Keep the callback pure — no logging to stdout, no writes to the DB.

Pattern 5 — distribution and aliasing

If your CLI is shipped as a phar or composer bin, document the exact --generate-hook --program=<name> invocation for the installed name. Use --multiple when a single hook must work regardless of how the binary is invoked. For containers or CI images, bake the source <(...) line into the image's /etc/bash_completion.d/ or the user's rc file — otherwise completion dies at the container boundary and people conclude the package is broken.

Pattern 6 — testing

Don't test through the shell when you can test the interface directly: instantiate the command, call completeArgumentValues('environment', new CompletionContext(...)) and assert the array. For end-to-end, invoke the _completion bridge with a simulated command line (on BASH, by exporting COMP_LINE / COMP_POINT before running the command) and diff the output against expectations in CI.

Gotchas and Tips

Everything printed to stdout corrupts completion. A stray var_dump(), a PHP deprecation notice, a printf in a service you inject — all of it becomes a completion candidate the shell inserts into the user's command line. Send diagnostics to stderr, and keep APP_DEBUG-style chatter out of the completion path entirely. If completion "works but inserts garbage," this is almost always the cause.

Custom application-level options are hidden on purpose. Since 0.9.0, CompletionCommand filters out options defined by your own Application subclass to avoid collisions with its own (--generate-hook, --program, --shell-type). If a global option stops being completed, override filterApplicationOptions() to whitelist it — don't fight the default.

Only --option value and --option "value" are supported. Completion is not implemented for --option=value. If your CLI's documentation leans on = syntax, users will press tab and get nothing, and you'll get the bug report. Same for option shortcuts: -xyz isn't offered in the completion list, but completing after a valid shortcut does work.

VALUE_OPTIONAL options always run value completion, because the library can't tell whether the user wanted to skip the value or complete it. If you have an optional-value option with an expensive provider, guard it manually.

Shell type detection is environment-dependent. ZSH/BASH is inferred from $SHELL; inside containers, sudo, cron-ish contexts, or when SHELL is unset, generation can produce the wrong hook. Pass --shell-type explicitly in any documented install snippet you control. BASH 3.x needs the | source /dev/stdin form; BASH also needs the bash-completion package, and the command emits an explicit error if it's missing (added in 0.6.1).

Register against the right program name. The hook binds to the absolute path by default. Aliases, wrapper scripts (bin/mycli invoking vendor/bin/mycli), and Windows-style shims all break silently — completion simply never fires. Always generate with --program matching what the user types. Re-run hook generation after renaming a binary.

Namespaced abbreviations work, but only in the way Symfony does. deploy:run, d:r, de:r, d:ru all resolve — since 0.5.1. If you hand-roll command resolution elsewhere, don't expect your logic to agree with completion's.

Hidden commands stay hidden. Commands marked setHidden(true) are excluded. If a command is missing from completion but present in list, check the hidden flag before assuming a bug. Conversely, keep _completion itself hidden so it doesn't pollute list output.

Spaces and newlines in values will bite you. Hand-rolled path completion breaks on filenames with spaces and quoting; ShellPathCompletion hands that responsibility back to the shell and gets the escaping right. Use it for anything filesystem-shaped rather than returning glob() results.

Performance is the user experience. Each tab is a full app boot. If your Laravel/Symfony app boots dozens of providers to answer a completion query, completion will feel sluggish and users will stop using it — the feature quietly dies rather than failing loudly. Measure time myapp _completion ... under a realistic command line before shipping.

Debugging recipe: generate the hook and read it (myapp _completion --generate-hook) to confirm the program name and shell branch; then call _completion directly with a synthetic command line and inspect output and exit code; then inspect the bridge function in your shell (declare -f _myapp) to confirm it's actually registered. Layer by layer, never "just re-source and hope."

Version/migration posture. From 0.16.0 the library delegates to Symfony's native shell completion API when no stecman handler provides values — the maintainers are steering users toward symfony/console's own completion and will only keep this alive for existing users. Practical consequences: pin the version (^0.16), don't build new large completion surfaces on the legacy Completion API if symfony/console's API is available to you, and treat your configureCompletion() code as something you may need to port. Existing CompletionAwareInterface implementations keep working and are cheap to keep around during the transition. On older Symfony/PHP stacks the whole calculus changes — stay on 0.11.0 and accept that bug fixes upstream won't reach you.

Interface limitation worth designing around: CompletionAwareInterface cannot reuse CompletionInterface implementations. If you find yourself copying the same three completeOptionValues() branches into five commands, that's the signal to switch to the CompletionCommand extension pattern — don't wait until it's eight.

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