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.
Architecture fit. laminas/laminas-cli is a console kernel, not a library of commands: it resolves a PSR-11 container, reads a laminas-cli.commands config map, and hands the resulting Symfony Console commands to a runner. Laravel already ships an equivalent kernel (Artisan) on the same underlying component (symfony/console). The two therefore compete at the entry-point layer and overlap ~100% functionally in a pure Laravel estate. The only durable architectural value for a Laravel shop is the interchange format: a Symfony\Component\Console\Command\Command subclass is portable between both runners. That reframes the package from "CLI framework" to "a second runner we would only need if we share command code with a non-Laravel application."
Integration feasibility. Technically achievable, low-to-moderate effort:
laminas-cli expects a config service returning an associative array, whereas Laravel binds config to Illuminate\Config\Repository. A thin adapter (or a bespoke container file) is required.--container=<path> global option (added in 1.1.0, hardened in 1.1.1) is the clean seam: a single PHP file can boot Laravel's application kernel and return a PSR-11 wrapper exposing config plus command factories. This avoids monkey-patching the framework.laminas-cli entirely and register the same command classes in Artisan via Kernel::$commands. Zero new dependencies.Technical risk.
| Risk | Severity | Notes |
|---|---|---|
| Symfony major churn | High | 1.10 dropped Symfony 5, 1.14 dropped Symfony 6 and permits 8. Each Laminas minor can invalidate our Laravel version pairing. This is the dominant constraint. |
| PHP floor drift | Medium | 1.13 dropped PHP 8.1 and added 8.5. Laravel 10 projects on PHP 8.1 are excluded from current releases. |
| Dual bootstrapping | Medium | Two entry points means two config-cache/.env paths that can silently diverge. |
| Bus factor / velocity | Medium | Recent releases are almost entirely dependency bumps (Renovate) by 2–3 contributors; feature work is sparse (one attribute-command PR in 1.15.0). Maintenance-mode posture. |
| Ecosystem signal | Medium | 56 stars, low dependent count, no first-party Laravel integration. Adoption risk if we later need community answers. |
| Feature gap vs Artisan | Low | No scheduling, no --env, no Tinker, no event/discovery parity. |
Key questions for the team.
Stack fit. Poor as a default dependency, viable as a scoped exception. Laravel's idiomatic answer to cross-application command sharing is a Composer package exposing Command classes, consumed by Artisan. laminas-cli should be evaluated only where a Laminas/Mezzio runtime is a first-class target.
Migration path (if we proceed). Use the package as an additive runner, never as a replacement:
symfony/console and PSR-11 — no Laminas runner, no Laravel framework.App\Console\Kernel (or via withCommands() in bootstrap/app.php for Laravel 11+ style). No laminas-cli dependency.laminas/laminas-cli and map the same classes under laminas-cli.commands, with a ServiceManager/dependencies factory entry per command.bin/laminas wrapper that returns a PSR-11 adapter over Illuminate\Container\Container with config projected to an array.Compatibility (verify against composer.json before committing).
| Host | PHP | symfony/console |
Highest usable laminas-cli |
|---|---|---|---|
| Laravel 10.x | 8.1–8.3 | ^6.2 | 1.13.x (8.1 hosts capped at 1.12.x) |
| Laravel 11.x | 8.2–8.4 | ^7.0 | 1.14–1.15 |
| Laravel 12.x | 8.2–8.5 | ^7.x | 1.14–1.15 |
Two independent floors (PHP and Symfony Console) must be satisfied simultaneously, and the ceilings move with each Laminas minor. Pin the package to a caret range and gate upgrades on our slowest-moving Laravel major.
Sequencing.
--container adapter, run one trivial command, and confirm composer why-not behaviour against our other pinned deps.Maintenance. Low code surface, ongoing constraint management. The real cost is not the package but the pairing matrix: every Laravel or Laminas upgrade requires re-validating PHP and Symfony Console ranges together. Budget ~1–2 engineer-days per major-version crossing. If the package is only consumed by Laminas services, that cost is contained; if it sits in a Laravel composer.json, it becomes a permanent transitive constraint on framework upgrades.
Support. There is no vendor SLA. Upstream responsiveness is community-driven and skewed toward dependency updates; a Symfony 9 or PHP 8.6 incompatibility would be resolved on maintainer availability, not our timeline. Mitigation: keep the shared command classes free of runner-specific code so a stalled upstream blocks only the runner, not the functionality. Internal ownership must be named explicitly — a platform engineer, not a feature team.
Scaling. Console scaling is about CI and operational fan-out, not request throughput:
spatie/fork); laminas-cli adds nothing here and does not implement a --parallel model.--container file is a per-invocation cost and a per-invocation trust boundary; it must be a checked-in, non-user-supplied path in production.Failure modes.
| Mode | Symptom | Detection | Response |
|---|---|---|---|
| Container adapter drift | config service absent or wrong shape → command map empty, "command not found" |
Smoke test in CI invoking a known command | Fail the pipeline; adapter is versioned code |
| Transitive version conflict | composer update blocked on symfony/console |
composer why-not gate in CI |
Cap laminas-cli, or drop it from the Laravel app |
| Dual-bootstrap divergence | Command reads stale config-cache / wrong .env |
Structured log of resolved environment at command start | Prefer Artisan for Laravel-hosted jobs |
| Unmaintained upstream | No release covering new PHP/Symfony | Renovate/Dependabot alerts unattended | Vendor or forklift the runner; commands remain portable |
| Silent exit-code semantics | Non-zero failures masked by wrapper shell scripts | Assert exit codes in integration tests | Standardise on Symfony Console return codes |
Ramp-up. Low for the command API (it is Symfony Console, already familiar to any Laravel engineer writing Artisan commands). Moderate for the Laminas side: mezzio/MVC service-manager factory conventions, module config aggregation, and the laminas-cli.commands map are a distinct mental model from Laravel's service providers and Kernel::$commands. Estimate half a day of onboarding for a Laravel engineer to author a portable command, and roughly a day to confidently operate the Laminas runner. Documentation in the repo is adequate (README plus docs site, referenced by the maturity signal) but assumes Laminas familiarity.
TPM recommendation. Do not adopt as a standard dependency in the Laravel estate. Pursue the narrow version of the opportunity: extract genuinely shared operators into a framework-neutral Symfony Console package, consume it via Artisan in Laravel, and add laminas-cli only to Laminas/Mezzio services that already exist. Re-evaluate if a Laminas footprint materialises or if Artisan proves insufficient for a cross-framework delivery requirement.
How can I help you explore Laravel packages today?