PHP 8が2020年11月26日に正式にGAリリースされました!
今回の新しいメジャーアップデートでは、多くの最適化と強力な機能が導入されます。本記事では、より良いコードを書き、より堅牢なアプリケーションを構築することができる最も興味深い変更点をご紹介します。
PHP JIT(ジャストインタイム・コンパイラー)
PHP 8の最も高く評価されている新機能は、ジャストインタイム(JIT)コンパイラーです。 JITとは何でしょうか?
RFC提案では、JITについて次のように説明しています。
PHP JITはOPcacheの一部として、しかしほぼ独立したものとして実装されます。PHPのコンパイル時に有効無効を設定します。有効にした場合、PHPファイルのネイティブコードがOPCacheの共有メモリに保存されるようになり、op_array→opcodes[].handler(s)はJITされたコードのエントリポイントへのポインターを保持します
では、どのようにしてJITに到達したのでしょうか?また、JITとOPcacheの違いは何ですか?
PHPのJITについて理解を深めるために、PHPのソースコードから最終結果までどの実行プロセスを簡単に見てみましょう。
PHPの実行は4段階のプロセスです。
- 字句解析/トークン化:まず、インタープリターがPHPコードを読み取り、トークンのセットを作成します。
- 構文解析:インタープリターは、スクリプトが構文規則に一致するかどうかを確認し、トークンを使用して、ソースコードの構造の階層を示す抽象構文ツリー(AST)を構築します。
- コンパイル:インタープリターはツリーを詳しく検討し、ASTノードをZend VMによって実行される命令の種類を決める数値識別子である低レベルZendオペコードに変換します。
- 翻訳:翻訳:オペコードが翻訳され、Zend VMにより実行されます。
次の画像は、基本的なPHP実行プロセスを表したものです。
では、OPcacheはどのようにしてPHPを高速化しますか?そして、実行プロセスのJITによる変更点は何でしょうか?
OPcache拡張機能
PHPはインタプリタ言語です。つまり、PHPスクリプトが実行されると、インタプリタはリクエストごとにコードを構文解析してコンパイルして実行します。これにより、CPUリソースを無駄に使用して、追加の時間が発生する場合があります。
ここでOPcache拡張機能が登場します。
OPcacheは、プリコンパイルされたスクリプトのバイトコードを共有メモリに保管する為、リクエストごとにスクリプトを読み込んでパースせずに済むことによりPHPのパフォーマンスを向上させます
OPcacheを有効にすると、PHPインタープリターは、スクリプトが初めて実行されるときにのみ、上記の4ステップのプロセスを実行します。PHPバイトコードは共有メモリに保存されるため、低レベルの中間表現としてすぐに利用でき、Zend VMですぐに実行できます。
PHP 5.5以降、Zend OPcache拡張機能はデフォルトで利用可能です。サーバー上のスクリプトからphpinfo()
を呼び出すか、php.iniファイルをチェックするだけで、正しく構成されているかどうかを確認できます(OPcache構成設定を参照)。
参照文献:WordPressのPHPのメモリ制限を改善する方法.
プリロード
OPcacheは最近、PHP 7.4で追加された新しいOPcache機能であるプリロードにより改善されました。プリロードは、「アプリケーションコードが実行される前に」指定されたスクリプトをOPcacheメモリに保存する機能ですが、通常のWebベースのアプリケーションの幅広いパフォーマンスの向上はもたらしません。
プリロードの詳細については、当社のPHP 7.4についての記事をご覧ください。
JITでは、PHPは一歩前進します。
JIT —ジャストインタイム・コンパイラー
オペコードが低レベルの中間表現であっても、マシンコードにコンパイルする必要があります。JITは「IR(中間表現)の新し形を導入しません」が、DynASM(コード生成エンジン用の動的アセンブラ)を使用して、PHPバイトコードから直接にネイティブコードを生成します。
つまり、JITは中間コードの重要な部分をマシンコードに変換します。コンパイルをバイパスするおかげで、パフォーマンスとメモリ使用状況を大幅に改善させるでしょう。
PHP JIT提案の共著者であるZeev Suraskyが、JITを使用すると計算がどれほど速くなるかを次のビデオで示しています。
一方、JITはWordPressのパフォーマンスを効果的に向上させるのでしょうか?
ライブウェブアプリ用のJIT
JIT RFCによると、ジャストインタイムのコンパイラー実装により、PHPのパフォーマンスが向上するようです。しかし、WordPressなどの実際のアプリで改善が実際に見られるのでしょうか?
初期のテストでは、JITがCPU消費の高いワークロードの実行を大幅に高速化することが示されていますが、RFCは次のように述べています。
…以前の試みと同様に、WordPressなどの実際のアプリは大幅に改善されないようです(opcache.jit=1235 326リクエスト/秒対 315リクエスト/秒)
プロファイリングと投機的な最適化を使用して、実際のアプリのJITを改善するための追加の取り組みは予定されています。」
JITを有効にすると、コードはZend VMではなくCPU自体により実行されるため、計算速度が向上します。WordPressなどのウェブアプリのパフォーマンスは、 TTFB、データベースの最適化、HTTPリクエストなどのその他の要素にもよるものです。
したがって、WordPressなどのアプリに関しては、PHPの実行速度が大幅に向上することは期待できません。それにもかかわらず、JITは開発者に多くの利点をもたらす可能性があります。
JITコンパイラーの利点は次の(RFCで既に概説されている)とおりです。
- 数値コードのパフォーマンスが大幅に向上します。
- 一般的なPHP ウェブアプリケーションコードのパフォーマンスがやや向上します。
- PHPが十分に高速になるため、さらに多くのコードをCからPHPに移動できるようになります
したがって、JITはWordPressのパフォーマンスに大きな改善をもたらすことはありませんが、PHP自体が大幅に改善し、多くの関数を直接に書き込むことができる言語になります。
一方、より複雑なものになるため、 メンテナンス、安定性、およびデバッグのコストの増加につながる可能性があるという短所があります。Dmitry Stogovは次のように述べています。
JITは非常に単純なものですが、いずれにしても、PHP全体の複雑さが高まる為、新しいバグのリスクと、開発と保守のコストが増加する可能性があります
JITの導入についての提案は50対2の投票で可決しました。
PHP 8の改善点と新機能
JIT以外にも、PHP 8には多くの新機能と改善が期待できます。次のリストは、PHPの信頼性と効率を高める追加と変更を厳選したものです。
コンストラクタのプロパティ昇格機能
PHPにおけるオブジェクトの使いやすさを向上させるための議論の結果として、 コンストラクタのプロパティ昇格機能のRFCでは、プロパティ宣言を単純化し、 冗長性をなくした新しい簡潔な構文が提案されました。
この提案は、昇格されたパラメータ、つまり public、protected、privateの可視性キーワードが前に付いたメソッドパラメータにのみを対象にしていします。
現在、プロパティをオブジェクトで使用できるように、何度か(少なくとも4回)繰り返して記入する必要があります。RFCの次の例を見てみましょう。
class Point {
public int $x;
public int $y;
public int $z;
public function __construct(
int $x = 0,
int $y = 0,
int $z = 0,
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
RFCの著者であるNikita Popov氏によると、プロパティ宣言、コンストラクタのパラメータ、プロパティの割り当ての3カ所で、プロパティ名を少なくとも4回は書かなければならないそうです。この構文は、特に多くのプロパティのある長いクラス名のクラスでは、使い勝手が悪いです。
このRFCでは、コンストラクタとパラメータの定義を統合することが提案されています。したがって、PHP 8では、パラメータの宣言のより使いやすい方法が採用されます。上記のコードは以下のように変更できます。
class Point {
public function __construct(
public int $x = 0,
public int $y = 0,
public int $z = 0,
) {}
}
以上です!このように、短く、読みやすく、エラーが起きにくいプロパティ昇格機能ができました。Nikitaは次のように述べています。
単純な構文変換なんです。一方、そのおかげで、特にバリューオブジェクトのために書かなければならないボイラープレートコードが減ります…
プロパティ宣言は、それらのプロパティを明示的に宣言したように変換され、リフレクションAPIを使って実行前にプロパティ定義をイントロスペクトすることができます(脱糖を参照)。
リフレクション(およびその他のイントロスペクション機構)は、 脱糖の後の状態を観察します。つまり、昇格されたプロパティは、明示的に宣言されたプロパティと同じように表示され、昇格されたコンストラクタの引数は、通常のコンストラクタの引数と同じように表示されます
// before desugaring
class Point {
public function __construct(public int $x = 0) {}
}
// after desugaring
class Point {
public int $x;
public function __construct(int $x = 0) {
$this->x = $x;
}
}
継承
昇格されたパラメータと一緒に継承を使用することに、特に制限はありません。いずれにしても、親クラスと子クラスのコンストラクタには特別な関係はありません。Nikitaは次のように述べています。
通常、メソッドは必ず親メソッドと互換性がなければならないと言われています。[…] しかし、このルールはコンストラクタには適用されません。つまり、あるコンストラクタは1つだけのクラスに属していて、親クラスと子クラスのコンストラクタには何の互換性も必要ないのです
下記はその一例です。
class Test {
public function __construct(
public int $x = 0
) {}
}
class Child extends Test {
public function __construct(
$x,
public int $y = 0,
public int $z = 0,
) {
parent::__construct($x);
}
}
昇格されたプロパティで許可されていないもの
昇格されたプロパティは、abstractコンストラクタやトレイトで使用できますが、いくつかの制限がありますので、ここで紹介します。
abstractコンストラクタ
昇格されたプロパティは、abstractクラスやインターフェイスでは使用できません。
abstract class Test {
// Error: Abstract constructor.
abstract public function __construct(private $x);
}
interface Test {
// Error: Abstract constructor.
public function __construct(private $x);
}
NULL可能な型
最も注目すべき制約の1つは、「NULL可能な型」に関するものです。これまでは、NULL可能でない形を使用していました。しかし、デフォルト値がnullの場合、その型は暗黙的にnull可能になります。しかし、プロパティ型では、昇格されたパラメータにはプロパティ宣言が必要であり、NULL可能な型は明示的に宣言しなければならないため、この暗黙の動作はありません。RFCの次の例をご覧ください。
class Test {
// Error: Using null default on non-nullable property
public function __construct(public Type $prop = null) {}
// Correct: Make the type explicitly nullable instead
public function __construct(public ?Type $prop = null) {}
}
呼び出し可能な型
呼び出し可能な型はプロパティでサポートされていないため、昇格されたプロパティで呼び出し可能な型を使用することはできません。
class Test {
// Error: Callable type not supported for properties.
public function __construct(public callable $callback) {}
}
var キーワードは使用不可
昇格されたパラメータに使用できるのは visibility キーワードだけなので、コンストラクタのプロパティを var
キーワードで宣言することはできません(RFC の次の例を参照)。
class Test {
// Error: "var" keyword is not supported.
public function __construct(var $prop) {}
}
重複は使用不可
昇格されたプロパティと明示的なプロパティを同じクラスで組み合わせることはできますが、プロパティを2回宣言することはできません。
class Test {
public string $prop;
public int $explicitProp;
// Correct
public function __construct(public int $promotedProp, int $arg) {
$this->explicitProp = $arg;
}
// Error: Redeclaration of property.
public function __construct(public string $prop) {}
}
可変パラメータは使用不可
宣言された型が、実際には配列である可変パラメータとは異なるためです。
class Test {
// Error: Variadic parameter.
public function __construct(public string ...$strings) {}
}
関連記事
コンストラクタのプロパティ昇格機能について詳しく知りたい方は、Nikita Popov氏へのインタビューをお聞きください。PHPにおけるオブジェクトの使いやすさの詳細については、こちらの記事とLarry Garfield氏へのインタビューをご覧ください。
トレイトの抽象メソッドの検証
トレイトは、「PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。」通常、複数のクラスで使用できるメソッドを宣言するために使用されます。
トレイトには、抽象メソッドを含めることもできます。これらのメソッドは、単にメソッドのシグネチャを特定しますが、メソッドの実装は、トレイトを使用してクラス内で行う必要があります。
PHPのマニュアルによると、
トレイトでは、静的なメンバーやメソッドを定義できます
したがって、メソッドのシグネチャが一致する必要があります。つまり、必要な引数の種類と数は同じである必要があります。
とにかく、RFCの作者であるNikita Popovによれば、シグネチャの検証は現在、限定的に強制されています。
- メソッドの実装がhttps://3v4l.org/SeVK3クラスの使用によって提供される最も一般的な場合では、シグネチャの検証が強制されません。
- 実装がhttps://3v4l.org/4VCIp親クラスからのものである場合は、強制されます。
- 実装がhttps://3v4l.org/q7Bq2子クラスからのものである場合は、強制されます
次のNikitaの例は、1番の(シグネチャが強制されない)場合の例です。
trait T {
abstract public function test(int $x);
}
class C {
use T;
// Allowed, but shouldn't be due to invalid type.
public function test(string $x) {}
}
そうは言っても、このRFCでは、実装メソッドがその起源に関係なく、トレイトの抽象メソッドと互換性がない場合、常に致命的なエラーを出すことが提案されています。
Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10
互換性のないメソッドシグネチャ
このRFCは満場一致で承認されました。
PHPでは、互換性のないメソッドシグネチャによる継承エラーは、エラーの原因に応じて、致命的なエラーまたは警告メッセージのいずれかを返します。
クラスがインターフェイスを実装している場合、互換性のないメソッドシグネチャは致命的なエラーを返します。オブジェクト インターフェイスのドキュメントによると:
インターフェイスを実装したクラスには、 そのインターフェイスで定義されているメソッドと、リスコフの置換原則(LSP)と互換性が取れたメソッドシグネチャを使う必要があります。そうしなければ致命的なエラーが発生します
以下は、インターフェースの継承エラーの例です。
interface I {
public function method(array $a);
}
class C implements I {
public function method(int $a) {}
}
PHP 7.4では、上記のコードは次のエラーを返します。
Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7
互換性のないシグネチャのある子クラスの関数は警告メッセージを返します。RFCの次のコードを参照してください。
class C1 {
public function method(array $a) {}
}
class C2 extends C1 {
public function method(int $a) {}
}
PHP 7.4では、上記のコードは単に警告メッセージを返します。
Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7
そこで、このRFCでは、互換性のないメソッドシグネチャに対して常に致命的なエラーを返すことが提案されています。PHP 8を使用すると、上記のコードで次の内容が表示されます。
Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7
マイナススタートの配列インデックス
PHPでは、配列の最初のインデックスが負(start_index < 0
)の場合は、それ以降のインデックスはゼロから始まります。(詳細については、array_fill
のドキュメントをご参照ください。)次の例をご覧ください。
$a = array_fill(-5, 4, true);
var_dump($a);
PHP 7.4では、結果は次のようになります。
array(4) {
[-5]=>
bool(true)
[0]=>
bool(true)
[1]=>
bool(true)
[2]=>
bool(true)
}
そこで、このRFCは、start_index
の値に関係なく、2番目のインデックスがstart_index + 1
になるように変更することが提案されています。
PHP 8では、上記のコードは次の配列になります。
array(4) {
[-5]=>
bool(true)
[-4]=>
bool(true)
[-3]=>
bool(true)
[-2]=>
bool(true)
}
PHP 8では、マイナススタートのインデックスの配列の動作が変わります。下位互換性の詳細については、RFCをご参照ください。
union型2.0
union型(union型) は、単一の型ではなく、複数の異なる型の値を受け入れます。PHPは既に、?Type
構文と特別なiterable
型を除いて、union型をサポートしています。
PHP 8以前のバージョンでは、次のRFCの例にのように、union型はphpdoc注釈でのみ指定できました。
class Number {
/**
* @var int|float $number
*/
private $number;
/**
* @param int|float $number
*/
public function setNumber($number) {
$this->number = $number;
}
/**
* @return int|float
*/
public function getNumber() {
return $this->number;
}
}
そこで、union型2.0の RFCでは、関数のシグネチャにunion型のサポートを追加することが提案されています。これにより、 インラインドキュメントに頼ることなく、union型を構文T1|T2|...
で表できるようになります。
class Number {
private int|float $number;
public function setNumber(int|float $number): void {
$this->number = $number;
}
public function getNumber(): int|float {
return $this->number;
}
}
RFCでは、Nikita Popovが次のように述べています。
言語でunion型をサポートすることにより、より多くの型情報をphpdocに頼ることなく関数シグネチャに移動することができ、多数の利点が得られます。
- 型は実際に強制されるため、ミスを早期に発見できる。
- エッジケースを見逃したり、仕様変更の際にドキュメントを更新し忘れたりする可能性が減る。
- 継承時にもリスコフの置換原則を適用できる。
- リフレクションから利用できる。
- phpdocより分量が減らせる
union型は、いくつかの例外がありますが、現在PHPでサポートされている全ての型をサポートします。
void
は関数が値を返さないことを意味するため、void
型がunion型の一部となることは決してできません。null
型はunion型の一部としてのみ有効な型で、単独で使うことはできません。- null許容型表記(
?T
)は、T|null
の省略形として今後も有効な構文ですが、union型と?T
表記を混ぜて使用することはできません。(?T1|T2
は全て不正な文法で、この場合はT1|T2|null
を使う必要があります。) - 多くの関数(例えば、
strpos()
、strstr()
、substr()
など)では、可能な戻り値の型の中にfalse
があるため、false
疑似型もサポートされています。
union型2.0の詳細については、RFCをご参照ください。
内部関数のTypeErrorの一貫性
不正なパラメーターが渡された場合、内部関数とユーザー定義関数の動作は異なります。
ユーザー定義関数ではTypeError
が返されていますが、内部関数の動作は複数の条件に応じてそれぞれ異なります。最もよくある動作は警告を出してnull
を返すことです。PHP 7.4の次の例を参照してください。
var_dump(strlen(new stdClass));
これにより、次の警告が表示されます。
Warning: strlen() expects parameter 1 to be string, object given in /path/to/your/test.php on line 4
NULL
strict_types
が有効になっている場合、または引数情報が型を指定している場合、動作は異なります。この場合では、型エラーが発生し、TypeError
が返されます。
この状況は、RFCの「課題」セクションで十分に説明されているように、多くの問題につながります。
これらの不整合をなくすために、このRFC proposesでは、パラメーター型が一致しない場合に内部パラメーター解析APIを常にThrowError
を生成するようにすることが提案されています。
PHP 8では、上記のコードは次のエラーを返します。
Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4
Stack trace:
#0 {main}
thrown in /path/to/your/test.php on line 4
throw式
PHPでは、throw
はステートメントであるため、式しか使用できない場所では使用できません。
このRFCでは、式が許可されているすべての場合で使用できるように、throw
ステートメントを式に変換することが提案されています。たとえば、矢印関数、null結合演算子、三項演算子およびelvis演算子など。
RFCの次の例を参照してください。
$callable = fn() => throw new Exception();
// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();
// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();
弱いマッピング
弱いマッピングは、キーが弱く参照される一連のデータ(オブジェクト)のことです。つまり、オブジェクトがガベージコレクションの対象となることが予防されません。
PHP 7.4では、オブジェクト自体の破棄を妨げないオブジェクトへの参照を保持する方法として、弱いリファレンスが導入されました。Nikita Popovが指摘したように、
生の弱いリファレンスの用途が限られており、実際には弱マッピングの方がはるかに一般的に使用されます。破棄時のコールバックが提供されていないため、PHPの弱いリファレンスの上に効率的な弱いマッピングを実装することはできません
したがって、このRFCではWeakMap
クラスが導入され、キーオブジェクトへのリファレンスがそれ以上ない場合、破棄してウィークマッピングから削除できるウィークマップキーとして使用されるオブジェクトが作成されます。
実行時間の長いプロセスでは、これによりメモリリークが防止され、パフォーマンスが向上します。RFCの次の例を参照してください。
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);
PHP 8を使用すると、上記のコードの結果は次のようになります。(実行中のコードについてはこちらを参照)
object(WeakMap)#1 (1) {
[0]=>
array(2) {
["key"]=>
object(stdClass)#2 (0) {
}
["value"]=>
int(42)
}
}
オブジェクトの指定を解除すると、キーはウィークマッピングから自動的に削除されます。
unset($obj);
var_dump($map);
これでは、結果は次のようになります。
object(WeakMap)#1 (0) {
}
弱いマッピングの詳細については、RFCをご参照ください。提案は満場一致で承認されました。
パラメータリストの末尾のカンマ
末尾のカンマは、あらゆるコンテキストのアイテムのリストに追加されるカンマです。PHP 7.2ではリスト構文の末尾のカンマ、PHP 7.3では関数呼び出しの末尾のカンマが導入されました。
PHP 8では、次の例に示すように、関数、メソッド、およびクロージャーを含むパラメータリストの末尾のカンマが導入されています。
class Foo {
public function __construct(
string $x,
int $y,
float $z, // trailing comma
) {
// do something
}
}
このRFCは58対1の投票で可決しました。
::class構文がオブジェクトで使用できるようになる
クラス名を取得するには、Foo\Bar::class
構文を使用します。このRFCでは、同じ構文をオブジェクトに展開して、以下の例のように、特定のオブジェクトのクラス名を取得できるようにすることが提案されています。
$object = new stdClass;
var_dump($object::class); // "stdClass"
$object = null;
var_dump($object::class); // TypeError
PHP 8では、$object::classはget_class($object)と同じ結果を返します。PHP 8では、$object::class
はget_class($object)
と同じ結果を返します。$object
がオブジェクトでない場合は、TypeError
例外が返されます。
提案は満場一致で承認されました。
属性v2
注釈とも呼ばれる属性は、オブジェクト、要素、またはファイルのプロパティを指定する構造化メタデータです。
PHP 7.4まで、クラスや関数などの宣言にメタデータを追加する唯一の方法はdoc-commentsでした。今回の属性v2のRFCでは、クラス、プロパティ、関数、メソッド、パラメーター、定数などの宣言に追加できる構造化された構文メタデータとして定義されるPHPの属性が導入されます。
属性は、参照する宣言の前に追加されます。RFCの次の例を参照してください。
<<ExampleAttribute>>
class Foo
{
<<ExampleAttribute>>
public const FOO = 'foo';
<<ExampleAttribute>>
public $x;
<<ExampleAttribute>>
public function foo(<<ExampleAttribute>> $bar) { }
}
$object = new <<ExampleAttribute>> class () { };
<<ExampleAttribute>>
function f1() { }
$f2 = <<ExampleAttribute>> function () { };
$f3 = <<ExampleAttribute>> fn () => 1;
属性は、doc-blockコメントの前にみ後にも追加できます。
<<ExampleAttribute>>
/** docblock */
<<AnotherExampleAttribute>>
function foo() {}
各宣言には1つ以上の属性があり、各属性には1つ以上の関連する値があります。
<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}
PHPの属性、使用例、および代替構文の詳細については、RFCをご参照ください。
名前付き引数
名前付き引数は、引数を渡すことを可能にするための新しい方法です。
名前付き引数は、位置ではなく、名前ベースで引数を渡すことを可能にします
関数に名前付きの引数を渡すには、その値の前にパラメータ名を加えるだけです。
callFunction(name: $value);
また、以下の例のように、予約済みのキーワードを使用することもできます。
callFunction(array: $value);
しかし、パラメータ名を動的に渡すことはできません。パラメータは識別子でなければならないため、次のような構文は使用不可です。
callFunction($name: $value);
このRFCの著者であるNikita Popov氏によると、名前付き引数にはいくつかの利点がありそうです。
まず第一に、名前付き引数は、その意味が記録されるため、より理解しやすいコードを書けます。RFCの以下の例を見れば、一目瞭然です。
array_fill(start_index: 0, num: 100, value: 50);
名前付き引数は、渡す順番は関係ありません。つまり、関数のシグネチャと同じ順序で関数に引数を渡さなくてもかまいません。
array_fill(value: 50, num: 100, start_index: 0);
名前付き引数は、位置を指定した引数と組み合わせることが出来ます。
htmlspecialchars($string, double_encode: false);
名前付き引数のもう一つの大きな利点は、変更したい引数だけを指定できることです。デフォルトの値を上書きしたくない場合は、デフォルトの引数を指定する必要はありません。RFCの次の例を見れば一目瞭然です。
htmlspecialchars($string, default, default, false);
// vs
htmlspecialchars($string, double_encode: false);
次のRFCの例のように、名前付き引数は、PHPの属性と組み合わせて使うことができます。
<<MyAttribute('A', b: 'B')>>
class Test {}
しかし、名前付き引数の後に位置付き引数を渡すことはできず、コンパイル時にエラーになります。同じパラメータ名を2回渡した場合も同様です。
名前付き引数はクラス宣言の際に便利です。コンストラクタは通常多くのパラメータを持つため、名前付き引数はより使いやすい方法でクラスを宣言することができます。
名前付き引数の詳細、制約事項、後方互換性、そしていくつかの例については、名前付き引数のRFCを参照してください。
null安全オペレータ
このRFCでは、完全なショートサーキット評価が可能なnull安全オペレータ$->
を提案します。
ショートサーキット評価では、最初の演算子がnull
と評価されない場合に限り、2 番目の演算子が評価されます。チェインの中の演算子がnull
と評価された場合、チェイン全体の実行が停止し、null
と評価されます。
RFCの次の例を参照してください。
$foo = $a?->b();
$a
が null の場合は、メソッド b()
は呼び出されず、$foo
はnull
に設定されます。
その他の例や例外、今後の範囲については、 null安全オペレータのRFCを参照ください。
文字列と数値の比較の合理化
以前のバージョンの PHP では、非厳密な比較演算子を用いた文字列と数値の比較は、文字列を数値にキャストし、その後整数か浮動小数の比較を行っています。この動作は非常に便利な場面もありますが、間違った結果を生むこともあり、バグやセキュリティ上の問題につながる可能性もあります。
RFCの次の例を参照してください。
$validValues = ["foo", "bar", "baz"];
$value = 0;
var_dump(in_array($value, $validValues));
// bool(true)
PHP 8 では、文字列と数値の比較をより合理的に行うことを目的とした文字列の数値の合理化が導入されました。Nikita Popov氏の言葉は次のとおりです。
このRFCでは、文字列と数値の比較をより合理的にすることを意図しています。 数値文字列を数値と比較する際には、文字列を数値に変換してから比較し、これは現在と同じ動作です。それ以外の場合は、数値を文字列に変換して文字列比較を行います
次の表は、以前のバージョンの PHP と PHP 8 での文字列と数値の比較の動作を比較したものです。
Comparison | Before | After ------------------------------ 0 == "0" | true | true 0 == "0.0" | true | true 0 == "foo" | true | false 0 == "" | true | false 42 == " 42" | true | true 42 == "42foo" | true | false
この変更による多くの影響や、文字列と数値の比較が PHP 8 でどのように変わるのかについては、Nikita Popov による公式 RFC を参照してください。
数値を含む文字列の合理化
PHPでは、数字を含む文字列は3つのカテゴリに分類されます。
- 数値文字列: 数値を含む文字列。前には空白がある場合があります。
- 先頭が数字の文字列: 最初の文字が数字の文字列で、最後の文字が非数字の文字列。
- 非数値文字列:前記のカテゴリーのいずれにも該当しない文字列。
数値文字列と先頭が数字の文字列は、実行する操作によって扱いが異なります。例えば、以下のようになります。
- 文字列から数値への明示的な変換(例:
(int)
および(float)
型のキャスト)は、数値文字列と先頭が数字の文字列を数値に変換します。数字でない文字列を明示的に数字に変換すると、0が生成されます。 - 文字列から数値への黙示的な変換(例:
strict_type
宣言なし)は、数値文字列と非数値文字列で異なる結果になります。数字ではない文字列から数字への変換を行うと、TypeError
が発生します。 is_numeric()
は、数字の文字列に対してのみtrueを返します。
文字列のオフセット、算術演算、増分・減分演算、文字列同士の比較、ビット演算でも結果は異なります。
このRFCでは、以下が提案されています。
さまざまな数値文字列モードを統一します。先頭と末尾の両方に空白を含む数字のみが許可されます。他のタイプの文字列は非数値文字であり、数値のコンテキストで使用するとTypeErrorが発生します
つまり、現在、E_NOTICE「A non well-formed numeric value encountered」が発しているすべての文字列は、先頭が数字の文字列に末尾のホワイトスペースしか含まれていない場合を除き、E_WARNING「A non-numeric value encountered」に再分類されます。また、現在E_WARNINGが発している様々なケースでは今後、
TypeError
が発生するようになります
PHP 8 における数値文字列関連の変更の詳細、コード例、例外の状況、後方互換性の問題などについては、RFCを参照してください。
match式2
新しいmatch
式はswitch
とよく似ていますが、より安全なセマンティクスを備えて、戻り値を可能にしています。
この2つの制御構造の違いを理解するために、RFCに掲載されている次のswitch
の例を考えてみましょう。
switch (1) {
case 0:
$result = 'Foo';
break;
case 1:
$result = 'Bar';
break;
case 2:
$result = 'Baz';
break;
}
echo $result;
//> Bar
次のmatch
式で、上のコードと同じ結果を得ることができます。
echo match (1) {
0 => 'Foo',
1 => 'Bar',
2 => 'Baz',
};
//> Bar
新しい match
式を使用することの大きな利点は、switch
文は緩やかな比較 (==
) を使い、予期しない結果になる可能性がありますが、match
式は厳密な比較(===
)で比較します。
また、match
式には複数のコンマ区切りの式を含めることができますので、構文がより簡潔になります(出典)。
$result = match ($x) {
// This match arm:
$a, $b, $c => 5,
// Is equivalent to these three match arms:
$a => 5,
$b => 5,
$c => 5,
};
その他の例やユースケースについては、match式 v2 のRFCおよび PHP のドキュメントを参照してください。
算術演算子とビット演算子における厳密な型チェック
以前のバージョンの PHP では、配列、リソース、オーバーロードされていないオブジェクトに算術演算子とビット演算子を適用することができました。しかし、その動作には一貫性がない場合がありました。
このRFCでは、Nikita Popov氏が簡単な例を挙げて、その動作がいかに変であるかを示しています。
var_dump([] % [42]);
// int(0)
配列やリソース、オーバーロードしていないオブジェクトに算術演算子やビット演算子を適用すると、異なる結果になることをNikita氏が次のように説明しています。
演算子 +, -, *, /, **
- 配列の演算対象でエラー例外を発生させます。(両方の演算対象が配列の場合は+を除く)
- リソースの演算対象を静かにリソースIDに変換して、整数化します。
- オブジェクトの演算対象を整数に変換しますが、警告を発生させます。
演算子 %, <<, >>, &, |, ^:
- 配列の演算対象を、空の場合は整数ゼロに、空でない場合は整数1に静かに変換します。
- リソースの演算対象を静かにリソースIDに変換して、整数化します。
- オブジェクトの演算対象を整数に変換しますが、警告を発生させます。
演算子~
- 配列、リソース、オブジェクトの演算対象に対してエラーの例外を発生させます。
演算子 ++ および –
- 演算対象が配列、リソース、オブジェクトの場合、静かに何もしません
PHP 8 では事情が変わり、すべての算術演算子およびビット演算子が適用された場合に同じ動作となります。
配列、リソース、オブジェクトの演算対象に対して TypeError
の例外を発生させます。
新しいPHP関数
PHP 8は、いくつかの新しい関数をもたらします。
str_contains
PHP 8以前のバージョンでは、開発者が特定の文字列内で needleを検索したときに最もよく使用されたものはstrstrとstrposでした。一方、どちらの関数も直感的でないもので、その使用が新人のPHP開発者にとって難しい場合があります。次の例をご覧ください。
$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
$pos = strpos($mystring, $findme);
if ($pos !== false) {
echo "The string has been found";
} else {
echo "String not found";
}
上記の例では、2つの値が同じ型であるかどうかもチェックする!==
比較演算子を使用しました。これにより、needleの位置が0の場合にエラーが発生しなくなります。
この関数は論理値 FALSE,を返す可能性がありますが、FALSEとして評価される値を返す可能性もあります。[…]この関数の返り値を調べるには ===演算子 を 使用してください
さらに、特定の文字列内の値を検索するヘルパー関数を提供するフレームワークが多いです。(例えば、Laravel Helpersのドキュメントをご参照ください。)
そこで、このRFCでは、文字列内を検索できる新しい関数str_contains
の導入が提案されています。
str_contains ( string $haystack , string $needle ) : bool
使い方はかなり簡単です。str_contains
は、$needle
が$haystack
にあるかどうかを確認し、それに応じてtrue
またはfalse
を返します。
したがって、str_contains
のおかげで、次のコードを書くことができるようになります。
$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
if (str_contains($mystring, $findme)) {
echo "The string has been found";
} else {
echo "String not found";
}
上記は、より読みやすく、エラーが発生しにくいものです。(実行中のコードについてはこちらを参照)。
本記事の執筆時点で、str_contains
は大文字と小文字を区別しますが、この特徴が将来的に変更される可能性があります。
str_contains
の提案は43対9の投票で可決しました。
str_starts_with()およびstr_ends_with()
str_contains関数に加えて、str_starts_with
及びstr_ends_with
の2つの新しい関数を使用しても、文字列内でneedleを検索ができます。
これらの新しい関数は、指定された文字列が別の文字列で始まるか/終わるかどうかをチェックします。
str_starts_with (string $haystack , string $needle) : bool
str_ends_with (string $haystack , string $needle) : bool
$needle
が$haystack
よりも長い場合、どちらの関数もfalse
を返します。
このRFCの作者であるWill Hudginsは次のように述べています。
str_starts_with
及びstr_ends_with
機能は非常に一般的に必要とされるものであるため、Symfony、Laravel、Yii、FuelPHPまたはPhalconなどの多くの主要なPHPフレームワークで既にサポートされています
これらのおかげで、substr
やstrpos
などの不十分で直感的でない関数の使用を回避できるようになります。どちらの関数でも大文字と小文字が区別されます。
$str = "WordPress";
if (str_starts_with($str, "Word")) echo "Found!";
if (str_starts_with($str, "word")) echo "Not found!";
実行中のコードについてはこちらをご参照ください。
このRFCは51対4の投票で可決しました。
get_debug_type
get_debug_typeは、変数の型を返す新しいPHP関数です。新しい関数はgettype
関数と非常によく似ていますが、get_debug_type
はネイティブの型名を返し、クラス名を解決します。
gettype()
は型チェックにあまり役立たないため、これは便利な改善点です。
RFCには、新しいget_debug_type()
関数とgettype()
の違いをよりよく理解できるために役立つ2つの例が用意されています。最初の例は、実行中のgettype
を示しています。
$bar = [1,2,3];
if (!($bar instanceof Foo)) {
throw new TypeError('Expected ' .Foo::class .', got ' .(is_object($bar) ? get_class($bar) : gettype($bar)));
}
PHP 8では、代わりにget_debug_type
を使用できます。
if (!($bar instanceof Foo)) {
throw new TypeError('Expected ' .Foo::class .' got ' . get_debug_type($bar));
}
次の表は、get_debug_type
及びgettype
の戻り値を示しています。
値 | gettype() | get_debug_type() |
---|---|---|
1 | integer | int |
0.1 | double | float |
true | boolean | bool |
false | boolean | bool |
null | NULL | null |
“WordPress” | string | string |
[1,2,3] | array | array |
「Foo \ Bar」という名前のクラス | object | Foo\Bar |
匿名クラス | object | class@anonymous |
その他のRFC
PHP 8ので導入されるその他の承認された改善点の一覧を以下に示します。
- Stringableインターフェース:このRFCでは、
__to String()
メソッドを実装するクラスに自動的に追加されるStringableインターフェースの導入が提案されています。ここでの主な目的は、string|Stringable
union型を使用することです。 - ext/domでのDOM Living Standardの新しいAPI:このRFCでは、新しいインターフェースとパブリックプロパティを導入することにより、現在の DOM Living StandardをPHPのDOM拡張機能に組み込むことが提案されています。
- 静的な戻り型:PHP 8では、
self
型 とparent
型の並びに、static
型も戻り型として使用できるようになります。 - 可変構文の微調整:このRFCは、PHPの可変構文の古くからの不整合をなくします。
PHP 8 のパフォーマンスベンチマーク
PHP 8がどのくらい速いのか気になる方のために、答えを用意しました。20のPHPプラットフォームや構成を対象に、7つの異なるPHPバージョン(5.6、7.0、7.1、7.2、7.3、8.0)をベンチマークしました。
WordPressやLaravelなど、PHPをサポートするほとんどのプラットフォームでPHP 8.0がチャンピオンでした。
PHP 8.0上のWordPressはPHP 7.4上よりも、1秒あたり18.4%多くのリクエストを処理できます。そしてLaravelも、PHP 7.3と比較して1秒間に8.5%多くリクエストを処理できます。
もし、あなたのウェブサイトやアプリがPHP 8.0に完全に対応しているのであれば、できるだけ早くサーバーの環境をPHP 8.0にアップデートした方が良いでしょう。あなたも、そしてあなたのユーザーも間違いなく、そのパフォーマンス上のメリットを享受できるでしょう。ただし、アップデートする前には、サイトを徹底的にテストしてください。
詳細なパフォーマンスデータ、その分析、またはきれいなグラフなどについては、当社のPHPベンチマークの記事をお読みください。
まとめ
うわぁ!本記事では、PHP 8で導入される最も興味深い最適化や新機能を取り上げました。最も期待されている新機能は、確かにジャストインタイム・コンパイラーですが、PHP 8にはそれ以外にも多くの機能が含まれるようです。
本記事を必ずブックマークしてください。🤓
次はあなたの番です。PHPの予定されている機能を試験する準備はできていますか?お好みの新機能は何ですか?下記のコメント欄でお知らせください。
コメントを残す