
Description
Modern PHP 8.x policy and pitfalls. Use when writing, reviewing, or refactoring PHP code — enforces strict types, enum/readonly/match/DNF policy, PHPStan discipline, and catches the subtle traps (type coercion, mixed abuse, readonly mutation through references, enum serialization, fiber lifecycle, PDO emulation) that LLMs get wrong by default.
SKILL.md
PHP — policy & pitfalls
Baseline PHP 8.x knowledge (enums, readonly, match, nullsafe, union/intersection/DNF types, named arguments, first-class callables, fibers, PSR-1/4/12, Composer) is assumed. This skill does not teach the language — it encodes project policy and the traps that keep appearing in code review.
Setup Check (run first)
Before writing non-trivial code:
- PHP version —
composer.jsonmust declare"php": ">=8.4"(or higher). PHP 8.4 is the current active release; 8.3 is in security-only mode (active support ended Dec 2025); 8.2 has security support until Dec 2026 but should not be chosen for new projects. Checkphp --versionif running locally. declare(strict_types=1);at the top of every file. Non-negotiable — without it PHP coerces"42"→42,3.7→3,"abc"→0silently.- Static analysis —
vendor/bin/phpstan(target level 8+) and/orvendor/bin/psalm. If missing, suggestcomposer require --dev phpstan/phpstan. Run before presenting code. - Formatter —
vendor/bin/pint/php-cs-fixer/phpcs. Respect the existing config; don't introduce new violations. - Lock file —
composer.lockmust be committed for applications. Never commitvendor/. - Framework — check for
laravel/framework,symfony/framework-bundle, etc. If present, stop and load the corresponding framework skill (laravel-engineer, etc.); this skill is pure PHP only.
MUST DO
- Strict types in every file —
declare(strict_types=1);on line 1 after<?php. - Type everything — parameters, return types, property types. Untyped code is an error.
- Backed
enumfor closed value sets — neverclass Status { const ACTIVE = 'active'; }. readonlyfor immutable data — DTOs, value objects, events. Preferreadonly class(PHP 8.2+) over per-propertyreadonly.matchoverswitch— strict comparison (===), exhaustive, returns value, throwsUnhandledMatchErroron miss.- Named arguments for 3+ parameters —
new UserDto(id: 1, name: 'n', email: 'e')prevents silent argument swaps. - Constructor property promotion — no separate property declarations + manual assignments.
DateTimeImmutable— neverDateTime; the mutable one causes aliasing bugs.- Prepared statements for every SQL query reaching user input. Bind with named or
?placeholders — never string-concat. password_hash($p, PASSWORD_BCRYPT)/PASSWORD_ARGON2ID+password_verify. Nevermd5/sha1/sha256for passwords.- Parameterized logging via PSR-3 —
$logger->info('user.updated', ['id' => $id]), not string concat.
MUST NOT DO
- No
mixedwithout a PHPDoc comment explaining why — it disables type checking on the value. - No untyped
arrayin public API — annotate with PHPDoc:/** @param list<User> $users */or/** @return array<string, int> */. - No raw
SimpleXMLElement/json_decodewithout validation — usejson_validate()(PHP 8.3+) first, orJSON_THROW_ON_ERROR. - No
(array)cast on objects to "export state" — it exposes private/protected with mangled keys ("\0*\0prop"). - No mutating a
readonlyproperty through a reference —$ref = &$obj->propthrowsErroron PHP 8.1+; PHP 8.3 also closed indirect bypass loopholes. Usecloneor awith*()factory method to produce a modified copy. - No
foreach ($arr as &$v)withoutunset($v)after — the reference leaks to the outer scope and a later assignment silently mutates the array. - No
count($big) === 0to check emptiness — use$big === []orempty($big).count()on aCountableobject can be expensive;count(null)throwsTypeErroron PHP 8.0+. - No
!=/==— always!==/===.0 == "abc"wastrueuntil PHP 8, and string/array comparisons still surprise. - No catching
\Throwableto silence errors — catch the specific exception type or let it propagate. - No
global/ service locator — constructor-inject everything. - No credentials / PII in logs or error messages.
Reference Guide
| Load when | File |
|---|---|
| Debugging subtle language / API behavior (enums, readonly, match, fibers, arrays, dates, comparison) | references/pitfalls.md |
Designing type signatures (PHPStan generics, DNF, mixed/never/void, narrowing) | references/types.md |
| Writing security-sensitive code (SQL, passwords, CSRF, file upload, serialization) | references/security.md |
Output Format
When producing code:
- A short plan (1–3 bullets) of what's changing.
- The code with
declare(strict_types=1);and types everywhere. - PHPStan passes at the project's configured level — mention if new
@phpstan-ignore/@paramannotations were added and why.
When reviewing code: call out MUST-DO / MUST-NOT violations explicitly and suggest the minimal fix.
More skills from the junie-extensions repository
View all 9 skillsandroid
build Android applications with Kotlin
Jul 13AndroidEngineeringKotlinMobilecontext7
search library and framework documentation
Jul 17DocumentationMCPReferencejava-engineer
write and refactor modern Java code
Jul 13Code AnalysisEngineeringJavakotlin-engineer
write and refactor Kotlin code
Jul 13Code ReviewEngineeringKotlinlaravel-engineer
build and architect Laravel applications
Jul 13BackendEngineeringLaravelPHPredis-best-practices
implement Redis best practices
Jul 13DatabaseEngineeringPerformanceRedis
More from JetBrains
View publishermps-aspect-accessories
configure JetBrains MPS module dependencies
MPS
Jul 17ArchitectureConfigurationEngineeringmps-aspect-actions
define and edit MPS node factories
MPS
Jul 17ArchitectureEngineeringmps-aspect-behavior
define and edit MPS concept behavior
MPS
Jul 13ArchitectureEngineeringmps-aspect-constraints
define JetBrains MPS language constraints
MPS
Jul 23ArchitectureCode Analysismps-aspect-dataflow
define and debug MPS dataflow builders
MPS
Jul 13Data Analysismps-aspect-editor
define MPS editor layouts
MPS
Jul 23DesignUI Components