class

Analyzer::Php::PhpEngine

Inherits FileScanEngine < Analyzer < FileHelper < Reference < Object

Constants

CALL_ARGS_TAIL = (/(?:[^)'"]++|'(?:[^'\\]|\\.)*+'|"(?:[^"\\]|\\.)*+")*+/).source

Regex fragment for the rest of a call's argument list, up to the ) that closes it. Quoted strings are skipped whole so a ) in one ('placeholder' => '(:num)') does not end the list; nested calls are not balanced, so the text stops at their first bare ). Possessive throughout, so it never backtracks.

ES_PLURAL_RE = /(?:alias|[^aou]us|t[lm]as|gas|ris|ss|x)es\z/i

Naive English singular of a resource name (photos -> photo, categories -> category), for nested-resource parameter names.

ILLUMINATE_ACCESSORS = "input|post|get|query|header|cookie|file|hasFile|has|filled|string|integer|boolean|float|date|enum|array"

Illuminate\Http\Request reads (Laravel, Lumen): $request->input('x'), request()->query('x'), $request->header('X-Token'), ... Capture 1 is the accessor, capture 2 the input name.

ILLUMINATE_CONST_READ_RE = /(?:\$request|\brequest\(\s*\))\s*->\s*(#{ILLUMINATE_ACCESSORS})\s*\(\s*(?:self|static)::([A-Z_][A-Z0-9_]*)\s*[,)]/

$request->has(self::ENTITY_KEY): the input named by a class constant.

ILLUMINATE_DYNAMIC_READ_RE = /\$request\s*->\s*([a-z_]\w*)\b(?!\s*(?:\(|->|=(?![=>])))/

A dynamic property read, $request->email: Request::__get returns that input. Not a call, not a chain, not an assignment, and not one of the request's own public properties ($request->query->get(...)).

ILLUMINATE_FOREACH_RE = /\bforeach\s*\(\s*(\$\w+|\[[^\[\]]*\])\s+as\s+(?:\$\w+\s*=>\s*)?\$(\w+)\s*\)\s*\{/

foreach ($fields as $field) / foreach (['a', 'b'] as $k => $field).

ILLUMINATE_MULTI_READ_RE = /(?:\$request|\brequest\(\s*\))\s*->\s*only\s*\(\s*(\[?\s*['"][^'"]+['"](?:\s*,\s*['"][^'"]+['"])*\s*,?\s*\]?)\s*\)/

$request->only('a', 'b') / ->only(['a', 'b']): several literal input names per call, as arguments or one array. Capture 1 the list. (except names the inputs the code drops, often _token.)

ILLUMINATE_PAGINATE_RE = /->\s*(paginate|simplePaginate|cursorPaginate)\s*\(/

->paginate(...) / simplePaginate read the current page from the request's page input, cursorPaginate its cursor.

ILLUMINATE_READ_TYPES = {"get" => "query", "query" => "query", "header" => "header", "cookie" => "cookie"}
ILLUMINATE_REQUEST_PROPERTIES = Set {"attributes", "request", "query", "server", "files", "cookies", "headers", "content", "json", "session"}
ILLUMINATE_REQUEST_READ_RE = /(?:\$request|\brequest\(\s*\))\s*->\s*(#{ILLUMINATE_ACCESSORS})\s*\(\s*['"]([^'"]+)['"]/
ILLUMINATE_VALIDATE_RE = /(?:->\s*validate(?:WithBag)?|\bValidator::make)\s*\(/

Calls taking a validation rules array: $request->validate([...]), $this->validate($request, [...]), Validator::make($data, [...]).

INLINE_CLOSURE_HEAD_RE = /\G(?:static\s+)?function\s*\([^)]*\)\s*(?:use\s*\([^)]*\)\s*)?(?::\s*[^{=]+)?\{/i

Body of an inline function (...) use (...) { ... } closure handler starting at pos: {body, position after the closure, body start line}, or {nil, pos, nil} when no closure starts there.

INTERPOLATION_BRACE_DOLLAR_RE = /\{\$([A-Za-z_]\w*)\}/
INTERPOLATION_DOLLAR_BRACE_RE = /\$\{([A-Za-z_]\w*)\}/

PHP double-quoted strings interpolate $var, {$var}, and ${var}. The route extractor captures the literal characters between the quotes, so "/api/{$VERSION}/items" came out as /api/{$VERSION}/items with the $ leaking into the URL. Rewrite each shape to {name} so the path-parameter extractor picks it up and the URL template reads cleanly. Same posture as the Python f-string and Ruby #{} fixes.

INTERPOLATION_DOLLAR_RE = /\$([A-Za-z_]\w*)/
LOCAL_VAR_RE = /\A\$(\w+)\z/
RESOURCE_FILTER_RE = /['"](only|except)['"]\s*=>\s*(?:['"]([^'"]*)['"]|(?:\[|array\s*\()([^\])]*))/i
RULE_CONFIRMED_RE = /['"]([\w-]+)['"]\s*=>\s*(?:['"][^'"]*\bconfirmed\b|\[[^\]]*['"]confirmed['"])/

'password' => 'required|confirmed': Laravel's confirmed rule reads a second input, password_confirmation.

RULE_KEY_RE = /['"]([\w-]+)(?:\.[^'"]*)?['"]\s*=>/

A rules-array key: 'title' =>, 'author.name' => (input author).

RULES_SUBSCRIPT_RE = /\A(.*?)\[\s*['"]([\w-]+)['"]\s*\]\z/m
STRING_LIST_RE = /\A\s*(?:(['"])[\w.-]+\1\s*(?:,\s*|\z))+\z/
TEST_DIR_RE = /\/(?:Tests|tests|Fixtures|workbench|legacy_tests)\/|\/Test\/(?:Unit|Integration|Mftf|Integrity|Legacy|Fixture|_files)\/|\/test\/phpunit\/|\/config\/routes\/test\//
THIS_MEMBER_RE = /\A\$this->(\w+)\s*(\(\s*\))?\z/

Class methods

test_path?(relative_path : String, root_docs : Bool = true) : Bool

Standard PHP test-source conventions:

  • /Tests/ — PSR-4 / Symfony convention (src/Symfony/Bundle/FrameworkBundle/Tests/...)
  • /tests/ — Laravel / CakePHP / PHPUnit default
  • /Test/Unit/, /Test/Mftf/, … — Magento's per-module test trees (Test/Unit/_files/webapi.xml), /test/phpunit/ — Dolibarr, and /config/routes/test/ — Symfony's test-env routes. A bare Test/ or test/ is not a test dir: it is the Test namespace of app/Http/Controllers/Test/ (Laravel), src/Controller/Test/ (Symfony), a ThinkPHP application/test/ or Yii modules/test/ module, all real routes.
  • /Fixtures/, /legacy_tests/ — test data and Dusk suites
  • /workbench/ — Orchestra Testbench skeleton app
  • /docs/ at the scan base — runnable doc snippets (API Platform docs/guides); anchored, because Folio mounts resources/views/docs as a real route. root_docs: false drops it for the plain PHP analyzer, which serves a docs/ section.
  • *Test.php filename — PHPUnit suffix convention
  • *Tests.php filename — pluralized variant (rare)

symfony/symfony's own repo accounts for ~63 phantom endpoints under src/Symfony/Bundle/FrameworkBundle/Tests/.... The conventions are unambiguous — production routing never adopts any of them.

Takes the scan-base-relative path (Analyzer#base_relative_path), never the absolute one. The conventions describe a location inside the project, so matching the absolute path handed the decision to whatever directory the checkout happened to live in: the same tree scanned from ~/work/tests/myapp reported 0 endpoints instead of 235.

Source