php-specialist について
PHP 8.x コードの作成、レビュー、リファクタリングを行う開発者向けのスキルです。最新の言語機能、PSR 規格、Composer、静的解析、SOLID に沿った設計を扱います。
主な機能
- 01PHP 8.x 言語機能の指針
- 02PSR 規格と Composer の実践
- 03PHPStan と Psalm による静的解析
- 04PHP CS Fixer または Pint によるスタイル検査
- 05型安全性と SOLID 設計パターン
活用シーン
- 01インターフェースを定義して PHP 機能を開発
- 02型安全性を高めるコードのリファクタリング
- 03Composer 依存関係とコード品質の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pixel-process-ug/ superkit-agents --skill php-specialistインストール後、Claude Code で/php-specialistを入力して読み込まれたか確認します。
詳細
PHP スペシャリスト
概要
PSR 標準と SOLID 原則に準拠した、モダンで型安全かつ保守しやすい PHP 8.x コードを作成します。このスキルでは、PHP 8.0 から 8.4 で導入された言語機能、PSR 相互運用標準、Composer による依存関係管理、PHPStan と Psalm による静的解析、PHP CS Fixer と Pint によるコーディングスタイルの適用、そして実行時ではなくコンパイル時の型システムを活用して正確性を保証するアーキテクチャパターンまで、モダンな PHP ツールチェーン全体を扱います。
フレームワークを使用する場合も、単独で使用する場合も、PHP コードの作成、レビュー、リファクタリング時には常にこのスキルを適用してください。
複数フェーズのプロセス
フェーズ 1:環境の評価
composer.jsonのrequire.phpから PHP のバージョンを確認しますcomposer.jsonを確認し、オートロード方式(PSR-4 名前空間)を把握します- 静的解析の設定(
phpstan.neon、psalm.xml)を確認します - コーディング規約ツール(
pint.json、.php-cs-fixer.php)を特定します - 既存のパターン(列挙型、DTO、値オブジェクト、インターフェース)を一覧にします
停止 — PHP のバージョンとオートロード方式を把握するまで、コードを書いてはいけません。
フェーズ 2:設計
- 実装に先立ち、インターフェースと契約を定義します
- readonly プロパティを使って値オブジェクトと DTO を設計します
- 該当する場合は、ドメイン概念を値付き列挙型に対応付けます
- ドメインの例外階層を計画します
- 依存性注入のための境界を特定します
停止 — 重要な境界のインターフェースを定義するまで、実装してはいけません。
フェーズ 3:実装
- まずインターフェースを記述します — 具象クラスより先に契約を定めます
- コンストラクタープロモーション、readonly プロパティ、ユニオン型/インターセクション型を使って実装します
- switch より match 式を使い、明確さのために名前付き引数を使います
- 関数型の合成には、ファーストクラス callable 構文を活用します
- すべてのクラス境界で SOLID 原則を適用します
停止 — PHP ファイルで strict_types 宣言を省略してはいけません。
フェーズ 4:品質保証
- 達成可能な最高レベル(目標レベル 9)で PHPStan を実行します
- PHP CS Fixer または Laravel Pint でコーディングスタイルを徹底します
- 型の網羅性を確認します — 正当な理由なく
mixedを使ってはいけません - SOLID 原則への違反やコードの不吉な兆候がないか確認します
- Composer のオートロードが最適化されていることを確認します(
--classmap-authoritative)
PHP バージョン機能の選択表
| 機能 | 最低バージョン | 使用する場面 |
|---|---|---|
| コンストラクタープロモーション | 8.0 | コンストラクターパラメーターがあるすべてのクラス |
| 名前付き引数 | 8.0 | パラメーターが 3 つ以上ある関数、または真偽値フラグを使う場合 |
| Match 式 | 8.0 | あらゆる switch 文(厳密で、値を返す) |
| ユニオン型 | 8.0 | パラメーターが複数の型を受け付ける場合 |
| 値付き列挙型 | 8.1 | 値を持つ名前付き定数の集合 |
| Readonly プロパティ | 8.1 | 不変の DTO、値オブジェクト |
| ファイバー | 8.1 | 非同期フレームワーク(直接使うことはまれ) |
| ファーストクラス callable | 8.1 | 関数型の合成、array_map/filter |
| Readonly クラス | 8.2 | プロパティをすべて readonly にした DTO(省略形) |
| DNF 型 | 8.2 | 複雑なユニオン型とインターセクション型の組み合わせ |
| Override 属性 | 8.3 | 親メソッドのオーバーライド(安全性の確認) |
| プロパティフック | 8.4 | 別メソッドを定義せずに計算プロパティを実現する場合 |
モダンな PHP 8.x の機能
列挙型(PHP 8.1 以降)
// Backed enum with methods — replaces class constants and magic strings
enum OrderStatus: string
{
case Draft = 'draft';
case Pending = 'pending';
case Confirmed = 'confirmed';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
public function label(): string
{
return match ($this) {
self::Draft => 'Draft',
self::Pending => 'Pending Review',
self::Confirmed => 'Confirmed',
self::Shipped => 'Shipped',
self::Delivered => 'Delivered',
self::Cancelled => 'Cancelled',
};
}
public function isFinal(): bool
{
return in_array($this, [self::Delivered, self::Cancelled], true);
}
/** @return list<self> */
public static function active(): array
{
return array_filter(self::cases(), fn (self $s) => ! $s->isFinal());
}
}
Readonly プロパティとクラス(PHP 8.1/8.2)
// Readonly class — all properties are implicitly readonly
readonly class Money
{
public function __construct(
public int $amount,
public string $currency,
) {}
public function add(self $other): self
{
if ($this->currency !== $other->currency) {
throw new CurrencyMismatchException($this->currency, $other->currency);
}
return new self($this->amount + $other->amount, $this->currency);
}
public function isPositive(): bool
{
return $this->amount > 0;
}
}
コンストラクタープロモーション
class CreateUserAction
{
public function __construct(
private readonly UserRepository $users,
private readonly Hasher $hasher,
private readonly EventDispatcher $events,
) {}
public function execute(CreateUserData $data): User
{
$user = $this->users->create([
'name' => $data->name,
'email' => $data->email,
'password' => $this->hasher->make($data->password),
]);
$this->events->dispatch(new UserCreated($user));
return $user;
}
}
名前付き引数
// Improves readability for functions with many parameters or boolean flags
$user = User::create(
name: $request->name,
email: $request->email,
isAdmin: false,
sendWelcomeEmail: true,
);
// Particularly valuable with optional parameters
$response = Http::timeout(seconds: 30)
->retry(times: 3, sleepMilliseconds: 500, throw: true)
->get($url);
Match 式
// match is strict (===), exhaustive, and returns a value
$discount = match (true) {
$total >= 10000 => 0.15,
$total >= 5000 => 0.10,
$total >= 1000 => 0.05,
default => 0.00,
};
// Replaces switch with no fall-through risk
$handler = match ($event::class) {
OrderPlaced::class => new HandleOrderPlaced(),
PaymentFailed::class => new HandlePaymentFailed(),
default => throw new UnhandledEventException($event),
};
ユニオン型とインターセクション型
// Union type — accepts either type
function findUser(int|string $identifier): User
{
return is_int($identifier)
? User::findOrFail($identifier)
: User::where('email', $identifier)->firstOrFail();
}
// Intersection type — must satisfy all interfaces
function processLoggableEntity(Loggable&Serializable $entity): void
{
$entity->log();
$data = $entity->serialize();
}
// DNF types (PHP 8.2) — combine union and intersection
function handle((Renderable&Countable)|string $content): string
{
if (is_string($content)) {
return $content;
}
return $content->render();
}
ファーストクラス callable 構文(PHP 8.1 以降)
// Create closures from named functions
$slugify = Str::slug(...);
$titles = array_map($slugify, $names);
// Method references
$validator = Validator::make(...);
// Useful for pipeline / collection patterns
$activeUsers = collect($users)
->filter(UserPolicy::isActive(...))
->map(UserTransformer::toArray(...))
->values();
ファイバー(PHP 8.1 以降)
// Fibers enable cooperative multitasking — foundation for async frameworks
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('paused');
echo "Resumed with: {$value}";
});
$result = $fiber->start(); // Returns 'paused'
$fiber->resume('hello world'); // Prints: "Resumed with: hello world"
// Practical use: async HTTP client internals, event loops (Revolt, ReactPHP)
// Application developers rarely use Fiber directly — frameworks abstract it
PSR 標準
| PSR | 名前 | 関連性 |
|---|---|---|
| PSR-1 | 基本コーディング標準 | 基準:<?php タグ、UTF-8、名前空間/クラスの規約 |
| PSR-4 | オートローディング | composer.json で名前空間をディレクトリに対応付けます — 必須 |
| PSR-7 | HTTP メッセージインターフェイス | ミドルウェアパイプラインで使用する不変のリクエスト/レスポンスオブジェクト |
| PSR-11 | コンテナインターフェイス | 依存性注入コンテナの相互運用性 |
| PSR-12 | 拡張コーディングスタイル | PSR-2 に代わる標準:書式、空白、宣言 |
| PSR-15 | HTTP サーバーミドルウェア | MiddlewareInterface と RequestHandlerInterface |
| PSR-17 | HTTP ファクトリー | PSR-7 オブジェクト(RequestFactory、ResponseFactory)を作成 |
| PSR-18 | HTTP クライアント | 相互運用可能な HTTP クライアント向けの ClientInterface |
PSR-4 オートローディング
{
"autoload": {
"psr-4": {
"App\\": "app/",
"Domain\\": "src/Domain/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
ルール:名前空間の各セグメントは、ディレクトリに 1 対 1 で対応します。App\Http\Controllers\UserController は app/Http/Controllers/UserController.php に配置されます。
Composer の依存関係管理
基本コマンド
| コマンド | 用途 |
|---|---|
composer require package/name | 本番環境用の依存関係を追加します |
composer require package/name --dev | 開発環境用の依存関係を追加します |
composer update --dry-run | 変更内容をプレビューします |
composer why package/name | パッケージがインストールされている理由を表示します |
composer audit | 既知のセキュリティ脆弱性を確認します |
composer bump | バージョン制約をインストール済みのバージョンに更新します |
composer validate --strict | composer.json と composer.lock を検証します |
ベストプラクティス
composer.lockは必ずコミットします — 環境を問わず再現可能なインストールを実現します^(キャレット)制約を使用します:"laravel/framework": "^12.0"はマイナー/パッチ更新を許可します- 開発用の依存関係を分離します:テスト、静的解析、デバッグツールは
require-devに指定します - 既知の脆弱性を検出するため、CI で
composer auditを実行します - 本番環境では速度向上のため
composer dump-autoload --classmap-authoritativeを使用します
静的解析
PHPStan のレベル
| レベル | チェック内容 |
|---|---|
| 0 | 基本:未定義変数、不明なクラス、誤った関数呼び出し |
| 1 | + 未定義の可能性がある変数、$this の不明なメソッド |
| 2 | + すべての式に対する不明なメソッド($this だけではありません) |
| 3 | + 戻り値の型を検証 |
| 4 | + デッドコード、常に true/false となる条件 |
| 5 | + 関数呼び出しの引数の型 |
| 6 | + 型ヒントの不足を報告 |
| 7 | + ユニオン型を網羅的にチェック |
| 8 | + nullable 型を厳密にチェック |
| 9 | + 明示的な処理がない場合、mixed 型を禁止 |
PHPStan の設定
# phpstan.neon
parameters:
level: 9
paths:
- app
- src
excludePaths:
- app/Console/Kernel.php
ignoreErrors: []
checkMissingIterableValueType: true
checkGenericClassInNonGenericObjectType: true
includes:
- vendor/larastan/larastan/extension.neon # Laravel-specific rules
PHP CS Fixer / Pint
// .php-cs-fixer.php — for non-Laravel projects
return (new PhpCsFixer\Config())
->setRules([
'@PER-CS' => true,
'strict_types' => true,
'declare_strict_types' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'trailing_comma_in_multiline' => true,
])
->setFinder(
PhpCsFixer\Finder::create()->in([__DIR__ . '/src', __DIR__ . '/tests'])
);
Laravel プロジェクトでは、pint.json プリセットを使って Pint を使用します。Pint は Laravel 固有のデフォルト設定を備えた PHP CS Fixer のラッパーです。
PHP における SOLID 原則
| 原則 | 指針 | PHP の仕組み |
|---|---|---|
| S — 単一責任 | クラスを変更する理由は 1 つだけ | アクションクラス、小規模なサービス |
| O — オープン/クローズド | ソースを変更せずに動作を拡張する | インターフェイス、ストラテジーパターン、列挙型 |
| L — リスコフの置換 | サブタイプは基底型と置き換え可能でなければならない | 共変戻り値、反変パラメーター |
| I — インターフェイス分離 | クライアントは使用するメソッドだけに依存する | 小規模で目的を絞ったインターフェイス |
| D — 依存性逆転 | 具象ではなく抽象に依存する | コンストラクターインジェクション、インターフェイスのバインド |
依存性逆転の例
// Contract (abstraction)
interface PaymentGateway
{
public function charge(Money $amount, PaymentMethod $method): PaymentResult;
}
// Implementation (concretion) — can be swapped without changing consumers
final class StripeGateway implements PaymentGateway
{
public function __construct(private readonly StripeClient $client) {}
public function charge(Money $amount, PaymentMethod $method): PaymentResult
{
// Stripe-specific logic
}
}
// Consumer depends on abstraction only
final class ProcessPaymentAction
{
public function __construct(private readonly PaymentGateway $gateway) {}
public function execute(Order $order): PaymentResult
{
return $this->gateway->charge($order->total, $order->paymentMethod);
}
}
エラー処理パターン
カスタム例外階層
// Base domain exception
abstract class DomainException extends \RuntimeException {}
// Specific exceptions with factory methods
final class InsufficientFundsException extends DomainException
{
public static function forAccount(Account $account, Money $required): self
{
return new self(sprintf(
'Account %s has %d %s but %d %s is required.',
$account->id,
$account->balance->amount,
$account->balance->currency,
$required->amount,
$required->currency,
));
}
}
Result パターン(値としてのエラー)
/** @template T */
readonly class Result
{
/** @param T|null $value */
private function __construct(
public bool $ok,
public mixed $value = null,
public ?string $error = null,
) {}
/** @param T $value */
public static function success(mixed $value): self
{
return new self(ok: true, value: $value);
}
public static function failure(string $error): self
{
return new self(ok: false, error: $error);
}
}
// Usage — caller must handle both paths
$result = $action->execute($data);
if (! $result->ok) {
return response()->json(['error' => $result->error], 422);
}
型安全性パターン
Readonly クラスによるブランド型/不透明型
// Prevent accidental mixing of IDs from different entities
readonly class UserId
{
public function __construct(public int $value) {}
public function equals(self $other): bool
{
return $this->value === $other->value;
}
}
readonly class OrderId
{
public function __construct(public int $value) {}
}
// Compiler prevents: processOrder(new UserId(1)) when OrderId is expected
function processOrder(OrderId $orderId): void { /* ... */ }
PHPStan アノテーションによるジェネリックコレクション
/**
* @template T
* @implements \IteratorAggregate<int, T>
*/
final class TypedCollection implements \IteratorAggregate, \Countable
{
/** @param list<T> $items */
public function __construct(private array $items = []) {}
/** @param T $item */
public function add(mixed $item): void
{
$this->items[] = $item;
}
/** @return \ArrayIterator<int, T> */
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->items);
}
public function count(): int
{
return count($this->items);
}
}
アンチパターン/よくある間違い
| アンチパターン | 失敗する理由 | 代わりにすべきこと |
|---|---|---|
mixedを逃げ道として使う | 型安全性に穴が開きます | 共用体型またはジェネリクスで型を絞り込みます |
| 文字列に依存した型付けのコード | タイプミスによる実行時エラーが発生します | 名前付き定数にはバックドEnumを使います |
| 巨大クラス(責務が多すぎる) | テストが困難で、結合度が高くなります | Actionクラスに分割します |
| 静的解析を抑制する | 実際のバグが隠れてしまいます | 問題を修正し、説明を添えた場合に限り@phpstan-ignoreを追加します |
declare(strict_types=1)がない | 気付かないうちに型変換によるバグが発生します | すべてのPHPファイルに追加します |
| 配列形式のドメインデータ | IDEのサポートがなく、型安全性もありません | readonly DTOまたは値オブジェクトを使います |
サービスロケーター(ロジック内のapp()) | 依存関係が隠れ、テストが困難になります | コンストラクターインジェクションを使います |
\Exceptionを広範囲に捕捉する | 想定外のエラーまで握りつぶします | 具体的な例外型を捕捉します |
| 可変の値オブジェクト | 共有状態によるバグが発生します | readonlyクラスを使い、新しいインスタンスを返します |
composer auditを無視する | 既知の脆弱性が本番環境に残ります | CIで実行し、ビルド失敗として扱います |
| 深い継承(3階層以上) | 基底クラスが脆弱になります | コンポジションとインターフェースを優先します |
finalが付いていないクラス | 意図しない拡張が可能になります | デフォルトでfinalを付け、拡張を意図して設計した場合のみ外します |
安易な正当化を防ぐルール
- 「小さなスクリプトにすぎない」という理由で
declare(strict_types=1)を省略しないでください。すべてのファイルに追加します。 - より限定的な型を使えない理由を説明するコメントなしに、
mixedを使わないでください。 - コードが正しい理由を記した説明なしに、PHPStanのエラーを抑制しないでください。
- Laravelを使っている場合でも、ビジネスロジックでサービスロケーターパターン(
app())を使わないでください。 - 「実装が1つしかない」という理由で、重要な境界にインターフェースを用意するのを省略しないでください。実装は2つになります。
ドキュメントの参照(Context7)
最新のドキュメントを参照するには、mcp__context7__resolve-library-idの後にmcp__context7__query-docsを使います。取得したドキュメントは、記憶している知識より優先されます。
php— 言語機能、組み込み関数、またはPHP 8.x構文の場合composer— パッケージ管理、自動読み込み、またはスクリプト設定の場合
統合ポイント
| スキル | 連携方法 |
|---|---|
laravel-specialist | PHP 8.xの機能は、Eloquentのキャスト、Enum、readonly DTO、型付きコレクションを支えます |
senior-backend | SOLIDアーキテクチャ、インターフェース主導の設計、エラー処理パターン |
test-driven-development | 厳密な型アサーションを用いたPHPUnit/Pestのテスト |
clean-code | PHPレベルでのSOLID、DRY、コードの不吉な臭いの検出 |
security-review | 入力検証、型変換のリスク、依存関係の脆弱性 |
laravel-boost | ガイドラインとMCPツールによるAI生成PHPコードの品質向上 |
スキルの種類
FLEXIBLE — 作業の規模に応じてプロセスのフェーズを調整してください。単一の関数であれば、Phase 3と4のみで十分な場合があります。新しいモジュールやパッケージでは、4つすべてのフェーズに従ってください。規模にかかわらず必須なのは、declare(strict_types=1)、プロジェクトで設定されたレベルでのPHPStan準拠、そしてPSR-4自動読み込みです。