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

Command Line Laravel Package

nette/command-line

Nette Command Line is a lightweight PHP library for parsing command-line arguments, options, and commands. It offers automatic help generation, input validation, and a simple API for building CLI tools and console applications.

Deep Wiki
Context7

Getting Started

Install the package and create a single executable entry point. The whole point is that a plain PHP script becomes a self-documenting CLI tool with almost no ceremony.

composer require nette/command-line
chmod +x bin/ops
#!/usr/bin/env php
<?php
require __DIR__ . '/../vendor/autoload.php';

$console = new Nette\CommandLine\Console();

$console->addCommand('greet', 'Say hello to someone', function (array $args, array $options) use ($console): void {
    $name = $args[0] ?? 'world';
    $console->say("Hello, {$name}");
});

$console->run();
php bin/ops greet John
php bin/ops --help
php bin/ops greet --help

Look first at Nette\CommandLine\Console (addCommand(), run(), say()) and the README examples. The API surface is deliberately tiny: you register commands, each handler receives the positional arguments and the parsed options, and run() dispatches. Help output, usage errors, and ANSI color handling come from the package.

The first realistic use case in a Laravel app is replacing an ad-hoc php scripts/fix-something.php with a properly named subcommand that has --help, documented arguments, and sane error output.

Implementation Patterns

One dispatcher, many commands. Keep a single bin/ops entry point and register each task. This gives you a discoverable tool instead of a directory of one-off scripts.

$console->addCommand('users:prune', 'Delete inactive users', function (array $args, array $options) use ($console): void {
    $days = (int) ($options['days'] ?? 365);
    $dryRun = isset($options['dry-run']);

    $query = User::where('last_seen_at', '<', now()->subDays($days));

    if ($dryRun) {
        $console->say("Would delete {$query->count()} users.");
        return;
    }

    $console->say("Deleted {$query->delete()} users.");
});
php bin/ops users:prune --days=180 --dry-run

Bootstrap Laravel before touching facades or Eloquent. A bin script does not get the framework for free.

$app = require __DIR__ . '/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

$console = new Nette\CommandLine\Console();
// ... register commands that use DB, config, facades ...
$console->run();

Keep handlers thin. The handler should parse/validate inputs, delegate to a service class, and report the result. That makes the business logic testable without spawning a process.

$console->addCommand('export:users', 'Export users to CSV', function (array $args, array $options) use ($console): void {
    $path = $args[0] ?? null;
    if ($path === null) {
        $console->say('Usage: export:users <path> [--format=csv]');
        exit(1);
    }

    $options += ['format' => 'csv']; // user-supplied value wins
    $format = (string) $options['format'];

    try {
        app(UserExporter::class)->export($path, $format);
        $console->say("Exported to {$path}");
    } catch (Throwable $e) {
        $console->say('Export failed: ' . $e->getMessage());
        exit(1);
    }
});

Handle argument validation yourself. There is no Symfony-style InputArgument::REQUIRED; check $args count and option values at the top of the handler and exit non-zero with a usage line.

Use it for operational tooling, not as an Artisan replacement. Good fits: deploy hooks, data migrations, backfills, cron jobs, local developer utilities, and small internal binaries. If the CLI is large, interactive, or needs command auto-discovery, use Artisan or Symfony Console instead.

Add a bin entry in composer.json so the tool is installed into vendor/bin and available without remembering the path:

{
    "bin": ["bin/ops"]
}

Test through the process boundary. Because colors turn off when output is piped, assertions are stable:

$process = new Symfony\Component\Process\Process([PHP_BINARY, 'bin/ops', 'greet', 'John']);
$process->run();

$this->assertSame(0, $process->getExitCode());
$this->assertSame("Hello, John\n", $process->getOutput());

Gotchas and Tips

  • Handler signature is (array $args, array $options). The command name has already been consumed; $args contains only positional arguments.
  • Prefer --option=value over --option value. The space-separated form can be ambiguous with positional arguments. Use = for values that start with -, and -- to stop option parsing when passing through arguments.
  • Boolean flags are presence-based. isset($options['force']) is the safe check; do not assume $options['force'] === true.
  • Options are untyped. Cast and validate: (int), filter_var(), allow-lists. A missing option is null/absent, not an exception.
  • return from a handler does not set an exit code. If a cron job or CI step depends on failure, call exit(1) explicitly or throw and confirm how your version reports uncaught exceptions.
  • No interactive prompts, progress bars, verbosity levels, or signal handling. Use say() lines for progress. For long-running workers that need SIGTERM, wire pcntl_signal() yourself.
  • Do not write raw ANSI codes. The package’s color handling is TTY-aware and disables colors in pipes; hardcoded escapes break logs and CI output.
  • --help is generated from your command descriptions. Write short, accurate descriptions and include a usage line in the handler error path; otherwise the tool documents itself poorly.
  • Bootstrap errors are the most common Laravel failure. “A facade root has not been set” almost always means the console kernel was not bootstrapped before the handler ran.
  • Do not shell out to artisan from inside the script. After bootstrapping, use Artisan::call() or call the underlying service directly.
  • Output ordering can interleave if you mix echo with say(). Pick one output path.
  • Windows: use php bin/ops ... or a .bat shim; shebang execution is Unix-only.
  • Keep it small on purpose. There is no DI container, auto-discovery, config system, or plugin mechanism. If you find yourself building those, you have outgrown the package.
  • Check the license file before redistribution. Package metadata reports NOASSERTION; Nette projects are typically BSD-3-Clause, but verify the repository’s license file rather than trusting the registry entry.
  • Pin the version in composer.json. The API is stable but minimal, and this is a niche package with a small maintainer/community footprint compared with Symfony Console.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package