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

Laminas Cli Laravel Package

laminas/laminas-cli

laminas-cli provides a Symfony Console-based command runner for Laminas and Mezzio applications, letting you register CLI commands as services and execute them through a unified laminas-cli binary.

View on GitHub
Deep Wiki
Context7

Getting Started

What it actually is: a thin bin wrapper around Symfony Console that boots your Laminas MVC / Mezzio PSR-11 container and resolves commands from that container. It is the supported replacement for the abandoned laminas-mvc-console, and it is the reason your CLI code can reuse the exact same services your HTTP stack uses.

Minimal wiring (MVC or Mezzio, identical config):

composer require laminas/laminas-cli symfony/console
vendor/bin/laminas list
// config/autoload/laminas-cli.global.php (MVC) — or your ConfigProvider (Mezzio)
return [
    'laminas-cli' => [
        'commands' => [
            'app:import' => App\Command\ImportCommand::class,
        ],
    ],
    // MVC: 'service_manager' => ['factories' => [...]]
    // Mezzio: 'dependencies' => ['factories' => [...]]
];

Register a factory for the command as soon as it has constructor dependencies (ServiceManager does not auto-wire by default). The array key is the command name users type; the value is the container service id.

Where to look first:

  1. vendor/bin/laminas list — confirms the package sees your config. If a command is missing here, it's a config/module-registration problem, not a code problem.
  2. docs/book/ in the repo — the introduction page walks MVC users through the three steps; the params chapter covers interactive commands.
  3. Symfony\Component\Console\Command\Command docs — laminas-cli adds almost nothing on top; your knowledge transfers directly.

First useful use case: pick the one-off script currently living at bin/import.php or scripts/fix-data.php that bootstraps the container by hand. Move it into src/Command/, inject the same service your HTTP handler uses, delete the script. You immediately get --help, argument validation, exit codes, and list discoverability for free.

Implementation Patterns

Thin command, fat service (the pattern that matters). Commands are adapters. All logic goes in an injected service so the same code path runs from a Mezzio handler, a queue consumer, and a cron job.

#[AsCommand(name: 'app:import', description: 'Import products from a CSV file')]
final class ImportCommand extends Command
{
    public function __construct(private readonly ProductImporter $importer) { parent::__construct(); }

    protected function configure(): void
    {
        $this->addArgument('file', InputArgument::REQUIRED, 'CSV path');
        $this->addOption('dry-run', null, InputOption::VALUE_NONE);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        try {
            $stats = $this->importer->import($input->getArgument('file'), (bool) $input->getOption('dry-run'));
        } catch (Throwable $e) {
            $io->error($e->getMessage());
            return Command::FAILURE;
        }
        $io->success(sprintf('%d rows', $stats->rows));
        return Command::SUCCESS;
    }
}

Since 1.15.0 the #[AsCommand] attribute is honoured, so you can drop the duplicated name from configure() and keep a single source of truth.

Param-aware commands for human-facing tooling. Laminas\Cli\Command\AbstractParamAwareCommand with Laminas\Cli\Input\*Param turns "which environment? which tenant?" into prompts, and silently becomes --env=prod --tenant=acme when flags are supplied — ideal for destructive tasks.

final class PurgeCommand extends AbstractParamAwareCommand
{
    protected function configure(): void
    {
        $this->setName('app:purge')
            ->addParam(new ChoiceParam('env', 'Target environment', ['dev', 'staging'], 'dev'))
            ->addParam(new StringParam('tenant', 'Tenant slug'))
            ->addParam(new BoolParam('confirm', 'Really purge?'));
    }
}

Ship commands from reusable packages. Put a ConfigProvider in your library that returns ['laminas-cli' => ['commands' => [...]]]. Any application that installs the package instantly gets yourvendor:sync — no app-side config edits, no doc page explaining a manual registration step.

Scheduling / workers. Point cron and supervisor at the same binary: vendor/bin/laminas app:sync --no-interaction. One entrypoint, one container bootstrap, one place to change config.

Alternative containers for testing. --container=path/to/container.php accepts a filename returning a PSR-11 instance. Use it to run the real CLI against a stub container (fixed clock, fake mailer), or to point the CLI at a different project checkout.

Gotchas and Tips

  • Command name collisions. list, help, completion, _complete are Symfony built-ins. Always namespace as vendor:action; laminas-cli will not warn you about shadowing.
  • Exit codes are your CI contract. Return Command::SUCCESS / Command::FAILURE. A command that returns null implicitly returns 0 and your pipeline goes green on failure. Uncaught exceptions give non-zero plus a stack trace — nicer for debugging, noisier for cron mail.
  • Container resolution failures are the #1 first-day error. Symptom: "Unable to resolve service …" or a raw ServiceNotFoundException when you invoke the command. Cause: class registered in laminas-cli.commands but no factory. Since 1.0.1 an unknown name gives a clean "command not found"; a broken factory does not.
  • Config not merged. In MVC the module must be enabled in config/modules.config.php; in Mezzio Laminas\Cli\ConfigProvider must reach your config-aggregator (automatic with laminas-component-installer). Verify with vendor/bin/laminas list --raw.
  • --no-interaction hangs or fails on param commands. Prompts require a TTY. Give every Param a usable default, or make the required ones explicit so the failure is loud rather than a silent hang in a container.
  • Don't assume HTTP context. Config or service factories that touch $request, sessions, or cookies will explode under CLI. Guard those services ($container->has(RequestInterface::class)) before your command needs them.
  • Symfony version drift is the main upgrade hazard. 1.10 allowed Symfony 7 / dropped 5; 1.11 fixed Symfony 7.1 compat; 1.14 dropped Symfony 6 and permits 8; 1.13 dropped PHP 8.1. Pin symfony/console deliberately and re-run vendor/bin/laminas list after every bump — attribute-command support (1.15) specifically exists because of a Symfony 7.x regression.
  • Lazy loading is a feature, not a detail. Commands are only instantiated on invocation, so a command with a heavy constructor is cheap until used — but a command whose factory eagerly connects to a database makes list slow. Keep factories lazy; connect inside execute().
  • Long-running loops leak. Doctrine's EntityManager and identity map grow per iteration. clear() every N records, chunk your queries, and print progress via ProgressBar so a supervisor-restarted worker is diagnosable.
  • Debugging: -vvv for verbose output, --no-ansi for clean logs, and SymfonyStyle for consistent formatting. vendor/bin/laminas app:import -vvv will show the exception trace Symfony hides at default verbosity.
  • Events hook in without touching the command. ConsoleEvents::COMMAND / TERMINATE listeners are the right place for cross-cutting concerns (timing, audit logging, memory reporting). Attach them as subscribers rather than one-by-one listeners — this is the pattern the docs corrected in 1.5.0.
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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator