-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture Overview
PHP Upgrade Preflight is a read-only analysis system.
It answers planning questions about a requested PHP or Composer upgrade.
It does not edit the analyzed project.
It does not run the application.
It does not prove runtime compatibility.
Junior developers can use this page to find where a behavior lives.
Technical managers can use it to understand system boundaries, evidence quality, and operational risk.
For a package-by-package index, see Package Map.
For a class lookup, see Core Service Reference.
The repository contains three production packages and two fixture packages.
| Package | Architectural role |
|---|---|
php-upgrade-preflight/core |
Framework-neutral analysis engine and report model |
php-upgrade-preflight/cli |
Generic command-line boundary and adapter discovery |
php-upgrade-preflight/laravel |
Laravel knowledge, source visitors, stages, and Artisan integration |
php-upgrade-preflight/test-adapter |
Current third-party adapter contract fixture |
php-upgrade-preflight/legacy-test-adapter |
Older adapter capability fixture |
Dependency flow points toward Core.
Core never imports Laravel classes.
Adapters import Core interfaces and models.
The following Mermaid diagram uses the real production class names.
flowchart TD
User[Developer or CI] --> App[Cli\\Application]
App --> CLI[Cli\\AnalyzeCommand]
App --> Wizard[Cli\\WizardCommand]
Wizard --> Lookup[Core\\Composer\\ComposerPackageMetadataLookup]
Wizard --> CLI
Artisan[Laravel Artisan] --> LAC[Laravel\\Commands\\AnalyzeUpgradeCommand]
CLI --> Parser[Cli\\CommandLineParser]
Parser --> Request[Core\\Model\\UpgradeRequest]
CLI --> Registry[Cli\\FrameworkIntegrationRegistry]
Registry --> Manifest[Cli\\AdapterManifestReader]
Registry --> Factory[Cli\\DefaultAnalyzerFactory]
LAC --> Request
Factory --> Analyzer[Core\\Analysis\\DefaultUpgradeAnalyzer]
Request --> Analyzer
Analyzer -. observational events .-> Progress[Core\\Progress\\AnalysisProgressReporter]
Progress --> CliProgress[Cli\\TerminalAnalysisProgressReporter]
Progress --> ArtisanProgress[Laravel\\Console\\ArtisanAnalysisProgressReporter]
Analyzer --> State[Core\\Composer\\ProjectStateBuilder]
State --> Platform[Core\\Model\\TargetPlatform]
Analyzer --> Engine[Core\\Analysis\\FrameworkRuleEngine]
Engine --> Laravel[Laravel\\LaravelFrameworkIntegration]
Analyzer --> Selector[Core\\Analysis\\ScenarioSelector]
Selector --> Runner[Core\\Composer\\ComposerScenarioRunner]
Runner --> Workspace[Core\\Composer\\ScenarioWorkspacePreparer]
Runner --> Results[Core\\Model\\ScenarioResult]
Results --> Diff[Core\\Analysis\\LockDiffBuilder]
Results --> Blockers[Core\\Analysis\\BlockerGrouper]
Analyzer --> Staged[Core\\Analysis\\StagedUpgradeOrchestrator]
Staged --> StageExec[Core\\Analysis\\StageExecutor]
Analyzer --> Scanner[Core\\Source\\SourceUsageScanner]
Scanner --> Ownership[Core\\Source\\AutoloadOwnershipIndexBuilder]
Ownership --> Impact[Core\\Analysis\\SourceImpactBuilder]
Engine --> Findings[Core\\Model\\CompatibilityFinding]
Findings --> Impact
Diff --> Estimate[Core\\Analysis\\RiskAndEffortEstimator]
Blockers --> Estimate
Impact --> Estimate
Staged --> Estimate
Estimate --> Assembler[Core\\Analysis\\ReportAssembler]
Assembler --> Report[Core\\Model\\UpgradeReport]
Report --> Resolver[Core\\Reporting\\ReportWriterResolver]
Resolver --> JSON[Core\\Reporting\\JsonReportWriter]
Resolver --> Markdown[Core\\Reporting\\MarkdownReportWriter]
JSON --> Output[stdout or ReportFileWriter]
Markdown --> Output
The diagram is a dependency flow, not a promise that every optional branch runs.
For example, no active stage provider means staged analysis is skipped.
There are three user-facing command flows over two executables.
The generic executable offers automation-safe upgrade-intel analyze and terminal-only upgrade-intel wizard flows. Cli\Application dispatches them. Cli\AnalyzeCommand owns explicit option parsing and delivery; Cli\WizardCommand collects choices, validates optional package metadata, prints the equivalent explicit command, and delegates back to the same analyzer command.
Its controller is Cli\AnalyzeCommand.
The Laravel command is php artisan upgrade:analyze.
Its controller is Laravel\Commands\AnalyzeUpgradeCommand.
The standalone analysis and Artisan controllers perform the same broad work:
- Parse command input.
- Construct validated model objects.
- Delegate to
UpgradeAnalyzer. - Select a report writer.
- Print or write the rendered report.
They do not parse Composer conflicts.
They do not calculate risk.
They do not contain Laravel transition rules.
This keeps presentation concerns outside the analysis engine.
Terminal progress follows the same boundary. Core emits validated observational events through AnalysisProgressReporter; CLI and Laravel render them to terminal-attached stderr. Non-TTY execution stays silent, and progress reporter failures cannot affect the canonical report.
UpgradeRequest is the validated boundary between delivery and analysis.
It contains:
- project path;
- package targets;
- current PHP evidence;
- target PHP;
- source paths;
- requested frameworks;
- report format and output path;
- debug mode;
- extension assumptions;
- optional target-platform profile;
- Composer execution configuration.
UpgradeTargetSet normalizes package and PHP targets.
Duplicate package names must not contradict one another.
Target PHP from different inputs must agree exactly after normalization.
Source paths must resolve inside the analyzed project.
ProjectStateBuilder loads composer.json and composer.lock.
JsonFileReader provides strict JSON-object validation.
The result is a ProjectStateLoadResult.
Success contains a ProjectState.
Failure contains a typed exception and enough partial state to build a terminal report.
Input failure is modeled rather than confused with a solver conflict.
TargetPlatform::fromRequest() combines request evidence and project Composer metadata.
It can represent PHP, extensions, target profile packages, and provenance.
Platform provenance matters because host state and explicit target state are not interchangeable.
An explicit extension assumption is evidence supplied by the caller.
It is not proof obtained by booting the target environment.
The generic CLI discovers adapter classes from installed Composer manifests.
AdapterManifestReader reads:
{
"extra": {
"php-upgrade-preflight": {
"framework-adapters": ["Vendor\\Adapter\\Integration"]
}
}
}FrameworkIntegrationRegistry instantiates valid no-required-argument integrations.
It rejects duplicate classes and duplicate case-insensitive integration names.
FrameworkRuleEngine chooses active integrations.
An explicit --framework=name activates only requested available names.
Without explicit names, project detection decides activation.
ScenarioSelector creates a bounded scenario matrix.
The usual scenarios are:
- baseline validation;
- exact target;
- target with all dependencies;
- minimal changes.
PHP plus package requests may add platform-only and staged-target diagnostic scenarios.
ComposerScenarioRunner executes each selected scenario.
It uses TemporaryWorkspaceManager and ScenarioWorkspacePreparer.
The analyzed tree remains untouched.
Each scenario returns a ScenarioResult.
A result can contain:
- exit code;
- bounded stdout and stderr;
- duration;
- Composer version;
- failure classification;
- diagnostics;
- candidate lock state;
- candidate lock evidence;
- debug workspace path.
Composer is an external process.
Its environment, version, repositories, cache, credentials, and network availability can affect results.
The report records relevant execution configuration and uncertainty.
Compatible mode uses normal Composer access with non-interactive/no-audit settings.
Restricted mode creates analyzer-owned Composer state and disables normal credential, proxy, prompt, and network paths where supported.
Restricted mode is not an operating-system sandbox.
LockDiffBuilder compares the baseline lock with the selected successful candidate.
BlockerGrouper converts reliable solver evidence into structured blockers.
ComposerBlockerParser helps extract package and constraint relations from Composer text.
AbandonedPackageDetector adds lock-metadata abandonment evidence.
The direct resolution can be:
-
feasible; -
feasible_with_changes; -
blocked; -
unknown.
Unknown is not a softer spelling of blocked.
It means reliable solver evidence was not available.
The analyzer considers successful target-feasibility scenarios with readable candidate locks.
It first prefers fewer package changes.
For equal change counts, strategy rank is:
- exact target;
- minimal changes;
- with all dependencies.
Original scenario order is the final tie-breaker.
This candidate drives the direct lock diff.
Staged analysis is separate from direct resolution.
StagedUpgradeOrchestrator asks active FrameworkStageTargetProvider integrations for plans.
StagePlanResolver validates and selects a usable provider plan.
StageAttemptPlanner creates bounded attempts.
StageExecutor runs them.
StageBlockerRegistry tracks conflict lifecycle.
The selected candidate state of one successful stage becomes input to the next stage.
The application source tree is still not rewritten.
SourceUsageScanner parses selected PHP files using nikic/php-parser.
Core visitors collect common declarations and usages.
Adapters may add visitors through SourceUsageVisitorProvider.
The first product is source inventory.
Inventory is not automatically actionable impact.
AutoloadOwnershipIndexBuilder uses root and locked-package autoload metadata.
SourceImpactBuilder correlates inventory with ownership, package changes, and framework findings.
This reduces false attribution caused by matching a symbol name alone.
Adapters supply maintained framework knowledge.
The Laravel adapter separates that knowledge into:
-
LaravelFrameworkDetector; -
LaravelRuleCatalog; -
LaravelRuleFactory; -
LaravelTransitionAssessor; -
LaravelStagePlanner; -
LaravelSourceUsageVisitor.
Core sees interfaces and model values, not Laravel implementation details.
See Laravel Package Internals.
RiskAndEffortEstimator consumes structured evidence.
Risk is a deterministic planning summary.
It is not a probability.
Effort is a range with confidence, components, and assumptions.
It is not a quotation or deadline.
StageAssessmentBuilder adds stage-level source impact, risk, effort, tests, and actions.
ReportAssembler is the single normal construction point for UpgradeReport.
ReportSectionBuilder creates normalized derived sections.
The report constructor validates evidence references and other invariants.
JSON is canonical.
Markdown is a projection of the same report object.
Writers do not rerun analysis.
| Boundary | Untrusted or variable input | Main controls |
|---|---|---|
| CLI | User-provided strings and paths | Parser and model validation |
| Project files | Composer JSON and PHP source | Strict readers, AST parser, contained uncertainties |
| Composer | External executable and output | Timeouts, safe command representation, classification, redaction |
| Adapters | Third-party classes and rules | Manifest validation, interface checks, exception containment |
| Reports | Paths, diagnostics, evidence context | Path markers, structured redaction, bounded excerpts |
Not every failure ends the process.
A broken optional adapter manifest is skipped and diagnosed.
A throwing adapter rule becomes evidence-backed uncertainty while remaining rules continue.
A failing stage provider can be contained as unavailable staged analysis.
A PHP parse failure becomes source uncertainty.
An invalid command invocation exits before analysis.
An unreadable project input produces a terminal input-failure report when possible.
- The analyzed project is not mutated.
- Candidate manifests and locks belong to analyzer workspaces.
- JSON is the canonical contract.
- Every registered evidence item must support a report claim.
- Framework-specific knowledge stays outside Core.
- Operational failure is not reported as a solver blocker.
- Unknown remains a first-class outcome.
- Stable ordering is deliberate, not accidental.
- Sensitive output is redacted before sharing.
- Debug mode weakens path hiding by explicit user choice.
vendor/bin/upgrade-intel analyze \
--path=./shop \
--target=laravel/framework:^13.0 \
--from-php=8.2 \
--target-php=8.3 \
--framework=laravel \
--format=jsonThe CLI constructs the request.
The registry supplies the Laravel integration.
Core loads Composer state and builds the target platform.
Direct scenarios test final-target feasibility.
The Laravel adapter can provide adjacent stage targets.
Core scans the original source snapshot.
The report combines direct, staged, source, and guidance evidence without conflating them.
- Core Package Guide for contributor workflows.
- Core Analysis Pipeline for a compact execution narrative.
- Core Service Reference for class ownership.
- Determinism and Evidence for stability and traceability.
- CLI Package Internals for the generic command boundary.
PHP Upgrade Preflight — common product and monorepo Wiki · Common repository
- Home
- Key Concepts
- Package Map
- Class and Service Index
- Getting Started
- CLI Reference
- Artisan Command
- Reading the Report
- Safety and Trust Boundaries
- Troubleshooting and FAQ
- Architecture Overview
- Core Package Guide
- Core Analysis Pipeline
- Core Service Reference
- Determinism and Evidence
- Report Schema
- CLI Package Internals
- Laravel Package Internals
- Test Adapters
- Writing a Framework Adapter
- Laravel Adapter Internals
- Contributing
- Roadmap and Status
- Tools Reference
- Quality and Release Tooling
- Release Wiki Strategy