複数のトレイトの利用
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 52/91。
単一のトレイトも便利ですが、本当の力は1つのクラスで複数のトレイトを組み合わせたときに発揮されます。PHPでは必要な数だけトレイトを取り込むことができ、クラスに複数のソースからの機能を一度に持たせることができます。
複数のトレイトを使用するには、useキーワードの後にカンマで区切って並べるだけです。
<?php
trait Timestampable {
public function getTimestamp(): string {
return date('Y-m-d H:i:s');
}
}
trait Identifiable {
private string $id;
public function generateId(): void {
$this->id = uniqid('id_');
}
public function getId(): string {
return $this->id;
}
}
class Post {
use Timestampable, Identifiable;
public function __construct(public string $title) {
$this->generateId();
}
}
$post = new Post("Hello World");
echo $post->getId() . " - " . $post->getTimestamp();
出力:
id_6789abc123 - 2024-01-15 10:30:00Postクラスは、両方のトレイトのメソッドを持つようになりました。各トレイトは独自の機能を提供します。Timestampableは時間関連のメソッドを提供し、Identifiableは一意のIDを処理します。このアプローチにより、各トレイトが単一の責任に集中し、コードのモジュール性と再利用性が向上します。
2つのトレイトが同じ名前のメソッドを持っている場合、何が起こるでしょうか?それは、次のレッスンで詳しく見ていく競合の状況です。
チャレンジ
簡単製品に価格設定と在庫管理の両方の機能を持たせるために、複数のトレイトを組み合わせた製品管理システムを構築しましょう。各トレイトは単一の責任を担い、製品クラスは両方から機能を得ることになります。
コードは3つのファイルに分けて構成します:
Priceable.php— 価格設定機能を扱うPriceableという名前のトレイトを作成します。このトレイトには以下を含める必要があります:- プライベートプロパティ
$price(float) - 価格を保存するメソッド
setPrice(float $price) - 価格を返すメソッド
getPrice() - 価格を小数点以下2桁の
"$[price]"形式でフォーマットして返すメソッドgetFormattedPrice()
- プライベートプロパティ
Stockable.php— 在庫を管理するStockableという名前のトレイトを作成します。このトレイトには以下を含める必要があります:- プライベートプロパティ
$stock(integer) - 在庫数を保存するメソッド
setStock(int $quantity) - 現在の在庫を返すメソッド
getStock() - 在庫が0より大きい場合は
true、それ以外の場合はfalseを返すメソッドisInStock()
- プライベートプロパティ
main.php— 両方のトレイトファイルをインクルードし、PriceableとStockableの両方のトレイトを使用するProductクラスを作成します。コンストラクタのプロパティ昇格(constructor promotion)を使用して、パブリックな$nameプロパティを定義してください。コンストラクタは価格と在庫数も受け取り、トレイトのメソッドを使用してそれらを設定する必要があります。製品名、価格、在庫数の3つの入力を受け取ります。これらの値を使用して製品を作成し、次の4行を出力してください:
- 製品名
- フォーマットされた価格 (
getFormattedPrice()を使用) - 在庫数
- 在庫がある場合は
"Available"、ない場合は"Out of stock"
これは、複数のトレイトがどのように連携するかを示しています。Product クラスは、あるトレイトから価格設定機能を、別のトレイトから在庫管理機能を得ることで、各関心をきれいに分離したまま、それらを組み合わせてフル機能の製品を実現しています。
チートシート
クラスで複数のトレイトを使用するには、useキーワードの後にカンマで区切って記述します:
<?php
trait Timestampable {
public function getTimestamp(): string {
return date('Y-m-d H:i:s');
}
}
trait Identifiable {
private string $id;
public function generateId(): void {
$this->id = uniqid('id_');
}
public function getId(): string {
return $this->id;
}
}
class Post {
use Timestampable, Identifiable;
public function __construct(public string $title) {
$this->generateId();
}
}
$post = new Post("Hello World");
echo $post->getId() . " - " . $post->getTimestamp();
// 出力: id_6789abc123 - 2024-01-15 10:30:00
各トレイトは、クラスに独自の機能を提供します。このアプローチにより、各トレイトを単一の責任に集中させることができ、コードのモジュール化と再利用性が向上します。
自分で試してみよう
<?php
require_once 'Priceable.php';
require_once 'Stockable.php';
// TODO: PriceableとStockableの両方のトレイトを使用するProductクラスを作成してください
// public $nameプロパティにはコンストラクタのプロモーションを使用してください
// コンストラクタはname、price、stockを受け取り、トレイトのメソッドを使用してpriceとstockを設定する必要があります
class Product {
// TODO: ここでトレイトを使用してください
// TODO: $nameのコンストラクタプロモーションを実装し、
// トレイトのメソッドを使用してpriceとstockを設定するコンストラクタを実装してください
}
// 入力を読み込む
$name = trim(fgets(STDIN));
$price = floatval(trim(fgets(STDIN)));
$stock = intval(trim(fgets(STDIN)));
// TODO: 入力値を使用してProductインスタンスを作成してください
// TODO: 商品名を表示してください
// TODO: getFormattedPrice()を使用してフォーマットされた価格を表示してください
// TODO: 在庫数を表示してください
// TODO: 在庫がある場合は"Available"、ない場合は"Out of stock"を表示してください
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。