From php
PHP language conventions, modern idioms, and type system. Invoke whenever task involves any interaction with PHP code — writing, reviewing, refactoring, debugging, or understanding PHP projects.
npx claudepluginhub xobotyi/cc-foundry --plugin phpThis skill uses the workspace's default tool permissions.
**Strict types, explicit contracts, no magic. If a class needs a docblock to explain what its properties do, the
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Strict types, explicit contracts, no magic. If a class needs a docblock to explain what its properties do, the properties are named wrong.
PHP 8.5+ is the baseline. Use modern syntax unconditionally — union types, enums, readonly classes, property hooks,
named arguments, match, pipe operator. No backward compatibility with older PHP versions unless the project explicitly
requires it.
Every PHP file starts with declare(strict_types=1).
${CLAUDE_SKILL_DIR}/references/typing.md — Union/intersection/DNF types, nullable patterns, typed
properties and constants, coercion rules, variance${CLAUDE_SKILL_DIR}/references/oop.md — Interfaces, traits, readonly, property hooks, enums,
constructor promotion, lazy objects, magic methods${CLAUDE_SKILL_DIR}/references/concurrency.md — Fiber API, generator coroutines, comparison table,
async library guidance${CLAUDE_SKILL_DIR}/references/packaging.md — composer.json templates, version constraints, project
layouts, namespace conventionsClasses, interfaces, traits, enums — PascalCase: UserService, Renderable, Status
Methods, functions — camelCase: findById, getFullName
Properties, variables — camelCase: $userName, $isActive
Constants (class and global) — UPPER_SNAKE_CASE: MAX_RETRIES, DEFAULT_LOCALE
Namespaces — PascalCase segments: App\Http\Controller
Enum cases — PascalCase: Status::Active, Suit::Hearts
Descriptive names. $userCount not $n. Short names ($i, $k, $v) only in tiny scopes (loops, array
operations).
No redundant context. $car->make not $car->carMake.
Boolean names: is/has/can/should prefix: $isValid, $hasAccess.
Abbreviations as words. HttpClient not HTTPClient, JsonParser not JSONParser. Treat abbreviations and
acronyms as regular words — uppercase first letter only (PER-CS).
No underscore prefix for protected/private visibility. Visibility modifiers exist for that.
PHP 8.5+ provides a complete type system. Use it everywhere.
declare(strict_types=1) in every file. No exceptions.bool, int, float, string. Never boolean, integer, double.|: string|int, Foo|null. Prefer ?T for single-type nullable.&: Countable&Traversable. Class/interface types only.array|(ArrayAccess&Traversable) — union of intersections in parentheses.void return: annotate on functions that return nothing.never return: functions that always throw or exit.mixed — it disables type safety. Use object when you mean "any object." Use mixed only at true interop
boundaries with untyped code.null last in unions: string|int|null, not null|string|int.callable cannot be used as a property type. Use Closure instead.class Config
{
public const int MAX_RETRIES = 3;
public const string DEFAULT_LOCALE = 'en';
protected const float TAX_RATE = 0.21;
}
mixed return can be narrowed to any type in a subclass.See ${CLAUDE_SKILL_DIR}/references/typing.md for the complete type system reference.
string or int) when the value must interoperate with external systems (JSON, database, API).from() throws on invalid value; tryFrom() returns null. Choose based on whether invalid input is a caller
error or expected.new'd.Status::{$name} (8.3+) for variable-based case resolution.enum Status: string
{
case Active = 'active';
case Inactive = 'inactive';
case Suspended = 'suspended';
public function label(): string
{
return match ($this) {
self::Active => 'Active',
self::Inactive => 'Inactive',
self::Suspended => 'Suspended',
};
}
}
class User
{
public function __construct(
public readonly string $name,
private string $email,
protected int $age = 0,
) {}
}
public protected(set) for publicly readable, internally writable properties.get/set logic on properties. Use instead of trivial getter/setter methods.
Incompatible with readonly.use statement per trait, each
on its own line.#[Override] (8.3+) on every method that overrides a parent or implements an interface method. Catches signature
drift at compile time.super() equivalent: always use parent::method(). Never hardcode grandparent class names.__get, __set) in new code. Typed properties with hooks are strictly better.__toString() — define when string conversion has meaningful semantics.__invoke() — for single-method objects that act as callables.__serialize() / __unserialize() — prefer over __sleep() / __wakeup().readonly class with constructor promotion. Immutable by default.ReflectionClass::newLazyGhost().createUser(name: 'John', admin: true).match over switch — match is an expression, uses strict comparison, and does not fall through.$result = $input
|> trim(...)
|> strtolower(...)
|> ucfirst(...);
... syntax: array_map(strlen(...), $strings).fn($x) => $x * 2. Arrow functions capture by value, not by reference.use to capture outer variables. Prefer static function /
static fn when $this is not needed.catch (InvalidArgumentException) not catch (Exception).catch (\Throwable) at arbitrary depths. Use at application boundaries only (controllers, CLI entry
points, queue workers).throw is an expression (8.0+): $value ?? throw new InvalidArgumentException().throw new AppException('context', previous: $e) preserves the original cause.class AppException extends \RuntimeException {}
class NotFoundException extends AppException {}
class ValidationException extends AppException {}
\RuntimeException subtree for application errors. \LogicException subtree for programming errors (wrong
arguments, unimplemented methods)."user not found: invalid ID format".#[Deprecated] attribute (8.4+) on functions, methods, and constants to emit E_USER_DEPRECATED when called.#[NoDiscard] attribute (8.5+) on functions whose return value must be consumed. Use (void) cast to
intentionally suppress.finally for unconditional cleanup. But prefer RAII-style patterns (destructors, resource wrappers) when
possible."Hello, {$name}".sprintf() for complex formatting: sprintf('Item %d: %s', $id, $name).<<<'EOT') when no interpolation needed.str_contains(), str_starts_with(), str_ends_with() (8.0+) — never strpos with === false for substring
checks.mb_trim(), mb_ltrim(), mb_rtrim() (8.4+) for multibyte-safe trimming.mb_ucfirst(), mb_lcfirst() (8.4+) for multibyte-safe case conversion."".join() equivalent: implode(', ', $parts) for building strings from arrays.$arr = [1, 2, 3]. Never array().array_map(), array_filter(), array_reduce() for functional transforms.array_find(), array_find_key() (8.4+) — find first element matching a callback.array_any(), array_all() (8.4+) — existence/universal checks.array_first(), array_last() (8.5+) — get first/last value without resetting the internal pointer.$merged = [...$defaults, ...$overrides].[$first, $second] = $array or ['key' => $value] = $assoc.$result = match ($status) {
Status::Active => 'active',
Status::Inactive, Status::Suspended => 'inactive',
default => throw new \UnexpectedValueException("Unknown status: {$status->value}"),
};
match is an expression — it returns a value. Use instead of switch.===) — no type coercion.default unless provably exhaustive. Unmatched value throws
UnhandledMatchError.strlen(...), $obj->method(...), ClassName::method(...).fn) for single-expression closures — auto-captures by value.static fn, static function) when $this is not needed — saves memory, prevents accidental
binding.Closure::bind() and Closure::fromCallable() for advanced callable manipulation.Closure in property types (not callable).composer.json is the single source of truth for project metadata, dependencies, autoloading, and scripts.^ constraints for dependencies: "vendor/package": "^2.0".autoload-dev for test namespaces.my-project/
├── composer.json
├── composer.lock
├── src/
│ └── ... (PSR-4: App\)
├── tests/
│ ├── Unit/
│ ├── Integration/
│ └── bootstrap.php
├── config/
├── public/
│ └── index.php
└── var/
├── cache/
└── log/
src/ — application code, one class per file, PSR-4 mapped.tests/ — mirrors src/ structure with Unit/ and Integration/ separation.public/ — web root, single entry point (index.php).var/ — generated files (cache, logs). Git-ignored.vendor/ — Composer dependencies. Git-ignored.<?php
declare(strict_types=1);
namespace App\Service;
use App\Entity\User;
use App\Repository\UserRepository;
use Psr\Log\LoggerInterface;
Order: <?php tag, blank line, declare(strict_types=1), blank line, namespace, blank line, use imports (classes,
then functions, then constants), blank line, code. No leading backslash on imports.
See ${CLAUDE_SKILL_DIR}/references/packaging.md for composer.json templates and PSR-4 mapping.
PER Coding Style is the baseline. These are conventions, not tool configuration.
if, for, while, etc.).match arms, use lists.abstract/final, visibility, static, readonly.new Foo() — always use parentheses when instantiating (even without arguments), unless immediately chaining:
new Foo()->method().| and &. Parentheses for DNF without internal spaces.class NotFoundException extends AppException {}When writing PHP code: apply all conventions silently — don't narrate each rule. If an existing codebase contradicts a convention, follow the codebase and flag the divergence once.
When reviewing PHP code: cite the specific violation and show the fix inline. Don't lecture — state what's wrong and how to fix it.
Bad: "According to PHP best practices, you should use strict_types
declaration at the top of every file..."
Good: "Missing declare(strict_types=1)."
An Intelephense LSP server is configured for .php and .phtml files. Always use LSP tools for code navigation
instead of Grep or Glob. LSP understands PHP's namespace system, type inference, scope rules, and Composer autoload
boundaries — text search does not.
goToDefinition — resolves use statements, aliases, namespace pathsfindReferences — scope-aware, no false positives from string matcheshover — instant type info without reading source filesdocumentSymbol — structured output: classes, methods, constantsworkspaceSymbol — searches all namespaces and Composer dependenciesgoToImplementation — knows the type hierarchyincomingCalls — precise call graph across namespace boundariesoutgoingCalls — structured dependency mapGrep/Glob remain appropriate for: text in comments, string literals, log messages, TODO markers, config values, env vars, file name patterns, URLs, error message text — anything that isn't a PHP identifier.
When spawning subagents for PHP codebase exploration, instruct them to use LSP tools. Subagents have access to the same LSP server.
The coding skill governs workflow (discovery, planning, verification); this skill governs PHP implementation choices. The phpunit skill governs testing conventions — both are active simultaneously when writing PHP tests.
Strict types everywhere. Types on everything. If PHP can check it at compile time, make it do so.