class Analyzer::Php::Cli

Overview

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.

Defined in:

analyzer/analyzers/php/cli.cr

Constant Summary

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 Method Summary

Instance methods inherited from class Analyzer

analyze analyze, base_path : String base_path, base_paths : Array(String) base_paths, callees_needed? : Bool callees_needed?, http_header_name(name : String) : String | Nil http_header_name, line_number_for_index(content : String, char_index : Int32) : Int32 line_number_for_index, logger : NoirLogger logger, parallel_analyze(files : Array(String), &block : String -> Nil) parallel_analyze, read_file_content(path : String) : String read_file_content, result : Array(Endpoint) result, unique_params(params : Array(Param)) : Array(Param) unique_params, url : String url, web_root_path(path : String, markers : Array(String)) : String web_root_path

Constructor methods inherited from class Analyzer

new(options : Hash(String, YAML::Any)) new

Instance methods inherited from module FileHelper

all_files : Array(String) all_files, get_files_by_extension(extension : String) : Array(String) get_files_by_extension, get_files_by_extensions(extensions : Array(String)) : Array(String) get_files_by_extensions, get_files_by_prefix(prefix : String) : Array(String) get_files_by_prefix, get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String) get_files_by_prefix_and_extension, get_public_dir_files(base_path : String, folder : String) : Array(String) get_public_dir_files, get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String) get_public_files

Instance Method Detail

def analyze #

[View source]