-
Notifications
You must be signed in to change notification settings - Fork 0
Core Analysis Pipeline
This page explains the services in php-upgrade-preflight/core in the order a request uses them. It is useful when debugging a result, adding a framework adapter, or estimating the scope of a change.
UpgradeAnalyzer exposes one operation: analyze an UpgradeRequest and return an UpgradeReport. DefaultUpgradeAnalyzer is the production implementation.
Conceptually:
$request = new UpgradeRequest(
projectPath: '/work/shop',
targets: [new UpgradeTarget('laravel/framework', '^12.0')],
fromPhp: '8.2',
targetPhp: '8.3',
sourcePaths: [],
frameworks: ['laravel'],
format: 'json',
outputPath: null,
debug: false
);
$report = $analyzer->analyzeUpgrade($request);In normal application code, prefer the generic CLI or Laravel command; the example shows the object boundary, not a complete bootstrap.
| Order | Service | Input | Output or decision |
|---|---|---|---|
| 1 | ProjectStateBuilder |
Project directory | Parsed Composer manifest and lock state, or a contained input failure |
| 2 |
TargetPlatform::fromRequest and TargetNormalizer
|
Request plus project metadata | Normalized target packages, PHP, extensions, and platform provenance |
| 3 | FrameworkRuleEngine |
Installed adapters, project, request | Active integrations, source paths, and package-family classifiers |
| 4 | ScenarioSelector |
Normalized targets and current PHP evidence | Ordered, deduplicated Composer scenarios |
| 5 | ComposerScenarioRunner |
One scenario | Exit status, bounded output, diagnostics, Composer version, and optional candidate lock |
| 6 |
LockDiffBuilder and BlockerGrouper
|
Scenario results | Candidate package changes or structured blockers |
| 7 | StagedUpgradeOrchestrator |
Active stage providers | Adjacent stage attempts, selected candidate states, and blocker lifecycle |
| 8 | SourceUsageScanner |
Original project source | AST-derived source inventory and uncertainties |
| 9 | Framework rules | Inventory plus transition guidance | Compatibility findings |
| 10 | Ownership and impact builders | Composer autoload metadata, inventory, lock changes | Actionable source impact |
| 11 | RiskAndEffortEstimator |
Blockers, changes, findings, impact, stages | Aggregate risk and effort ranges |
| 12 | ReportAssembler |
All accumulated evidence | Canonical UpgradeReport
|
DefaultUpgradeAnalyzer can receive an AnalysisProgressReporter. It emits analysis start/completion/failure, phase start/completion, and Composer scenario start/completion events around the same pipeline. The stable phases are project loading, Composer feasibility, staged resolution, source scan, framework evaluation, and report assembly.
Progress is deliberately outside the report model. A reporter exception is caught and ignored; it cannot cancel, retry, reorder, or reinterpret work. The default reporter is NoOpAnalysisProgressReporter. The standalone CLI and Laravel package provide their own TTY-aware stderr renderers, while non-terminal and embedded consumers retain the original silent behavior.
ProjectStateBuilder reads composer.json and composer.lock through JsonFileReader. Missing or invalid input does not have to crash the whole command. DefaultUpgradeAnalyzer can return an input-failure report with a project-input scenario.
Examples of modeled outcomes include:
-
invalid_jsonfor an invalid Composer JSON document; -
lockfile_missingwhencomposer.lockis missing; -
workspace_failurefor other project-state loading failures.
Paths in failure messages pass through PathExposurePolicy before entering a shareable report.
For a package target, ScenarioSelector starts with these scenarios:
| Scenario | Purpose | Target feasibility? |
|---|---|---|
baseline-validation |
Check whether the copied current manifest and lock are internally usable | No |
exact-target |
Try the requested target without --with-all-dependencies
|
Yes |
target-with-all-dependencies |
Allow dependency movement around target packages | Yes |
minimal-changes |
Combine dependency movement with Composer minimal-change behavior | Yes |
When both package targets and a target PHP are present, Core can also add:
-
target-platform-only, a diagnostic partial probe that does not determine the final target; -
staged-targets, which tries package targets against the current PHP. This requires--from-phporconfig.platform.php; otherwise the report records an uncertainty.
Equivalent executions are deduplicated. For example, a PHP-only request does not create several scenarios that would run the same Composer operation.
ScenarioWorkspacePreparer writes copied composer.json and composer.lock data into an analyzer-owned workspace. Target constraints and simulated platform values are applied to that copy.
Example transformation inside the temporary copy:
{
"require": {
"laravel/framework": "^12.0"
},
"config": {
"platform": {
"php": "8.3"
}
}
}If a targeted package originally lives only in require-dev, its target constraint is updated there. Relative Composer path and artifact repository URLs are made absolute relative to the analyzed project so the copied manifest retains their meaning.
The analyzer does not write this transformed manifest back to the project.
The default compatible mode inherits the normal Composer environment, apart from non-interactive/no-audit settings. Restricted mode creates isolated Composer home/cache/XDG directories inside the temporary workspace, clears proxy and prompt variables, sets empty auth, and requests Composer network disablement.
Restricted mode reduces environmental reach; it does not turn Composer into a security sandbox. See the safety documentation before running against an untrusted project.
Several scenarios may succeed with candidate locks. DefaultUpgradeAnalyzer selects the candidate with the fewest package changes. Ties prefer the exact-target strategy, then minimal-changes, then with-all-dependencies, with original scenario order as the final tie-breaker.
This selected candidate drives the direct LockDiff. If no successful target-feasibility scenario yields a readable candidate lock, Core does not invent package versions and returns an empty candidate diff.
BlockerGrouper turns solver evidence into structured blockers. It uses scenario output, Composer diagnostics, the relevant lock, requested constraints, and target platform.
The important distinction is:
Composer process failed
|
+-- solver evidence is reliable --> structured blocker(s)
|
+-- execution/evidence failed ----> unknown + uncertainty
A timeout, unavailable Composer executable, or unreadable candidate lock is not automatically a dependency conflict.
Adapters may implement FrameworkStageTargetProvider. StagedUpgradeOrchestrator asks eligible active integrations for a plan and executes a contiguous chain.
For Laravel 10 to 13, an adapter may produce:
laravel-10-to-11 -> laravel-11-to-12 -> laravel-12-to-13
Each successful stage supplies the candidate ProjectState used by the next stage. A failed or unknown stage stops later execution; later planned stages are reported as skipped rather than silently omitted.
Staged execution uses bounded timeouts from StagedAnalysisPolicy. The blocker registry tracks whether blockers are detected, persist, resolve, or are superseded across attempts.
SourceUsageScanner parses PHP through nikic/php-parser. Framework adapters can provide extra AST visitors through SourceUsageVisitorProvider.
The scan produces an inventory first. AutoloadOwnershipIndexBuilder then maps relevant declarations and symbols to Composer packages using autoload metadata. SourceImpactBuilder correlates usages with framework findings or actual candidate package changes.
Example:
Inventory: app/Service.php uses Vendor\Package\Client
Ownership: Vendor\Package\Client belongs to vendor/package
Lock diff: vendor/package changes in the selected candidate
Result: actionable source-impact finding
Without ownership or transition relevance, a usage can remain inventory without becoming an impact claim.
The original source snapshot is scanned even when staged Composer candidates exist. A staged candidate is dependency evidence, not a rewritten application tree.
| Interface | What an adapter supplies |
|---|---|
FrameworkIntegration |
Name, project detection, compatibility rules, default source paths |
FrameworkTransitionProvider |
Evidence-backed hop guidance |
FrameworkStageTargetProvider |
Exact adjacent stage targets and optional remediation candidates |
PackageFamilyClassifier |
Family labels for package changes, such as Laravel or Symfony |
SourceUsageVisitorProvider |
Framework-specific AST collectors |
HopAwareCompatibilityRule |
A rule that can evaluate one specific transition hop |
Core checks optional interfaces at runtime. An older adapter implementing only the original interfaces can still provide useful detection and guidance; it simply cannot provide newer capabilities such as staged targets.
ReportAssembler creates a complete UpgradeReport. Rendering happens afterward:
| Service | Role |
|---|---|
JsonReportWriter |
Canonical machine-readable JSON |
MarkdownReportWriter |
Human-readable projection |
ReportWriterResolver |
Chooses a writer from the requested format |
ReportFileWriter |
Validates and writes an output destination |
Report writers do not analyze the project and must not introduce independent conclusions.
| Desired change | Likely home |
|---|---|
| Add a new direct Composer scenario |
Analysis/ScenarioSelector plus runner/report tests |
| Improve solver conflict parsing |
Analysis/ComposerBlockerParser or BlockerGrouper
|
| Change temporary manifest behavior | Composer/ScenarioWorkspacePreparer |
| Add a report field | Model, assembler, both writers, schema, snapshots, and Wiki |
| Add framework-specific knowledge | An adapter package, not Core |
| Improve redaction |
Support/SensitiveOutputRedactor and path policy tests |
| Change staged attempt policy | Staged planner/executor services and budget tests |
| Add or rename a progress phase |
Progress/AnalysisPhase, analyzer event tests, and both console renderers |
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