あっぽログ
← 記事一覧に戻る

PHPのトレイトを使いこなす:コードの再利用をトレイトで実現する

トレイト(Trait)とは何か

PHPはシングル継承(単一継承)の言語です。つまり、1つのクラスが継承できる親クラスは1つだけです。これはシンプルで安全な設計ですが、複数のクラスで同じロジックを使い回したいときに不便を感じることがあります。

そこで登場するのが トレイト(Trait) です。トレイトはクラスに「ミックスイン」するための仕組みで、複数のクラスに同じメソッドやプロパティを持たせることができます。

<?php

trait Greetable
{
    public function greet(): string
    {
        return "こんにちは、私は {$this->name} です。";
    }
}

これだけ見ると「インターフェースと何が違うの?」と思うかもしれません。インターフェースはメソッドの宣言のみを強制しますが、トレイトはメソッドの実装ごと使い回せる点が大きな違いです。


基本的な使い方

トレイトは trait キーワードで定義し、クラスから use キーワードで取り込みます。

<?php

trait Greetable
{
    public function greet(): string
    {
        return "こんにちは、私は {$this->name} です。";
    }
}

class User
{
    use Greetable;

    public function __construct(public string $name) {}
}

class Admin
{
    use Greetable;

    public function __construct(public string $name) {}
}

$user  = new User("田中");
$admin = new Admin("鈴木");

echo $user->greet();  // こんにちは、私は 田中 です。
echo $admin->greet(); // こんにちは、私は 鈴木 です。

UserAdmin は共通の greet() メソッドを持っていますが、継承関係はありません。トレイトを使うことで、コードの重複を避けつつクラス設計を柔軟に保てます。


複数のトレイトを同時に使う

1つのクラスに複数のトレイトを取り込むことができます。

<?php

trait Timestampable
{
    public function getCreatedAt(): string
    {
        return date('Y-m-d H:i:s');
    }
}

trait SoftDeletable
{
    private bool $deleted = false;

    public function delete(): void
    {
        $this->deleted = true;
    }

    public function isDeleted(): bool
    {
        return $this->deleted;
    }
}

class Post
{
    use Timestampable, SoftDeletable;

    public function __construct(public string $title) {}
}

$post = new Post("PHPトレイト入門");
echo $post->getCreatedAt(); // 現在日時

$post->delete();
var_dump($post->isDeleted()); // bool(true)

カンマ区切りで複数のトレイトを use できます。機能ごとにトレイトを分割すると、単一責任の原則に近い設計になります。


メソッドの競合を解決する

複数のトレイトに同名のメソッドが存在する場合、そのままでは致命的エラーが発生します。insteadofas を使って競合を解決できます。

<?php

trait LoggerA
{
    public function log(string $message): void
    {
        echo "[A] {$message}\n";
    }
}

trait LoggerB
{
    public function log(string $message): void
    {
        echo "[B] {$message}\n";
    }
}

class Application
{
    use LoggerA, LoggerB {
        LoggerA::log insteadof LoggerB; // LoggerA の log を優先する
        LoggerB::log as logB;           // LoggerB の log を別名で使えるようにする
    }
}

$app = new Application();
$app->log("起動しました");  // [A] 起動しました
$app->logB("デバッグ情報"); // [B] デバッグ情報

insteadof でどちらを優先するかを決め、as で別名を付けることで両方使うことができます。


抽象メソッドをトレイトで宣言する

トレイト内で abstract メソッドを宣言することで、使用するクラスに実装を強制できます。

<?php

trait Validatable
{
    abstract protected function rules(): array;

    public function validate(array $data): bool
    {
        foreach ($this->rules() as $field => $rule) {
            if ($rule === 'required' && empty($data[$field])) {
                echo "{$field} は必須です。\n";
                return false;
            }
        }
        return true;
    }
}

class RegistrationForm
{
    use Validatable;

    protected function rules(): array
    {
        return [
            'name'  => 'required',
            'email' => 'required',
        ];
    }
}

$form = new RegistrationForm();
$form->validate(['name' => '田中', 'email' => '']); // email は必須です。

トレイトがテンプレートとなり、各クラスに具体的なルールを定義させる設計です。


トレイト・継承・インターフェースの使い分け

機能使いどころ
継承「is-a」の関係。DogAnimal である
インターフェース実装を強制したいメソッドの契約を定義する
トレイト複数のクラスで使い回したい実装を提供する

トレイトは継承の代替ではなく、あくまで「横断的な機能の共有」に使うものです。ログ出力・タイムスタンプ管理・ソフトデリートなど、どのクラスにも付け加えられる補助的な機能に向いています。


まとめ

  • トレイトは trait で定義し、クラスから use で取り込む
  • 複数のトレイトをカンマ区切りで同時に使える
  • 同名メソッドの競合は insteadofas で解決できる
  • abstract メソッドを使うと、取り込むクラスに実装を強制できる
  • 継承・インターフェースと組み合わせて柔軟な設計を実現できる

トレイトを上手に活用することで、DRY(Don't Repeat Yourself)の原則を守りつつ、スッキリしたクラス設計が可能になります。ぜひ実際のプロジェクトで試してみてください。

← 記事一覧に戻る