php-x-7

内容来源:clawhub · 原始地址 · 查看安装指南

原始内容


name: php slug: php version: 1.0.2 changelog: "Full coverage pass: deeper guides, situation-named files, and per-user configuration" description: >- Writes, debugs, and reviews PHP: type juggling, arrays, OOP, Composer, PDO, sessions, PHP-FPM, OPcache, PHP 8 features. Use when PHP returns a blank white page or a 500 with no message, when "headers already sent", "Allowed memory size exhausted", "Maximum execution time exceeded", "Class not found" after a Composer install, or an undefined array key warning appears; when == compares wrong, strpos or preg_match returns a falsy match, accents turn into mojibake, DateTime shifts by a day or an hour, json_encode returns false, PDO refuses to bind a parameter or emulates prepares, sessions serialize parallel AJAX requests, nginx answers 502 or 504 in front of PHP-FPM, OPcache keeps serving old code, or a version upgrade breaks on deprecations. Also for escaping output, hashing passwords, sizing FPM workers, writing PHPUnit tests, and clearing PHPStan errors. Not for framework internals — laravel covers Eloquent, queues, and Laravel auth. homepage: https://clawic.com/skills/php metadata: clawdbot: emoji: 🐘 requires: bins: - php os: - linux - darwin - win32 displayName: PHP configPaths: - ~/Clawic/data/php/


User preferences live in ~/Clawic/data/php/config.yaml (see Configuration); nothing else is stored on the user's machine. If you have data at an old location (~/php/ or ~/clawic/php/), move it to ~/Clawic/data/php/.

When To Use

  • Writing or reviewing PHP — read Core Rules and Output Gates before emitting code
  • Any error whose cause is not obvious in ten seconds: white page, silent 500, wrong value with no exception, a hang
  • Runtime and deployment work: php.ini precedence, FPM pool sizing, OPcache, CLI workers, cron
  • Dependency and upgrade work: Composer constraints, autoloading, deprecations, moving to a newer 8.x
  • Data-boundary work: PDO queries, JSON payloads, uploads, sessions, dates, encodings
  • Hardening: escaping per context, password hashing, deserialization, path containment, upload validation
  • Not for framework internals — laravel owns Eloquent, queues, Blade, and Laravel auth

Quick Reference

Situation Play
== compares wrong, a numeric string misbehaves, floats do not add up === everywhere; "0e12" == "0e34" is true → hash_equalstypes.md
Keys vanish, order is wrong, a filtered list encodes as a JSON object array_filter keeps keys; array_values() before encoding → arrays.md
Accents break, strlen disagrees with what you see, a regex silently fails mb_* for text, /u on every pattern, check preg_last_error()strings.md
Class design: traits, static::, readonly, enums, magic methods, clone self:: binds at compile time, static:: at call time → oop.md
Exceptions, error handlers, warnings you cannot see, fatal errors Catch \Throwable; fatals need register_shutdown_functionerrors.md
Injection, XSS, uploads, unserialize, passwords, tokens Bind every value, escape per output context, random_bytes for secrets → security.md
Which PHP 8 feature to use, and how it bites match is strict and exhaustive; named args make parameter names API → modern.md
Upgrading PHP, a deprecation wall, "which version can I still target" Run the OLD version with deprecations as errors first → versions.md
Class not found, lock conflicts, ^ vs ~, slow or huge installs Commit the lock for apps, never for libraries → composer.md
White page, silent 500, "it worked yesterday", a hang with no output Symptom → cause chains, plus the Xdebug 3 setup that actually works → debugging.md
Slow requests, high memory, "should we turn on JIT" OPcache first, profile second; JIT does almost nothing for I/O-bound web → performance.md
A setting you changed has no effect; CLI and web disagree php --ini per SAPI; .user.ini is cached for 300s → php-ini.md
502, 504, "server reached pm.max_children", workers eating RAM Size from measured RSS; slowlog names the hung function → fpm.md
Console scripts, daemons, cron, signals, exit codes max_execution_time is 0 on CLI; recycle workers on a job count → cli.md
PHPUnit setup, flaky or falsely-passing tests, coverage, mocks assertSame not assertEquals; static state leaks between tests → testing.md
PHPStan or Psalm errors, baselines, coding standard, automated refactors Baseline the legacy, ratchet one level per merge → static-analysis.md
PDO connections, binding, transactions, "MySQL server has gone away" charset=utf8mb4 in the DSN, EMULATE_PREPARES => falsedatabase.md
Request/response handling, JSON bodies, uploads, outbound HTTP calls JSON bodies live in php://input, not $_POSThttp.md
Parallel requests from one user run one at a time; logins do not stick session_start() holds an exclusive lock — session_write_close() early → sessions.md
Dates off by a day or an hour, DST, parsing, storage DateTimeImmutable + IANA zones; createFromFormat inherits today → datetime.md
json_decode returns null, big IDs lose digits, json_encode returns false Always pass JSON_THROW_ON_ERRORjson.md
Reading or writing files, CSV, streams, temp files, permissions Stream instead of slurping; write temp then rename()files.md
Parallel work, queues, locks across processes, long-running runtimes Share-nothing per request; proc_open deadlocks if you read one pipe → concurrency.md
Anything else Core Rules below, then reproduce with php -d error_reporting=E_ALL -d display_errors=1 -r '<five suspect lines>' and add one thing back at a time

Each file above is one sub-job and is self-contained: read SKILL.md by default, open exactly one guide when the situation matches.

Core Rules

  1. declare(strict_types=1); on line 1 of every file. It is per-file and governs the CALLS made in that file, not the functions declared there — so an untyped legacy caller still passes "7" into your int parameter. Return values are checked against the mode of the file where the function is DECLARED (types.md).
  2. === and !== by default; == only where both operand types are proven. Worked case: "0e12" == "0e34" is true because both are numeric strings equal to 0 — a hash compared with == accepts the wrong password. Compare digests with hash_equals($known, $given), which is also constant-time.
  3. Check anything that can return false with !== false, never with truthiness. strpos($haystack, $needle) returns 0 for a match at offset 0, and if (!strpos(...)) reads that correct answer as a miss; the same shape breaks preg_match (0 = no match, false = engine error) and file_get_contents ("" is a valid file).
  4. Every SQL value is a bound parameter; every identifier comes from an allowlist you wrote. Placeholders cannot stand in for table or column names, and with the MySQL driver's default emulation the "prepared" statement is interpolated client-side — set PDO::ATTR_EMULATE_PREPARES => false (database.md).
  5. Escape at output, per context, never at input. htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') covers HTML body and quoted attributes only; a value going into JavaScript needs json_encode with the HEX flags, one going into a URL needs rawurlencode, one going into a shell needs escapeshellarg (security.md).
  6. Text is characters, not bytes: strlen, substr, str_pad, strrev, and ucfirst count bytes and will cut a UTF-8 sequence in half. Use mb_strlen/mb_substr/mb_str_pad (php >=8.3) for anything a human reads; keep the byte functions for binary data, where they are the correct tool.
  7. DateTimeImmutable everywhere. DateTime::modify() mutates the object that every reference shares, so a helper that "returns tomorrow" also moves the caller's date. The same call on an immutable returns a new instance and the bug cannot happen (datetime.md).
  8. Every call that leaves the process gets an explicit timeout: CURLOPT_TIMEOUT plus CURLOPT_CONNECTTIMEOUT, a stream context timeout for fopen, PDO::ATTR_TIMEOUT for connections. max_execution_time will not save you — on Unix it does not count time spent inside system calls, so a hung socket read is only killed by FPM's request_terminate_timeout (fpm.md).
  9. Stream instead of slurping. file_get_contents on a 400 MB export needs 400 MB plus copies inside the default 128M memory_limit; fgetcsv in a loop, or a generator, holds one row. The same rule retires fetchAll() on large result sets (files.md, performance.md).

Version Floors

The floors that shape everyday code. Individual guides carry more, inline in this same php >=X.Y form next to the instruction each one gates. Support windows and the upgrade procedure: versions.md.

Feature Needs
Typed properties, arrow functions fn() =>, spread in calls, ??=, covariant return types php >=7.4
match, named arguments, constructor promotion, ?->, union types, attributes, str_contains/str_starts_with/str_ends_with, static return type, throw as an expression, saner string-to-number comparison, PDO defaulting to exception error mode php >=8.0
Enums, readonly properties, fibers, never, first-class callables f(...), new in initializers, array_is_list(), string-keyed array unpacking, overridable interface constants, htmlspecialchars escaping single quotes by default php >=8.1
readonly classes, DNF types, null/false/true as standalone types, locale-independent strtolower/strtoupper, dynamic properties deprecated, ${var} interpolation deprecated php >=8.2
Typed class constants, #[\Override], json_validate(), mb_str_pad(), readonly reinitialization inside __clone, stack-overflow detection via zend.max_allowed_stack_size php >=8.3
Property hooks, asymmetric visibility, new Foo()->m() without parentheses, lazy objects, array_find/array_any/array_all, mb_trim, implicit nullable parameters (Foo $x = null) deprecated php >=8.4
Pipe operator |>, #[\NoDiscard] php >=8.5

Output Gates

Before delivering PHP code, check:

  • declare(strict_types=1); present, and every parameter, property, and return typed as narrowly as the value allows
  • No ==, !=, or switch on values whose types are not both proven; in_array and array_search carry true as the strict flag
  • Every SQL value bound, every identifier allowlisted, EMULATE_PREPARES off
  • Every value reaching HTML, an attribute, JavaScript, a URL, or a shell escaped with that context's function, at the point of output
  • Nothing user-controlled reaches unserialize, include/require, eval, extract, a filesystem path, or a header() value without validation
  • Every outbound call (HTTP, DB, subprocess, lock) has a timeout, and every false-returning call has its return inspected
  • Dates are DateTimeImmutable with an explicit IANA timezone; human-facing text handled with mb_*
  • json_encode/json_decode carry JSON_THROW_ON_ERROR
  • Syntax and functions stay within min_php; layout matches style; new behavior arrives with a test that was seen failing first

Configuration

User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/php/config.yaml. Never interview the user — record a preference the moment it is stated.

Variable Type Default Effect
min_php 7.4 | 8.0 | 8.1 | 8.2 | 8.3 | 8.4 | 8.5 8.2 Gates which Version Floors features may be emitted unguarded, and which fallback appears instead (versions.md)
sapi fpm | cli | apache-mod | swoole | roadrunner fpm Assumptions about request lifetime, static state, timeouts, and where output goes (fpm.md, cli.md, concurrency.md)
framework none | laravel | symfony | wordpress | slim none Whether to emit plain PHP or framework idiom, and when to hand off to a framework skill
strict_types always | new-files | never always Whether emitted files open with declare(strict_types=1), and how coercion is discussed (types.md)
style per-cs | psr-12 | custom per-cs Layout of emitted code and the fixer ruleset produced (static-analysis.md)
static_analysis none | phpstan | psalm phpstan Which checker's annotations, config file, and baseline commands appear (static-analysis.md)
analysis_level number (0-9) 6 Strictness assumed when annotating, and whether a docblock is required alongside a native type (static-analysis.md)
test_framework phpunit | pest | none phpunit Shape of emitted tests, assertions, and data providers (testing.md)
db_layer pdo | doctrine | eloquent | mysqli pdo Which data-access idiom every query example uses (database.md)

Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied:

  • Tooling: fixer (PHP-CS-Fixer vs PHP_CodeSniffer), Rector adoption, Xdebug vs pcov for coverage, profiler of choice, local runtime (Docker, Homebrew, Valet, WSL) — affects every emitted config block and command
  • Conventions: namespace and directory layout, DTO style (arrays vs readonly classes vs enums), exception hierarchy, naming, docblock policy — affects generated code and review comments
  • Platform: deployment target (FPM behind nginx, shared hosting, container, serverless, long-running runtime), OS, extension availability (ext-intl, ext-mbstring, ext-pcntl), private Packagist — affects composer.md, php-ini.md, fpm.md
  • Safety posture: how aggressively to flag unserialize, dynamic include, extract, @, missing timeouts, unpinned dependencies, and emulated prepares — affects security.md and review depth
  • Dependencies: banned or mandated packages (Guzzle vs Symfony HttpClient, Carbon vs native DateTimeImmutable), tolerance for new transitive dependencies, stdlib-only constraints — affects every recommendation
  • Output format: whole file vs minimal diff, how much explanation, whether tests and a changelog entry accompany every change — affects the shape of the answer, never the correctness rules
  • Work order: which gates run before a change is proposed rather than after — analyzer and suite green first, a plan approved before editing, a profile before any optimization — affects the sequence of every task, never the correctness rules

Traps

Trap Why it fails Do instead
if (!strpos($s, $x)) Returns 0 for a match at position 0 — the correct answer reads as failure strpos($s, $x) !== false, or str_contains() on php >=8.0 (strings.md)
== to compare a password hash or token "0e12" == "0e34" is true; both are numeric strings equal to zero hash_equals(), or password_verify() for passwords (security.md)
array_filter() then json_encode() Keys survive, so the gap turns the array into a JSON object and the client's .map() fails array_values() after every filter that feeds JSON (arrays.md, json.md)
json_decode($s) with no flags Returns null for the input "null" AND for a parse error — indistinguishable Always JSON_THROW_ON_ERROR (json.md)
$_POST for a JSON request body Populated only for form-encoded and multipart bodies json_decode(file_get_contents('php://input'), ...) (http.md)
Trusting $_FILES['f']['type'] The MIME type is supplied by the client and is trivially forged finfo_file() on the temp file plus an extension allowlist (security.md)
session_start() at the top and never closing The session lock is held for the whole request, so one user's parallel requests execute one at a time session_write_close() as soon as the last write is done (sessions.md)
DateTime::createFromFormat('Y-m-d', $d) Unspecified fields default to NOW, so the date carries the current time and can roll the day Prefix the format with ! to zero them, then check getLastErrors() (datetime.md)
@ to silence a warning Hides the diagnosis and leaves the broken value in flight; on php >=8.0 it no longer even silences fatals Handle the failure, or narrow error_reporting deliberately (errors.md)
mt_rand/uniqid() for a token or password reset Predictable: uniqid() is a formatted timestamp, mt_rand is a seeded PRNG random_bytes()/random_int() (security.md)
PDO::ATTR_PERSISTENT without cleanup A worker inherits an open transaction, session variables, or temporary tables from the previous request Non-persistent connections, or an explicit rollback on checkout (database.md)
A closing ?> at the end of a PHP file One trailing newline becomes output, and every later header() fails with "headers already sent" Omit the closing tag in pure-PHP files (http.md)
readfile() for a large download Fills the output buffer with the whole file before sending fpassthru with buffers flushed, or X-Accel-Redirect/X-Sendfile (http.md)
opcache.validate_timestamps=0 with a deploy that only swaps files Workers keep executing the previous release's bytecode Reload FPM (kill -USR2) as the last deploy step (performance.md, fpm.md)
SELECT then INSERT to avoid duplicates Two workers pass the check in the same millisecond Unique index plus catching the duplicate-key error (concurrency.md)

Where Experts Disagree

  • Arrays vs objects for structured data. The array-shape school keeps everything a list<array{id:int,…}> and lets the analyzer verify it: no boilerplate, refactors are grep-based. The value-object school makes each shape a readonly class: constructor validation, no key typos, IDE completion. Boundary: data crossing a module or process boundary earns a class; a shape used in three lines of one function does not (static-analysis.md).
  • Active record vs data mapper. Eloquent-style records are faster to write and let any layer reach the database; Doctrine-style mappers keep the domain persistence-free and make the unit of work explicit. Boundary: how long the codebase must outlive its storage decisions — not how large it is (database.md).
  • How strict to run the analyzer. One camp treats anything below max as unfinished; the other stops where the false-positive rate exceeds the bug-catch rate. Boundary: a greenfield typed domain reaches high levels cheaply; a decade-old codebase baselines and ratchets, and measures success as baseline shrinkage rather than level number (static-analysis.md).
  • Long-running runtimes. Swoole, RoadRunner, and FrankenPHP remove per-request bootstrap and can cut latency materially; they also delete PHP's strongest safety property, that every request starts from a clean process. Boundary: adopt when bootstrap is a measured share of p95 AND the team accepts hunting state leaks — never as a default (concurrency.md).
  • Framework or none. A framework supplies routing, DI, migrations, and a hiring pool; plain PHP with a few PSR packages supplies a dependency tree you can read end to end. Boundary: the number of cross-cutting concerns you would otherwise hand-write — auth, queues, and migrations together is the tipping point.

Related Skills

More Clawic skills, get them at https://clawic.com/skills/php (install if the user confirms):

  • laravel — Eloquent, queues, Blade, and Laravel-specific auth
  • mysql — schema design, indexes, and query plans behind PDO
  • nginx — the server in front of PHP-FPM: buffering, timeouts, static files
  • regex — PCRE pattern design; this skill's strings.md covers only the PHP bindings
  • debugging — language-agnostic fault isolation; the PHP-specific version is this skill's debugging.md

Feedback

Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/php.