agentsclimarketplace

Php expert

Skill Akayashuu/agent-skills/skills/php-expert

Use when writing, reviewing, or refactoring PHP (8.1+) — modelling data with enums/readonly value objects, choosing match over switch, designing exceptions vs result types, wiring PSR interfaces & Composer autoloading, or tightening types for PHPStan.From its SKILL.md

Install
npx -y skills add Akayashuu/agent-skills --skill php-expert

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.9 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it

PHP Expert

Overview

Modern PHP (8.1+) is statically-mindful and immutable-by-default when you let it be. The judgment is in making invalid states unconstructable — readonly value objects validated in their constructor, backed enums instead of loose constants, match instead of fall-through switch — and in depending on PSR interfaces rather than concrete libraries. The runtime is forgiving; PHPStan at level max is where the design pressure comes from. These are calls a linter alone won't make for you.

Quick Reference

GoalDoAvoid
Closed set of valuesbacked/pure enumclass const soup, magic strings
Immutable datapublic readonly promoted propssetters / mutable DTOs
Branch on a value, returnmatch (===, expression)switch (loose ==, fall-through)
Expected failure (parse/lookup)union return Ok|Errexceptions for control flow
Truly exceptional conditionthrow a typed \Exceptionerror codes / false sentinels
Reach a method as a callable$obj->m(...) first-class syntax[$obj, 'm'] / Closure::fromCallable
Self-documenting call sitenamed argumentspositional bool/null flags
Function that always exits: never return type: void + implicit return
Optional chainnullsafe $a?->b?->cnested if ($a !== null) ladders
Depend on a collaboratorPSR interface (LoggerInterface)a concrete class (Monolog\Logger)
Fake generics@template docblockmixed everywhere with no @var
Suppress a warningfix the cause / check first@ error-suppression operator
File of definitionsdeclare(strict_types=1); at toprelying on type juggling

Core Patterns

Readonly value object + enum — invalid instances can't exist. Validate in the constructor; "mutation" returns a new instance. Implement Stringable for a clean cast.

final class Money implements Stringable {
    public function __construct(
        public readonly int $amountMinor,   // cents, never floats for money
        public readonly Currency $currency, // backed enum
    ) {
        if ($amountMinor < 0) throw new InvalidArgumentException('negative');
    }
}

Runnable: examples/value-object.php

Compatibility gotcha: public readonly promoted properties work in 8.1, but the class-level readonly class Foo {} shorthand is 8.2+. If you target 8.1, mark each property — don't reach for readonly class.

Result type for expected failures — match forces both arms. Reserve exceptions for the exceptional; a parse miss is not exceptional, so return it.

/** @return Ok<int>|Err */
function parsePositiveInt(string $raw): Ok|Err { /* … */ }

Runnable: examples/result-type.php

match over switch. Strict ===, no fall-through, it's an expression, and an unmapped value throws \UnhandledMatchError instead of silently doing nothing. Over a pure enum, PHPStan flags a missing arm at analysis time. Use : never for the always-throwing helper so flow analysis knows the branch terminates.

$idempotent = match ($method) {
    HttpMethod::Get, HttpMethod::Put, HttpMethod::Delete => true,
    HttpMethod::Post => false,
};

Runnable: examples/match-exhaustive.php

Program to a PSR interface, not an implementation. Type-hint LoggerInterface (PSR-3), RequestInterface (PSR-7), ContainerInterface (PSR-11), ClientInterface (PSR-18) — any conforming library or test double drops in. And never let fire-and-forget analytics throw: swallow \Throwable at the edge so tracking can't break the request. The first-class callable $obj->track(...) captures the method without a wrapper lambda.

public function __construct(private readonly LoggerInterface $log) {}
public function track(string $event, array $props = []): void {
    try { $this->log->info("event:$event", $props); }
    catch (\Throwable) { /* analytics must not affect control flow */ }
}

Runnable: examples/psr-discovery.php

Generics via docblock. PHP has no native generics; encode them with @template so PHPStan checks element types. Named arguments document the call site.

/** @template T */
final class TypedStack {
    /** @param T $item */ public function push(mixed $item): void { /* … */ }
    /** @return T */      public function pop(): mixed { /* … */ }
}
/** @var TypedStack<string> $s */

Runnable: examples/template-generics.php

Common Mistakes

  • Class constants for a closed set — const STATUS_ACTIVE = 1 carries no type and no exhaustiveness. Use a backed enum; you get ::cases(), ::from(), and methods.
  • switch for value branching — loose ==, accidental fall-through on a missing break, and it's a statement not an expression. Prefer match.
  • Exceptions as control flow / false sentinels — a function returning User|false forces === false checks and loses the failure reason. Return a typed result for expected failures; throw only for the exceptional.
  • @ error suppression — it hides the warning and its cause, and survives into production. Check the precondition (isset, is_file) or let it throw.
  • mixed plus @var casts to silence PHPStan — mixed disables checking and /** @var X */ is an unchecked claim. Narrow with instanceof/is_* or model the type. At level max these are smells, not fixes.
  • No declare(strict_types=1); — without it "7" == 7 and f(int $x) coerces silently. Put it at the top of every file.
  • Nullsafe to paper over a null that shouldn't exist — ?-> is for genuinely optional chains, not for dodging a missing-data bug. Fix the type if it's never supposed to be null.
  • Reaching for readonly class — fine on 8.2+, a fatal parse error on 8.1. Know your floor.

When NOT to over-engineer

A Result type, @template generics, and deeply factored value objects have a readability cost. Reach for them when they prevent a class of real bugs — a public parser, a shared collection, money. For a private one-off transform, a plain typed function with a thrown exception is clearer than a hand-rolled Ok|Err. Don't add a PSR interface for a collaborator that will only ever have one implementation inside the same module.

Sources

What ships with it: 5 files

6.8 KB alongside SKILL.md, 5 of them executable

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.