Surfaces the command-line attack surface of PHP programs as cli://
endpoints: one endpoint per (sub)command with named options
(param_type "flag"), positional arguments ("argument") and consumed
environment variables ("env"). Covers Symfony Console, Laravel Artisan
($signature), Robo (@command docblocks) and WP-CLI, plus builtin
getopt / $argv / $_ENV / getenv.
Line-scan analyzer (Go/Ruby/Rust CLI house style) merging endpoints by
URL. Subclasses Analyzer directly (PhpEngine is abstract) and reuses
PhpEngine.test_path? to skip tests.
Constants
ADD_ARGUMENT = /->\s*addArgument\s*\(\s*['"]([^'"]+)['"]/
ADD_OPTION = /->\s*addOption\s*\(\s*['"]([^'"]+)['"]/
ARGV_INDEX = /\$argv\s*\[\s*(\d+)\s*\]/
ARTISAN_SIGNATURE = /protected\s+(?:static\s+)?\$signature\s*=\s*(['"])((?:\\.|(?!\1).)*)\1/
Laravel Artisan: protected $signature = '...'. The value is the
Symfony-Console-flavored mini-grammar Artisan parses itself:
name {arg} {arg? : desc} {arg=default} {--flag} {--flag=default : desc}.
ARTISAN_TOKEN = /\{\s*([^{}]*?)\s*\}/
AS_COMMAND = /#\[\s*AsCommand\s*\(\s*name\s*:\s*['"]([^'"]+)['"]/
CLI_HINT_RE =
Regex.union("
Symfony\\
Component\\
Console", /extends\s+
Command/, "
AsCommand", "getopt", "$argv", "
League\\
CLImate", "
Minicli\\", "$signature", "
Robo\\
Tasks", "
WP_CLI")
Cheap file-level reject before the precise multi-regex evidence
check. Every accepted CLI shape contains at least one of these
literals.
DEFAULT_NAME = /protected\s+(?:static\s+)?\$defaultName\s*=\s*['"]([^'"]+)['"]/
ENV_BRACKET = /\$_
ENV\s*\[\s*['"]([^'"]+)['"]\s*\]/
GET_ARGUMENT = /\$input->getArgument\s*\(\s*['"]([^'"]+)['"]/
GET_OPTION = /\$input->getOption\s*\(\s*['"]([^'"]+)['"]/
GETENV = /\bgetenv\s*\(\s*['"]([^'"]+)['"]/
GETOPT = /\bgetopt\s*\(\s*(['"])([^'"]*)\1\s*(?:,\s*\[([^\]]*)\])?/
ROBO_COMMAND_TAG = /@command\s+(\S+)/
ROBO_FUNCTION_LINE = /\bfunction\s+\w+\s*\(([^)]*)\)/
ROBO_MARKER = /Robo\\Tasks\b/
Robo (consolidation/Robo task runner): a @command <name> docblock
tag binds to the very next method signature only — never sticky.
SET_NAME = /\$this->setName\s*\(\s*['"]([^'"]+)['"]/
WEB_FRAMEWORK_RE = /\buse\s+(?:Illuminate\\(?:Foundation|Http|Routing)|Symfony\\Bundle\\FrameworkBundle|Symfony\\Component\\HttpFoundation|Symfony\\Component\\HttpKernel|Slim\\(?:App|Factory)|Laminas\\(?:Mvc|Mezzio)|Mezzio\\|Cake\\(?:Routing|Http)|Hyperf\\HttpServer)\b|extends\s+AbstractController\b/
WP_ADD_COMMAND = /WP_CLI::add_command\s*\(\s*(['"])((?:\\.|(?!\1).)*)\1\s*,\s*(?:new\s+)?['"]?(\w+)/
WP_ARGS_INDEX = /\$args\s*\[\s*(\d+)\s*\]/
WP_ASSOC_ARG_BRACKET = /\$assoc_args\s*\[\s*['"]([^'"]+)['"]\s*\]/
WP_CLASS_LINE = /\bclass\s+(\w+)/
WP_FLAG_VALUE = /get_flag_value\s*\(\s*\$assoc_args\s*,\s*['"]([^'"]+)['"]/
WP_MARKER = /WP_CLI(?:_Command)?\b/
WP-CLI: WP_CLI::add_command('foo bar', 'Foo_Bar_Command') registers
a class as a command. Only the method whose OWN signature is the
conventional ($args, $assoc_args) callback is scanned for params —
never the whole class body.
WP_METHOD_LINE = /\bfunction\s+\w+\s*\((?=[^)]*\$args\b)(?=[^)]*\$assoc_args\b)[^)]*\)/
Instance methods
tech
Instance-side view of the same declaration. The per-file rescues live on
this base class, which has no way to name the analyzer that is running
inside them, so a skipped file could not be attributed to a tech.
Deriving it from analyzer_for keeps the name written exactly once.
Source