処理を再利用できるトレイト – trait

関連性のない複数のクラスで、共通の処理を使いたいときなどに便利なのがトレイト(Trait)です。例えば、次のプログラムを見てみましょう。

<?php
const TAX = 10;

// 商品を扱うクラス
class Item {
  private int $price;

  public function getTax(): int {
    return TAX;
  }
}

// サービスを扱うクラス
class Service {
  private int $service_price;
}

Itemというクラスと、Serviceというクラスが定義されていて、Itemには「getTax」という消費税を返すメソッドが定義されています。

しかし、Serviceにはこのメソッドが定義されていません。この時、Serviceでも「getTax」を使いたい場合にトレイトとして定義する方法があります。

トレイトを定義する

次のように追加しましょう。

// 消費税を扱うトレイト
trait Tax {
    public function getTax() {
        return TAX;
    }
}

「trait」という宣言で定義します。内容は通常のクラス定義と同様にメソッドなどを定義できます。

トレイトを利用する

定義したトレイトは「use」という宣言で利用することができます。Serviceクラスに追記してみましょう。

// サービスを扱うクラス
class Service {
    use Tax;

    private int $service_price;
}

これで、Serviceクラスで「getTax」メソッドが利用できるようになりました。次のようにして利用しましょう。

$service = new Service();
echo $service->getTax();

Itemクラスの方もトレイトを使うように変更したら次のような完成プログラムになります。

<?php
const TAX = 10;

// 消費税を扱うトレイト
trait Tax {
    public function getTax() {
        return TAX;
    }
}

// 商品を扱うクラス
class Item {
    use Tax;

    private int $price;
}

// サービスを扱うクラス
class Service {
    use Tax;

    private int $service_price;
}

$item = new Item();
echo $item->getTax();

$service = new Service();
echo $service->getTax();

複数のトレイトを使う

トレイトはカンマで区切って、複数同時に利用することもできます。

use Trait01, Trait02...;

ただしこの時、複数のトレイトに同じ名前のメソッドが定義されていると利用できなくなります。例えば次の例を見てみましょう。

trait Tax {
  public function getTax():int {
    return 10; // 日本の消費税
  }
}

trait ForeignTax {
  public function getTax(): int {
    return 20; // 外国の消費税
  }
}

日本と海外の消費税を扱うトレイトがあったとして、その両方に「getTax」メソッドが定義されています。これを両方使おうとすると、エラーになります。

class Item {
  use Tax, ForeignTax;
}

次のように、メソッド名が重複しているというエラーメッセージが表示されます。

Trait method getTax has not been applied, because there are collisions with other trait methods on Item

このような場合、「insteadof」を使ってどちらのメソッドを優先するかを指定することができます。

優先するメソッド名を定義する insteadof

例えば次の場合は、Taxの方のgetTaxを採用します。

class Item {
    use Tax, ForeignTax {
        Tax::getTax insteadof ForeignTax;
    }

    private int $price;
}

次のように、このクラスのインスタンスからgetTaxを利用すると、10と表示されます。

$item = new Item();
echo $item->getTax(); // 10が表示されます

ただし、このようにしてしまうと外国の消費税を得る方法がなくなってしまいます。そこで、こちらには別名を付加することができます。

重複したメソッドに別名を付加する as

次のように変更しましょう。

class Item {
  use Tax, ForeignTax {
    Tax::getTax insteadof ForeignTax;
    ForeignTax::getTax as getForeignTax;
  }

  private int $price;
}

これで、外国の消費税は「getForeignTax」というメソッド名に置き換わりました。次のように利用できます。

$item = new Item();
echo $item->getTax(), "\n"; // 10が表示されます
echo $item->getForeignTax(); // 20が表示されます