Skip to content

Architecture

  1. Discover files. Framework defaults and configuration choose the paths; excluded directories are pruned.
  2. Parse PHP. Source becomes an abstract syntax tree. No analyzed PHP file is executed by the standalone scanner.
  3. Collect a semantic index. Track classes, properties, writes and supported container bindings across files.
  4. Evaluate rules. Local observations and project-level relationships produce findings.
  5. Apply reporting policy. Suppressions, baselines and the failure threshold determine what is displayed and the exit status.
  • Every file is parsed exactly once. A single AST walk drives semantic indexing, suppression collection and rule dispatch, so adding a rule costs no extra pass.
  • Excluded directories are pruned during traversal rather than filtered afterwards, which is what keeps vendor/ from ever being walked.
  • Files are read and released one at a time, so peak memory is bounded by the largest single file plus the semantic index, not by the size of the project.
  • Rules that need cross-file knowledge emit their findings after the walk, from the shared index — never by re-parsing.

Parallel scanning is not implemented in v1. The architecture allows it: file analysis is independent apart from the shared index.

worker-safety test is a second pipeline, deliberately kept separate from the static one rather than forced through AnalysisRequest — which carries files, rules and binding collectors that mean nothing to a black-box HTTP client.

  1. Load a scenario. YAML, validated as strictly as worker-safety.yaml: unknown keys are errors, and parsing allows no objects or custom tags.
  2. Send each step in order, over one client, against an already-running application. Order is the whole point — step N runs after step N-1 against the same process.
  3. Check the response. Status, JSON dot-paths, headers and body substrings, each returning a result rather than throwing, so one run reports every failure instead of stopping at the first.
  4. Report. Console or JSON, with a schema versioned independently of the scan report.

Two boundaries hold this together:

  • Nothing of the target application is loaded. No bootstrap, no autoload, no container. test is an HTTP client, the same way scan is a parser — which is what lets the scanner keep its promise never to execute what it analyzes.
  • A request that did not complete is not a result. A refused connection, a stalled body or a response shorter than its Content-Length is a transport error (exit 3), never a passed or failed expectation. An assertion can only speak about a response that actually arrived.

See Runtime replay for usage.

  • Symfony-specific rules for container and kernel state.
  • Inherited and trait-provided static property analysis.
  • Parallel file analysis.
  • More --fix-style guidance, but not automatic code modification.