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`).

Quellinformationen

Repository
ericrisco/rsc-harness
Letzte Quellaktivität
29. Juli 2026 um 23:23
Erkannte Sprache von SKILL.md
Englisch
Sterne
110
Forks
9

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
6 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen