andydefer/php-signature-parser
A strict, typed parser for CLI signatures that extracts the command source, required and default arguments, nullables, variadics, enums, and flags using value objects and typed collections, with ^ spacing, inline comments, special tokens, and custom tags.
composer require andydefer/php-signature-parser
Requires PHP 8.1+ (the package uses enums, typed collections and value objects). Verify the tag you install — with zero stars and no release history you should pin an exact version in composer.json rather than accept a floating constraint.
Where to look first. The README is in French and is ordered as a tutorial, not a reference. Read it in this sequence to avoid getting lost:
Then open src/ directly. Look for the parser entry class (the one called in section 9), a VO/ (or similarly named) directory containing SignatureStructureVO, and the four collection classes: ArgumentCollection, FlagCollection, EnumCollection, VariadicArgumentCollection. Those seven types are effectively the entire public surface — read them before reading anything else, because the README describes behaviour but not method signatures.
First use case. Treat a signature string as a declarative command contract and compile it into a structure you can introspect:
$structure = $parser->parse('greet {name} {--loud} {times=1} {tags*?} ^"Say hello"');
$structure->arguments(); // ArgumentCollection
$structure->flags(); // FlagCollection
$structure->enums(); // EnumCollection
$structure->variadics(); // VariadicArgumentCollection
The obvious day-one win is generating --help output and input validation from the same string that defines the command, instead of maintaining a separate definition array by hand.
Compile once, reuse. Parsing is a pure function of a static string. Do not call the parser on every request or every command invocation. Parse at container-build/boot time (or in a cached config step) and keep the SignatureStructureVO around — or serialise it if the VO supports it. In a long-running worker (Octane, Swoole, queue daemon) this turns into a zero-cost operation after warmup.
Signature-as-contract with a fixture suite. Because the grammar is strict and typed, signature strings deserve their own tests. Keep a directory of .sig fixtures or a data provider of [signature, expectedArguments, expectedFlags] triples. This is how you catch the day someone reorders an argument (see Gotchas) and silently breaks the CLI for users.
Adapter layer, not direct coupling. The VOs are convenient but they are owned by a 0-star package. Convert them into your own CommandDefinition / InputDefinition (Symfony Console or Laravel-style) at the boundary. Every handler, help renderer and validator then depends on your type, and swapping the parser later is a one-file change.
Map collections to framework primitives. The typical wiring:
ArgumentCollection → required positional params, in order.FlagCollection → boolean options; feed them to your option parser as no-value switches.EnumCollection → the choices list in --help and the runtime validator; deriving both from one source removes a classic source of drift.VariadicArgumentCollection → trailing array parameter; enforce "must be last" in your own validation if the parser doesn't.Custom tags as metadata, not logic. Custom tags are the natural place for @hidden, @deprecated, @group, @alias. Parse them once and translate immediately into your own attribute or enum. Do not let tag strings leak into command classes — you lose type safety and make renames a grep exercise.
Use inline comments and ^ for maintainability. Inline comments let the signature stay readable next to the code that consumes it. The ^ token (space formatting) is best reserved for help-text rendering, so the alignment of your --help output is declared in the signature rather than hard-coded in a template. Snapshot-test the rendered help output, because hand-verifying whitespace is tedious and this token is unidiomatic.
Wire the ? and _ tokens deliberately. ? (explicit null) and _ (skip) are the two tokens most likely to be cargo-culted. Decide a house rule — e.g. "? for optional positional values, _ only when bridging a legacy positional signature" — and document it in your own contributing notes, since upstream docs are French-only and terse.
Strict argument order is your public API. Reordering arguments in a signature almost certainly changes which positional input maps to which parameter, and the parser will accept it happily. Treat a reorder as a breaking change and let your fixture tests be the tripwire.
Reserved characters have no obvious escape hatch. ?, _ and ^ are parser-significant. Confirm in src/ whether a literal ? or _ inside a default value or description can be escaped. If it cannot, avoid those characters in defaults — a default of "unknown?" is exactly the kind of thing that parses without error today and misbehaves after an upgrade.
Check _ against positional indexing. A skip token presumably still occupies a position. Verify with a test whether cmd {a} _ {b} shifts b to index 2 or 3 — getting this wrong on a legacy-compatibility shim is a subtle, high-cost bug.
Nullable vs default precedence. ? (explicit null) and {x=1} (default) overlap conceptually. Test all combinations — {x?}, {x=1}, and any combined form — and assert on the resulting value rather than assuming. This is the highest-uncertainty area of the grammar.
Variadic must be last — confirm who enforces it. If the parser doesn't reject {a*} {b}, your code will produce a nonsensical input definition. Add your own assertion; it's three lines.
Docs are French-only and tutorial-shaped. Method names, exception types and edge cases are only discoverable in source. Budget an hour to read src/ end to end — it is a small package, and you will otherwise be guessing at return types. Keep your own English notes; the next developer on the team will thank you.
Pin and insulate. With 0 stars, 0 dependents, 0 score and no docs/ beyond the README, there is no community to answer questions and no track record of maintenance. Lock an exact version, commit your composer.lock, and keep the adapter layer thin enough that replacement is a day's work.
Debugging output. If SignatureStructureVO and the collections implement JsonSerializable or expose toArray(), dump those rather than var_dump on the VO graph — typed collections are noisy when printed raw and the recursive VO structure is easy to misread. If there's no serialisation method, write a tiny dump() helper in your test suite as the first thing you do; you will use it constantly.
Extension points to inspect. Custom-tag registration (there is likely a registry or a config array you must populate before parsing), collection interfaces (implement them if you need your own filtered views rather than materialising arrays), and VO construction (if the VOs are final readonly, plan for mapping rather than subclassing).
Watch the PHP floor. Enums require 8.1; if any downstream consumer still targets 8.0 you cannot share the VOs across that boundary. Your adapter layer solves this too — expose arrays or your own DTOs at package edges rather than the raw VOs.
How can I help you explore Laravel packages today?