agentleFS
JMAILLYJMAILLY/claude-migration-skills/skills/php-deprecations-audit/SKILL.md

php-deprecations-audit

Skill0 starsChanged 24 days agoMITView on GitHub

Audit and fix deprecated Drupal/PHP APIs in custom code before a major Drupal core bump, on a Dockerized Makefile-driven project. Use when the user mentions upgrade_status, drupal-rector, phpstan deprecation detection, "removed in D11" (or the next major) APIs, implicitly-nullable PHP 8.4 parameters, or fixing custom-code deprecations before bumping core. Runs upgrade_status + rector + phpstan, applies automatic + manual fixes, loops until 0 errors. IMPORTANT: all commands MUST go through Makefile targets; run this WHILE core is still on the OLD major, never after the bump.

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Never call drush, composer or php directly on the host — use make:

    line 25

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

PHP & Drupal deprecations audit (before a major core bump)

⚠️ RUN WHILE CORE IS STILL ON THE OLD MAJOR (e.g. Drupal 10). Upgrade Status and drupal-rector derive their target from the installed core. On D10 they surface/fix "deprecated in D10, removed in D11" — exactly what we want. If core is already on D11 they switch to the D12 target and the D11 correction list becomes unreachable. Fix everything and re-scan until 0 errors for the target major BEFORE bumping core.

Golden rule: everything goes through the Makefile

Never call drush, composer or php directly on the host — use make:

NeedMake target
Generic Drush commandmake drush c='<cmd>'
Add a dev dependencymake composer-require <pkg> --dev
Static analysismake phpstan
Coding-standards fixmake phpcbf
Shell in the PHP containermake shell
Enable a modulemake en <m>

For tools that must run inside the container (rector, php -v…), use make shell and execute from within.

Main tool: Upgrade Status

drupal/upgrade_status scans custom and contrib code for deprecated APIs and next-major incompatibilities.

make composer-require drupal/upgrade_status --dev
make en upgrade_status
make drush c='upgrade_status:analyze'                 # overview

Report also at /admin/reports/upgrade-status. Target a specific module:

make drush c='upgrade_status:analyze <custom_module_name>'
make drush c='pml --type=module --status=enabled --no-core'

Static analysis with PHPStan

The project has a make phpstan target. Ensure phpstan.neon includes web/modules/custom and web/themes/custom. Recommended for deprecation detection: mglaman/phpstan-drupal + phpstan/phpstan-deprecation-rules.

make phpstan

Automate fixes: Drupal Rector

drupal-rector applies a large share of the corrections automatically. Its config targets the next version relative to the installed core: run on D10 it prepares the code for D11 — which is why it must run before the core bump (on D11 it would target D12).

make composer-require palantirnet/drupal-rector --dev
make shell
# inside the container — dry-run first:
vendor/bin/rector process web/modules/custom --dry-run
# if OK:
vendor/bin/rector process web/modules/custom
exit

Rector doesn't fix everything: re-run make phpstan and upgrade_status afterwards and handle the remainder manually.

Manual scan of custom modules

ls web/modules/custom
grep -rn 'entity.manager\|entityManager(' web/modules/custom
grep -rn 'drupal_set_message' web/modules/custom
grep -rn 'file_unmanaged_' web/modules/custom
grep -rn '\\\\Drupal::url(' web/modules/custom
grep -rn 'node_load(\|user_load(\|taxonomy_term_load(' web/modules/custom

Main D11 removals table

Removed in D11Replacement
\Drupal::service('entity.manager')\Drupal::entityTypeManager()
node_load(), user_load()entityTypeManager()->getStorage(...)->load()
drupal_set_message()\Drupal::messenger()->addMessage()
file_unmanaged_*\Drupal\Core\File\FileSystemInterface
\Drupal::url()Url::fromRoute() / Url::fromUri()
format_date()\Drupal::service('date.formatter')->format()
drupal_render()renderer service
Procedural hooksStill work but prefer PHP attribute hooks (D11)
EntityManagerInterfaceEntityTypeManagerInterface (+ dedicated services)

PHP language-level deprecations (not just Drupal APIs)

Rector and Upgrade Status only target removed Drupal APIs — they miss pure PHP deprecations tied to the new runtime (see php-docker-upgrade). Detect these with make phpstan running under the target PHP version.

Implicitly nullable parameters — the frequent PHP 8.4 one

A pure PHP 8.4 deprecation, not a Drupal API change. Rector and Upgrade Status do not report it — a separate phpstan pass is required.

// Before (deprecated in PHP 8.4)
public function buildForm(array $form, FormStateInterface $form_state, Request $request = null) {}

// After (correct)
public function buildForm(array $form, FormStateInterface $form_state, ?Request $request = null) {}
// or
public function buildForm(array $form, FormStateInterface $form_state, Request|null $request = null) {}

Rule: whenever a typed parameter has = null as its default, the type must be explicitly nullable (?Type or Type|null). Frequent on buildForm() and injected optional params.

Detect:

make phpstan 2>&1 | grep -i "implicitly"

# or grep the source directly
grep -rn "function.*\(.*[A-Z][a-zA-Z]*\s\+\$[a-z].*=\s*null" web/modules/custom/ web/themes/custom/

Known contrib affected (watch when updating): field_group, config_ignore. They emit runtime warnings but don't block — report upstream.

Loop until clean

Custom modules live under web/modules/custom. Re-run upgrade_status:analyze after each pass until custom code is declared compatible with the target major. Do not bump core before this reaches 0 errors.

make drush c='upgrade_status:analyze <module>'   # re-scan

Cleanup at the end of the whole upgrade

The upgrade_status and drupal-rector dev dependencies are audit-only — remove them once the core bump is done (handled by the d11 orchestrator, not here):

make pmu upgrade_status
make composer-remove drupal/upgrade_status
make composer-remove palantirnet/drupal-rector

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.