7月も半ばを過ぎ、暑い日々が続いています。引き続き、11月のリリースに向けてPHPバージョン8.4の開発が行われており、α版のリリースが開始されました。今回の記事では、PHP 8.4の最大の目玉の機能の一つであるプロパティアクセスフックについて紹介します。
PHP 8.4の開発状況
PHP 8.4の開発は11月21日の一般公開に向けて順調に進んでおり、計画通りα版のリリースが開始されています。今後、二週間毎にα版が合計3回リリースされる予定です。本稿執筆時点で、7月5日にα1版、7月18日にα2版がリリースされています。今後、8月1日にα3版がリリースされた後、8月13日にフィーチャーフリーズが宣言される予定です。この後は、新機能の追加は原則として行われず、バグ修正のみが行われることになります。現在、PHP 8.4で採用される機能に関する投票が6件行われており、これらの投票の結果をもって、実質的にPHP 8.4の機能が確定することになります。
プロパティアクセスのフック
PHP 8.4では、クラスのプロパティ変数へのget/setアクセスを変数毎に細かく定義できるようになります。従来、プロパティ変数のgetアクセス(プロパティの値を取得)およびsetアクセス(プロパティの値を設定)は、専用のメソッド(ゲッター・セッターと呼ばれる)を定義するか、定義されていないプロパティへのアクセス時にコールされる特殊メソッド__get()、__set()を定義する必要がありました。これらは、複数のプロパティ変数の一部のみに適用する場合でもすべての変数に対応する必要があるなど、記述が煩雑でした。
PHP 8.4では、プロパティ変数毎に専用のゲッター・セッターを簡易的に定義する仕組みとして、「プロパティアクセスフック」という仕組みが導入されます。このような仕組みは、Swift、C#やKotlinなどの他のプログラミング言語ではすでにサポートされており、特にKotlinの仕様を参考にしてPHPの機能が実装されています。
この機能は、有名な開発者であるNikita Popov氏がPHP 8.1向けに提案した内容に対して、レアなケースを含めて細部仕様を大幅に拡充したものになります。2024年4月にPHP開発者の投票によりPHP 8.4向けの新機能としての採用が決定されました。
従来、プロパティ変数に値を入力する際には、以下のようにゲッター・セッターをメソッドとして実装していました。
class User {
private string $first, $last;
public function __construct(string $first, string $last) {
$this->first = $first;
$this->last = $last;
}
public function getName(): string {
return $this->first . ' ' . $this->last;
}
public function setName(string $name): void {
[$this->first, $this->last] = explode(' ', $name, 2);
}
}
使い方は、例えば、以下となります。
$u = new User('Taro', 'Suzuki');
echo $u->getName(); // Taro Suzuki
$u->setName('Hanako Yamada');
echo $u->getName(); // Hanako Yamada
privateをアクセサとするプロパティ変数$first, $lastの値を設定、取得するためにゲッターとしてgetName()メソッド、セッターとしてsetName()メソッドを定義しています。
また、以下のように特殊なメソッド __get()および __set() をゲッター・セッターとして定義することも可能です。
class User {
private string $first, $last;
public function __construct(string $first, string $last) {
$this->first = $first;
$this->last = $last;
}
public function __get(string $name): string {
if ($name != 'name') {
throw new ValueError("未定義の変数");
}
return $this->first . ' ' . $this->last;
}
public function __set(string $name, string $value) {
if ($name != 'name') {
throw new ValueError("未定義の変数");
}
[$this->first, $this->last] = explode(' ', $value, 2);
}
}
この場合、以下のようにプロパティ変数へのアクセス(取得または設定)に対して自動的に __get()メソッド、 __set()メソッドがコールされ、protected変数の値を設定、取得することができます。
ここで、 ___set() および __get() メソッドは未定義のプロパティ変数がアクセスされる度にコールされます。$name 変数としてアクセスされた場合のみ、取得・設定の処理を行うため、変数名を確認し、意図と異なる場合は例外を発生させる処理を行っています。
利用方法の例は、以下のようになります。
$u = new User('Taro', 'Suzuki');
echo $u->name; // Taro Suzuki
$u->name = 'Hanako Yamada';
echo $u->name; // Hanako Yamada
同様のコードは、PHP 8.4で追加されるプロパティアクセスのフックを用いると以下のように記述できます。
class User {
public function __construct(private string $first, private string $last) {}
public string $name {
set (string $value) {
[$this->first, $this->last] = explode(' ', $value, 2);
}
get {
return $this->first . " " . $this->last;
}
}
}
プロパティ変数へのアクセスのフックでは、プロパティ変数の末尾(’;’)を波括弧( {…} )に置き換え、波括弧の内側に処理を記述します。上記のコードでは、setメソッド、getメソッドによるプロパティ変数$nameへのアクセスをフックするコードを記述しています。
なお、ここでは、PHP 8.0で導入されたコンストラクタのプロモーション機能を使って、コンストラクタを簡潔に記述しています。
setのフックで指定される変数 $value は、以下のように定義を省略することができます。また、省略記法”=>”を用いて、 “get => 戻り値;” のように記述を簡略化することができます。
class User {
public function __construct(private string $first, private string $last) {}
public string $name {
set {
[$this->first, $this->last] = explode(' ', $value, 2);
}
get => $this->first . " " . $this->last;
}
}
省略記法を利用することで、getフックを簡略に記述できることがわかります。
利用するコードは、以下のように特殊メソッド __get()、__set() を利用する場合と同じになります。動作は同じですが、特定のプロパティ変数のフックとして記述しているため、変数名の確認を行わないで済むなど、シンプルに記述できることがわかります。
$u = new User('Taro', 'Suzuki');
echo $u->name; // Taro Suzuki
$u->name = 'Hanako Yamada';
echo $u->name; // Hanako Yamada
次にsetフックの省略記法の例を見てみましょう。 省略記法をsetに適用した場合、当該プロパティの値がフックにより更新されます。このため、set省略記法は、仮想プロパティでは利用できません。
class User {
public string $name {
set => strtolower($value);
}
}
$user = new User();
$user->name = 'Hanako Yamada';
echo $user->name; // hanako yamada
この例えは、実プロパティ変数nameに値を代入すると、setフックによりstrtolower()関数により小文字に変換されて、代入されます。
配列の要素のようにプロパティの参照を取得する際には、&getフックを用います。以下は、配列の値を取得する際に、配列の遅延初期化を行う例となります。
class Test {
public array $list {
&get {
$this->list ??= ['a','b','c'];
return $this->list;
}
}
}
$obj = new Test();
echo $obj->list[1]; // 'b'
$obj->list[] = 'd';
echo count($obj->list); // 4
11行目でプロパティ変数の配列listの要素にアクセスした際、&getフックがコールされ、配列が[‘a’,’b’,’c’] への参照に初期化されます。 その後のアクセスは、通常の配列と変わりません。なお、&get フックを用いる場合、setフックを定義することはできません。
プロパティアクセスのフックは、インターフェイスにも対応しています。前の例を以下のように書き換えてみましょう。クラスUserはインターフェイスNameを実装しています。インターフェイスNameでは、プロパティ変数$nameが定義され、getへのフックが定義されています。クラスUserでは前の例と同様に実装が行われています。
interface Name {
public string $name { get;}
}
class User implements Name {
public function __construct(public string $first, public string $last) {}
public string $name {
set {
[$this->first, $this->last] = explode(' ', $value, 2);
}
get => $this->first . " " . $this->last;
}
}
ここで、11行目のgetのフックに関する実装をコメントアウトして無効にしてみましょう。すると、PHPスクリプトの実行時に以下のようなgetフックが未実装であるというエラーを発生します。
Fatal error: Class User contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Name::$name::get)
今回は、前回までに続き、11月のリリースに向けてα版のリリースが開始された PHP 8.4の開発状況について紹介し、目玉機能としてプロパティアクセスのフックについて紹介しました。次回以降もPHP 8.4のその他の変更点について紹介していきます。