migrate-php74-to-php80

changed sort() stability assumptions, and magic-method signature tightening — each behind existing tests, never as a blind Rector rewrite.

anyorgname/php-skills1 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI

Agent Skills format with YAML frontmatter. Claude Code reads it as-is.

---
name: "migrate-php74-to-php80"
description: "changed sort() stability assumptions, and magic-method signature tightening — each behind existing tests, never as a blind Rector rewrite."
license: "MIT"
---

<!-- generated: do not edit — source: knowledge/atoms/php/migrate-php74-to-php80/ -->
<!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 -->
<!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->

# Migrate PHP 7.4 to PHP 8.0

## When to use

- The user hits PHP 8.0 breaking changes (reserved match keyword, magic method signatures, removed functions, stricter comparisons) while upgrading from 7.4.
- The user wants to modernize a legacy PHP project and adopt PHP 8.0 syntax such as constructor promotion, match, and union types.
- The user wants to migrate or upgrade a codebase from PHP 7.4 to PHP 8.0.

## When NOT to use

- Do not use this skill when the codebase already runs on PHP 8.0 or later; use the target-version skill for new 8.1+ features instead.
- Do not use this skill for a greenfield project that has no PHP 7.4 code to carry forward.

## Core guidance

- **Fix semantic breaking changes by hand under test coverage** — Fix behavior-changing breaks by hand: the saner string-to-number comparison,
changed `sort()` stability assumptions, and magic-method signature tightening —
each behind existing tests, never as a blind Rector rewrite.
- **Automate mechanical rewrites with Rector, review each set** — Apply Rector's `PHP_80` set list for mechanical rewrites (null coalescing,
`match`, constructor promotion, `#[Attribute]`), committing one rule set at a
time so every change is reviewable in isolation.
- **Run static analysis on 7.4 before touching code** — Before changing anything, run PHPStan or Psalm against the PHP 7.4 codebase and
capture a clean baseline, then run Rector in `--dry-run` to inventory the diff.

## Decision Engine

> Walk this section before choosing an implementation path.

```text
Is the change a mechanical syntax rewrite, or does it alter runtime behavior?

├── Alters behavior: Is the affected code covered by tests?

│   ├── Covered: → Manual fix under tests — edit the semantics by hand and rely on the green suite to prove behavior.

│   └── Untested: → Staged upgrade — add characterization tests first, then fix, keeping both runtimes green in CI.

└── Mechanical rewrite: Does Rector's PHP_80 set list cover this rewrite?

    ├── No rule: → Manual fix — hand-edit the small set of files and add a Rector rule only if the pattern repeats.

    └── Rule exists: → Automate with Rector — apply the PHP_80 rule set, commit per set, and review the diff.

```

**Full engine:** [references/decision-engine.md](references/decision-engine.md) — Decision Tree, Priority Matrix, Decision Table.

## Workflows

### Workflow: Incremental Cutover

```text
Progress:
- [ ] Extend the CI matrix to run the suite on PHP 8.0 alongside 7.4, allowing the 8.0 job to
fail at first so the remaining breaks become visible
- [ ] Fix breaks one at a time — forward-compatible changes that pass on both runtimes — until
the PHP 8.0 CI job is green while 7.4 stays green
- [ ] Raise `require.php` and `config.platform.php` to `^8.0`, drop 7.4 from the matrix, and
deploy — the cut-over is now a tiny, low-risk change
```

1. Extend the CI matrix to run the suite on PHP 8.0 alongside 7.4, allowing the 8.0 job to
fail at first so the remaining breaks become visible.
   - Verify: CI shows a PHP 8.0 job whose failures list the outstanding breaking changes
2. Fix breaks one at a time — forward-compatible changes that pass on both runtimes — until
the PHP 8.0 CI job is green while 7.4 stays green.
   - Verify: Both the PHP 7.4 and PHP 8.0 CI jobs are green on the same commit
3. Raise `require.php` and `config.platform.php` to `^8.0`, drop 7.4 from the matrix, and
deploy — the cut-over is now a tiny, low-risk change.
   - Verify: Only PHP 8.0 remains in CI, it is green, and the deployed app runs on 8.0

### Workflow: Rector Automated Migration

```text
Progress:
- [ ] Add `rector/rector` as a dev dependency and create `rector.php` enabling the `PHP_80`
set list scoped to your source paths
- [ ] Run `vendor/bin/rector process --dry-run` and read the diff to separate safe mechanical
rewrites from any behavior-sensitive changes you will handle by hand
- [ ] Run `vendor/bin/rector process`, run the test suite, and commit the rewrite as its own
change so it can be reviewed and reverted independently
```

1. Add `rector/rector` as a dev dependency and create `rector.php` enabling the `PHP_80`
set list scoped to your source paths.
   - Verify: `vendor/bin/rector --version` runs and `rector.php` lists the PHP_80 set
2. Run `vendor/bin/rector process --dry-run` and read the diff to separate safe mechanical
rewrites from any behavior-sensitive changes you will handle by hand.
   - Verify: The dry-run output is reviewed and behavior-sensitive hunks are noted for manual work
3. Run `vendor/bin/rector process`, run the test suite, and commit the rewrite as its own
change so it can be reviewed and reverted independently.
   - Verify: The suite passes after the rewrite and the commit touches only the rewritten set

## Best practices

- Run `composer why-not php 8.0` and upgrade or replace every package that caps
`php` below 8.0 before raising the `platform` requirement in `composer.json`
- Add PHP 8.0 to the CI matrix alongside 7.4 and keep both green until cut-over, so
the suite proves the code runs on the old and new runtime at every commit
- Land the migration as many small merges to the main line rather than one giant
branch: fix breaks that are already 7.4-and-8.0 compatible first, then flip the
runtime as the last, smallest change

## Anti-patterns

| Anti-pattern | Why it fails | Do instead |
| --- | --- | --- |
| Big-bang migration on a long-lived branch | The branch rots against ongoing main development, the final merge is huge and unreviewable,<br>and any regression is nearly impossible to bisect. | Land forward-compatible fixes as small merges to main and make the runtime flip the last,<br>smallest commit. |
| Mixing feature refactors into the migration commits | Blending unrelated changes makes review and rollback impossible to reason about, so a<br>migration regression cannot be separated from a feature bug. | Keep migration commits mechanical and reversible; do feature work and large refactors in<br>separate, independently reviewed changes. |
| Silencing new PHP 8.0 errors instead of fixing them | Suppression hides real bugs the stricter runtime just exposed, deferring failures to<br>production where they are far more expensive. | Fix each promoted notice at its source — guard undefined keys, correct types — and keep<br>`error_reporting(E_ALL)` on in CI. |

See [references/anti-patterns.md](references/anti-patterns.md).

## Debugging

1. **Saner comparisons silently flip a branch** — There is no exception or warning — the value simply differs, so the regression hides
until a test or a user hits the changed path. Fix: Grep for loose `==` against mixed types, make the intent explicit with `===` or a cast,
and lean on tests that exercise the equality on the PHP 8.0 runner.
2. **Warnings promoted to errors take down requests** — Code that quietly tolerated a warning on 7.4 now throws, so a page that rendered before
can start returning a fatal error after the bump. Fix: Run the suite with `error_reporting(E_ALL)` on 8.0, fix the newly fatal notices, and add
null-safe access or explicit defaults where arrays or types were assumed.
3. **Parameter names become part of the public API** — What used to be a safe internal rename can now be a breaking change for any consumer
using named arguments against your function or method. Fix: Settle on final parameter names during the migration and treat renames of public
parameters as semver-breaking from then on.
4. **Trusting Rector to fix behavior-sensitive breaks** — Mixing behavior-changing rules into a migration commit makes it impossible to tell a
syntax rewrite from a logic change during review. Fix: Scope Rector to the `PHP_80` set during migration, keep quality refactors in separate
commits, and never run Rector over untested code.

## Production checklist

- [ ] The full test suite and static analysis pass on PHP 7.4 before any migration change lands.
- [ ] `composer why-not php 8.0` returns nothing — every dependency permits the new runtime.
- [ ] The CI matrix runs the suite on PHP 7.4 and 8.0 and both jobs are green before cut-over.
- [ ] `vendor/bin/rector process --dry-run` with the PHP_80 set reports zero pending changes.
- [ ] The `config.platform.php` and `require.php` constraints in `composer.json` are raised
to `^8.0` only after every other check passes.

## Version notes

- **Native attributes replace annotation docblocks** (PHP >=8.0) — PHP 8.0 adds native attributes with the `#[Attribute]` syntax, a first-class replacement
for docblock annotations parsed by libraries on 7.4.
- **Constructor property promotion added** (PHP >=8.0) — PHP 8.0 lets you declare and assign properties directly in the constructor signature
with a visibility modifier, collapsing the boilerplate 7.4 required.
- **Magic method signatures enforced** (PHP >=8.0) — PHP 8.0 enforces the declared signatures of magic methods such as `__toString`, `__get`,
and `__call`, rejecting mismatched parameter or return types that 7.4 tolerated.
- **match expression added; match becomes reserved** (PHP >=8.0) — PHP 8.0 adds the `match` expression (strict comparison, returns a value) and makes
`match` a reserved keyword, so any identifier named `match` must be renamed.
- **Named arguments added** (PHP >=8.0) — PHP 8.0 adds named arguments, letting callers pass `func(limit: 10)` and skip optional
parameters — but it makes parameter names part of the callable's public contract.
- **Nullsafe operator ?-> added** (PHP >=8.0) — PHP 8.0 adds the nullsafe operator `?->`, which short-circuits to `null` when the left
operand is null instead of erroring on a method call on null.
- **Functions removed in PHP 8.0** (PHP >=8.0) — PHP 8.0 removes functions deprecated earlier, including `each()`, `create_function()`,
`money_format()`, and the `gmp_random()` alias — calls to them fatal-error.
- **Saner numeric-string comparison semantics** (PHP >=8.0) — PHP 8.0 changes number-to-non-numeric-string comparison: the number is cast to string
rather than the string to `0`, so `0 == 'foo'` is now false where it was true on 7.4.
- **throw is now an expression** (PHP >=8.0) — In PHP 8.0 `throw` is an expression, so it can be used in arrow functions, the null
coalescing operator, and ternaries — for example `$x = $v ?? throw new E();`.
- **Native union types added** (PHP >=8.0) — PHP 8.0 supports native union types such as `int|string` in parameter, return, and
property declarations, replacing 7.4 phpdoc `@param int|string` hints.
- **Many warnings promoted to errors** (PHP >=8.0) — PHP 8.0 promotes several former warnings and notices to `Error` or `TypeError` — for
example undefined array keys warn, while some invalid operations now throw.

## Security

- Not applicable — no I/O, auth, or untrusted input.

## Testing

- Activation and decision behavior are verified in `eval/scenarios/php/migrate-php74-to-php80.yaml`.
- Fix behavior-changing breaks by hand: the saner string-to-number comparison,
changed `sort()` stability assumptions, and magic-method signature tightening —
each behind existing tests, never as a blind Rector rewrite.

## Additional resources

- [references/anti-patterns.md](references/anti-patterns.md)
- [references/core.md](references/core.md)
- [references/decision-engine.md](references/decision-engine.md)
- [references/examples.md](references/examples.md)
- [references/pitfalls.md](references/pitfalls.md)
- [references/recipes.md](references/recipes.md)
- [references/version-differences.md](references/version-differences.md)

## Knowledge graph

**Related:** [audit/php](../../audit/php/SKILL.md), [audit/project-analysis](../../audit/project-analysis/SKILL.md), [php/attributes](../../php/attributes/SKILL.md), [php/enums](../../php/enums/SKILL.md), [php/typed-properties](../../php/typed-properties/SKILL.md)  
**Behavior constraints:** [ai-rules/strategy-no-untested-refactor](../../ai-rules/strategy-no-untested-refactor/SKILL.md)  

<!-- graph-fragment: graph/fragments/php/migrate-php74-to-php80.yaml -->

## Skill contract

**Produces:** `migration.php80-ready`  
**Requires:** `codebase.php74`  
**Risk:** high · **Confidence:** 0.9

<!-- contract-fragment: contracts/fragments/php/migrate-php74-to-php80.yaml -->

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY