2020年も夏が終わり、涼しげな秋がやってきています。PHPの次期メジャーリリースである8.0のリリースに向けた開発はいよいよ終盤に差し掛かっています。PHP 8.0に採用される機能を決定するフィーチャーフリーズが計画通り8月4日に行われました。β版も計画に基づき8月6日に最初の版(β1)がリリースされ、以降2週間毎にβ版がリリースされています。計画では、ベータ版のリリースは3回の予定でしたが、JIT機能などに更なる改良を要することが判明したため、9月17日にβ4がリリースされています。そして、いよいよ、正式版リリース前の最終段階となるリリース候補版として、10月1日に最初のリリース候補版(RC1)が計画通りリリースされました。この後、2週間毎にリリース候補版が合計4回リリースされる予定です。最終的な8.0のリリース日は11月26日のままですが、今後の開発の進捗によっては予定が変更される可能性があります。
この連載では、これまで数回に渡りPHP 8の新機能について紹介してきました。今回は、前回の記事からフィーチャーフリーズまでの約2か月間で変更・追加となった機能について紹介します。
属性(attribute)の仕様再変更
属性(attribute)は、クラス、変数(プロパティ)、関数(メソッド)などの宣言に構造化された属性を付与する機能であり、通常のコメントと同様にPHPの実行コード自体には影響を与えませんが、静的解析ツールなどによるコード品質の確保やコンパイラによる判断を容易にしてくれる付帯情報(メタデータ)です。前回記事で紹介した通り、本機能は5月4日にattribute v2という名前の提案に基づき開発者による投票が行われ、PHP 8.0への採用が決まっています。その際は、「<<属性>>」という構文でしたが、その後、代案として「@@属性」という構文が提案され、7月1日に投票により採用(変更)が決まっています。しかし、9月2日まで行われた更なる構文の見直しを求める投票により50対11で再変更を行うことが可決されました。再変更が提案された背景は、名前空間に関係する一部のユースケースで問題が発見されたこと、後述するグルーピング機能がサポートされていないことなどです。なお、名前空間に関する問題は、別途提案された名前空間に関する改善により、解消しています。再変更の候補となったのは以下の5案です。
- @@属性
- #[属性]
- @[属性]
- <<属性>>
- @:属性
これらの中で、案2(#[属性])が賛成多数で採用されました。これまでの案「@@属性」に比べて優れている点は以下です。
- 終端デリミタ”]”があり、複数行にわたる記述ができる
- 一度に複数の指定を行うグルーピングが可能
2番目の点について、@@attr(“foo”) @@attr(“bar”) のような記述は、新構文では、
#[attr(“foo”)] #attr(“bar”)] となりますが、グルーピングを行うと、
#[attr(“foo”), attr(“bar”)] のようにカンマでつなげて一組のデリミタの中に複数の属性を記述できます。
文字列部分一致確認用関数の導入
ある文字列に特定の文字列が含まれるかどうかを確認するための関数str_contains()が導入されました。従来、同種の用途では、より汎用の関数であるstrpos()やstrstr()が使用されてきましたが、専用の関数を使用することでよりシンプルで分かりやすい記述が可能となります。構文は、str_containts(文字列,指定文字列) であり、文字列の中に指定文字列が存在する場合はtrueが返されます。また、同様の関数としてstr_starts_with()、str_ends_with()も追加されています。こちらは、それぞれ、文字列が指定文字列で始まる場合、または終わる場合にtrueを返します。簡単な使用例を以下に示します。
—
<?php
$s=”東京特許許可局”;
var_dump(str_contains($s,”特許”)); // bool(true)
var_dump(str_starts_with($s,”東京”)); // bool(true)
var_dump(str_ends_with($s,”許可局”)); // bool(true)
—
この関数は、シングルバイト文字列、マルチバイト文字列を問わず使用可能です。このため、mb_str_containts()のようなマルチバイト文字用の関数は定義されません。
ヌルセーフ演算子
PHPスクリプトでは、変数や戻り値の値が外部データベースの情報やユーザ入力に依存しており、クラスのプロパティやメソッドの戻り値がヌルでないかを確認する処理が必要となる場合があります。例えば、以下のようなクラスを考えてみましょう。
—
<?php
class moo { public $d=123;}
class foo {
public function get() {
return new moo();
}
}
$b = new foo();
—
fooクラスのインスタンス、getメソッドの戻り値、プロパティdのそれぞれがヌルになる可能性を考慮すると、プロパティdを出力するコードは例えば以下のようになります。
—
if ($b!==null) {
$a=$b->get();
if ($a!==null) {
$d=$a->d;
}
}
echo $d; // 123
—
PHP 8.0から導入されるヌルセーフ構文(?->)では、値を取得する際にヌルの確認が自動的に行われ、式全体がヌルとして評価されます。上記のコードは以下のように簡略化して記述できます。
echo $b?->get()?->d;
match式
PHP 8.0では、switch文をより簡便に記述できるmatch式が導入されます。switch文は以下のように変数の複数の値に応じて処理を分岐する際に使用されます。
—
<?php
$id = 1;
switch ($id) {
case 0:
$name = ‘Taro’;
break;
case 1:
$name = ‘Jiro’;
break;
case 2:
case 3:
$name = ‘Hanako’;
break;
default:
$name = “Anonymous”;
break;
}
echo $name;
—
上記のコードはシンプルな例ですが、複雑なコードになると、各ケースの最後のbreak文を忘れたり、出力変数(この場合は$name)への代入を忘れたりするケースがあります。
match式を使用すると上記のコードは以下のようなシンプルなコードに置き換えることができます。
—
$id=1;
echo match($id) {0=>’Taro’,1=>’Jiro’,2,3=>’Hanako’, default=>’Anonymous’};
—
match式では、「式の値=>値」の形で関係を記述します。式の値は複数の値をカンマで区切って指定することができます。値には式や関数を記述することも可能です。
mixed型の導入
PHP 8では、union型の他に、PHPのあらゆる変数型を表す汎用の型名としてmixed型が導入されます。mixedは、array、bool、callable、int、float、null、object、resource、stringのどれかであることを示します。mixedにはnullが含まれています。したがって、?mixedという表現は実質的にmixedと同じ意味となります。mixedは従来、PHPマニュアル等で任意の型を表す型名として使用されてきましたが、今回、実際にPHPスクリプトで使用可能になったということになります。
Mixedは関数(メソッド)の引数および戻り値における型指定、プロパティにおける型指定で指定可能です。一方、mixedは、(mixed)$fooのように型キャストでは使用できません。以下の簡単な例を見てみましょう。
—
<?php
class A {
public mixed $bar;
public function foo(int $value): mixed {}
}
class B extends A {
public mixed $bar;
public function foo(mixed $value): int {}
}
—
クラスBは、クラスAの派生クラスです。ここで、クラスAではメソッドfooの引数はintでしたが、クラスAを継承するクラスBではmixedを使用しており、型の自由度を拡大しています。逆にメソッドfooの戻り値は、クラスAではmixedですが、クラスBではintに自由度を縮小しています。
この関係は、PHP 7.4で導入された「共変戻り値と反変パラメータ」(詳細は本連載の第9回参照)に相当し、オブジェクト指向プログラミングの基本概念である「リスコフの置換原理(LSP)」に準拠しています。た、
また、プロパティ変数$barは、継承による型の自由度の縮小または拡大が許されていません。上記のコードでもクラスA、Bともにmixedと宣言しており、自由度を維持しています。
ここで、上記のスクリプトを以下のように書き換えてみましょう。ここでは、fooメソッドにおいてmixedとintの関係を入れ替え、プロパティ変数$barの型を変更しています。
—
<?php
class A {
public int $bar;
public function foo(mixed $value): int {}
}
class B extends A {
public mixed $bar;
public function foo(int $value): mixed {}
}
—
この場合、以下のように引数および戻り値の関係に関する致命的なエラーが発生します。
Fatal error: Declaration of B::foo(int $value): int must be compatible with A::foo(mixed $value): mixed in t19a.php on line 9
Fatal error: Declaration of B::foo(mixed $value): mixed must be compatible with A::foo(int $value): int in t19a.php on line 9
これは、このコードがLSPに準拠していないためです。また、このコードでは、プロパティ変数の型を変更しているため、以下のエラーも発生します。
Fatal error: Type of B::$bar must be int (as in class A) in t19a.php on line 10
mixedの導入によりプログラミング言語としての自由度は向上しますが、PHPスクリプトにおいては型指定を行わないケースと同様であるため、実際のユースケースは限定的と思われます。
次回は、PHP 8において発生する可能性がある過去のコードとの互換性に関する注意事項について記述します。