Skip to main content

php

Use when writing, reviewing or modernizing PHP (8.3-8.5) outside Laravel — strict types, union and intersection types, readonly classes, backed enums, property hooks and asymmetric visibility, the pipe operator and clone-with, Composer with PSR-4, PER-CS style, PSR interop, and the quality toolchain (PHPStan, Pint, Rector, PHPUnit/Pest). NOT Eloquent, Blade or Artisan (that is `laravel`).

Informations de source

Dépôt
ericrisco/rsc-harness
Dernière activité de la source
29 juillet 2026 à 23:23
Langue détectée de SKILL.md
anglais
Étoiles
110
Forks
9

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
6 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
php
description
Use when writing, reviewing or modernizing PHP (8.3-8.5) outside Laravel — strict types, union and intersection types, readonly classes, backed enums, property hooks and asymmetric visibility, the pipe operator and clone-with, Composer with PSR-4, PER-CS style, PSR interop, and the quality toolchain (PHPStan, Pint, Rector, PHPUnit/Pest). NOT Eloquent, Blade or Artisan (that is `laravel`).
tags
["php","php8","composer","psr","enums","types","static-analysis"]
recommends
["laravel","secure-coding","mysql"]
origin
risco
# Modern PHP (8.x) Write PHP the way the 2025-2026 ecosystem does: `declare(strict_types=1)` at the top of every file, typed everything, Composer-first, statically analyzed at the top level — not the way a 2015 WordPress plugin did. This skill owns the **language and its framework-agnostic ecosystem**: the type system, Composer + PSR-4, PER-CS style, the PSR interop interfaces, and the quality toolchain. **Version targeting.** Floor is **8.3** (security-only, the lowest you should support). Default new code to **8.4** (property hooks, asymmetric visibility). Use **8.5** features (`|>`, `clone with`, `array_first`/`array_last`, `#[\NoDiscard]`) only when the deploy runtime is confirmed 8.5+ — 8.5 released 2025-11-20. 7.x and 8.0-8.2 are EOL; never target them. ## When to use - Authoring/reviewing/refactoring any `.php` file or a `composer.json`. - Designing classes: enums, DTOs, value objects, readonly classes, interfaces, traits. - Standing up a vanilla-PHP project: Composer, PSR-4 autoload, namespaces, entrypoint. - Wiring quality gates: PHPStan/Psalm, Pint/PHP-CS-Fixer, Rector, CI. - Modernizing legacy 5.x/7.x patterns to 8.x idioms. - Picking framework-agnostic libs (Symfony components, Guzzle, Monolog, Doctrine DBAL, league/*) and PSR-compatible interop. ## When NOT to use (delegate) | The ask is about | Route to | This skill keeps | |---|---|---| | Eloquent, Blade, Artisan, container bindings, queues | laravel | the PHP *underneath* Laravel only | | WP hooks, the loop, `wp_*`, `$wpdb` | wordpress | nothing WP-specific | | Shopify app/theme SDK work | shopify | nothing Shopify-specific | | OWASP threat modeling, authz/abuse review | secure-coding | PHP-native controls (PDO, `password_hash`, escaping) | | REST resource naming, status-code contract as a discipline | api-design | PHP request/response code only | | DB schema/index tuning | mysql / postgresdb | PDO usage from the PHP side | The type system, Composer, PSR, and the static-analysis toolchain are canonical **here** and nowhere else in the catalog. ## Non-negotiables 1. **`declare(strict_types=1);` is the first statement in every `.php` file.** Without it PHP silently coerces `"5"` to `5`, `1` to `true` — bugs that type hints exist to stop. 2. **Type every parameter, return, and property.** An untyped signature is a `mixed` you did not ask for; PHPStan cannot reason about it. 3. **One namespace per file, PSR-4, Composer-autoloaded.** No `require_once` chains, no hand-rolled autoloaders. PSR-0 is deprecated. 4. **`final` by default.** Open a class for extension only when you have designed the extension point. Inheritance you did not plan for is a maintenance bill. 5. **Commit `composer.lock`** for applications (reproducible installs); libraries commit it for dev too but do not ship it in the package. 6. **PHPStan at max + style-clean before "done".** "It runs" is not the bar; the static analyzer and formatter passing is. ## The type system | Tool | Use it for | One-line why | |---|---|---| | Union `A\|B` | a value that is genuinely one of N types | beats `mixed`; PHPStan narrows it | | Intersection `A&B` | a value that must satisfy several interfaces | expresses "Countable *and* Traversable" without a marker type | | `readonly` property | a field set once in the constructor | immutability the engine enforces, no manual guard | | `readonly class` (8.2+) | a whole value object | every property readonly; can only build a changed copy | | Pure enum | a closed set with no scalar backing | replaces stringly-typed class constants | | Backed enum (`: string`/`: int`) | a closed set that maps to a DB/JSON value | `from()`/`tryFrom()` give safe parsing | | `never` return | a function that always throws/exits | tells the analyzer the path is dead | | `true`/`false` literal types (8.2+) | a method that only ever returns one | precise contracts | | Nullable `?T` | "may be absent" | distinct from "optional argument with a default" | | `@template` docblock generics | typed collections/containers | PHPStan reads them; the engine does not have native generics | ```php <?php declare(strict_types=1); // Bad: stringly-typed, untyped, mutable, coercible. class Order { public $status; // untyped -> mixed public function setStatus($s) { $this->status = $s; } // accepts anything } // Good: backed enum + readonly + typed signatures. enum OrderStatus: string { case Pending = 'pending'; case Paid = 'paid'; case Shipped = 'shipped'; public function isFinal(): bool { return $this === self::Shipped; } } final readonly class Order { public function __construct( public string $id, public OrderStatus $status, ) {} } $status = OrderStatus::tryFrom($raw) ?? OrderStatus::Pending; // safe parse, never throws on bad input ``` Generics live in docblocks until the engine ships them — PHPStan enforces them: ```php <?php declare(strict_types=1); /** * @template T */ final class Collection { /** @var list<T> */ private array $items = []; /** @param T $item */ public function add(mixed $item): void { $this->items[] = $item; } /** @return list<T> */ public function all(): array { return $this->items; } } ``` See [references/type-system.md](references/type-system.md) for enum-with-interface patterns, variance, the asymmetric-visibility matrix, and `readonly` edge cases. ## Modern OO idioms ```php <?php declare(strict_types=1); final class PriceCalculator { // Constructor property promotion: declare + assign in one place. public function __construct(private readonly TaxRate $rate) {} public function total(Money $net): Money { // match (not switch): expression, strict ===, no fall-through, exhaustive-ish. $multiplier = match ($this->rate->region) { Region::EU => 1.21, Region::US => 1.00, }; return $net->times($multiplier); } } // Named arguments: skip optional params, self-document call sites. $client = new HttpClient(timeout: 5, retries: 3); // First-class callable syntax: pass a method as a callable without a closure wrapper. $ids = array_map($repo->idOf(...), $orders); ``` Rules: promote constructor properties; prefer `match` over `switch`; enums over class constants; immutable DTOs over mutable bags; `$fn(...)` over `Closure::fromCallable`. ## PHP 8.4: property hooks + asymmetric visibility Property hooks give computed/guarded properties the engine and PHPStan can see — no docblock getters. Asymmetric visibility lets a property be read widely but written narrowly, killing get/set boilerplate. ```php <?php declare(strict_types=1); // Bad: manual getter/setter pair, invisible to static analysis as a "property". final class Temperature { private float $celsius = 0.0; public function getFahrenheit(): float { return $this->celsius * 9 / 5 + 32; } public function setCelsius(float $c): void { if ($c < -273.15) { throw new \InvalidArgumentException('below absolute zero'); } $this->celsius = $c; } } // Good: a computed property hook + a guarded set hook (PHP 8.4+). final class Temperature { public float $celsius = 0.0 { set (float $value) { if ($value < -273.15) { throw new \InvalidArgumentException('below absolute zero'); } $this->celsius = $value; } } public float $fahrenheit { get => $this->celsius * 9 / 5 + 32; } } // Asymmetric visibility: readable everywhere, writable only inside the class. final class Account { public function __construct(public private(set) int $balance) {} public function deposit(int $amount): void { $this->balance += $amount; } } ``` **Trap:** keep hooks pure-ish. A `get` hook that runs a query or mutates state turns a field access into a hidden side effect. For lazy initialization or I/O, use an explicit method, not a hook. ## PHP 8.5: pipe, clone-with, and friends (8.5+ runtime only) Only reach for these when the deploy target is confirmed 8.5+ (released 2025-11-20). ```php <?php declare(strict_types=1); // Bad: deeply nested calls read inside-out. $result = array_sum(array_filter(array_map(strlen(...), $words), fn($n) => $n > 3)); // Good: pipe operator |> reads left-to-right as a transform pipeline (8.5+). $result = $words |> fn($w) => array_map(strlen(...), $w) |> fn($n) => array_filter($n, fn($x) => $x > 3) |> array_sum(...); // clone with: a with-er for readonly objects in one expression (8.5+). $shipped = clone $order with ['status' => OrderStatus::Shipped]; // array_first / array_last: no more reset()/end() side effects (8.5+). $head = array_first($items); $tail = array_last($items); ``` `#[\NoDiscard]` (8.5+) marks a return value that must be used — the engine warns if a caller ignores it. Put it on a method whose result is the whole point (a built value, a `Result`). ## Composer & project layout ``` my-package/ ├── composer.json ├── composer.lock # commit it for apps ├── src/ # PSR-4 root -> namespace App\ ├── tests/ └── phpstan.neon ``` ```json { "name": "acme/my-package", "type": "library", "require": { "php": ">=8.3", "psr/log": "^3.0" }, "require-dev": { "phpstan/phpstan": "^2.1", "laravel/pint": "^1.18", "pestphp/pest": "^4.0", "rector/rector": "^2.0" }, "autoload": { "psr-4": { "App\\": "src/" } }, "autoload-dev": { "psr-4": { "App\\Tests\\": "tests/" } }, "scripts": { "lint": "pint --test", "stan": "phpstan analyse", "test": "pest", "check": ["@lint", "@stan", "@test"] }, "config": { "sort-packages": true } } ``` Rules: `require` = runtime deps, `require-dev` = tools/tests; the `psr-4` map points a namespace prefix at a base dir (PSR-0 is dead); `composer check` is your one-shot gate. ## Error handling ```php <?php declare(strict_types=1); // A typed hierarchy lets callers catch by meaning, not by string matching. abstract class DomainException extends \RuntimeException {} final class OrderNotFound extends DomainException {} final class PaymentDeclined extends DomainException {} try { $order = $repo->find($id) ?? throw new OrderNotFound("order {$id}"); } catch (PaymentDeclined $e) { $logger->warning('payment declined', ['order' => $id, 'reason' => $e->getMessage()]); throw $e; // rethrow; do not swallow } finally { $lock->release(); // runs whether or not we threw } ``` Rules: throw typed exceptions, never bare `\Exception`; never swallow (no empty `catch`); never use the `@` error-suppression operator — it hides fatals from the analyzer; clean up in `finally`; catch `\Throwable` only at a process boundary (CLI entry, request handler). ## Security controls (PHP-native) Generic appsec (OWASP, authz, threat modeling) is [../secure-coding/SKILL.md](../secure-coding/SKILL.md). The PHP-specific controls below stay here. | Control | API | Why | |---|---|---| | Parametrized SQL | PDO prepared statements | the only safe defense against SQLi; never interpolate | | Password storage | `password_hash()` / `password_verify()` | bcrypt/argon2 with per-hash salt; never `md5`/`sha1` | | Tokens / secrets | `random_bytes()` / `random_int()` | cryptographically secure; `rand()`/`mt_rand()` are not | | Output to HTML | `htmlspecialchars($s, ENT_QUOTES, 'UTF-8')` | stops reflected/stored XSS at the boundary | | `unserialize()` | `['allowed_classes' => false]` | blocks object-injection gadget chains | | Comparing secrets | `hash_equals()` | constant-time; `===` leaks length/timing | ```php <?php declare(strict_types=1); // Bad: string interpolation = SQL injection.
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub