2020年12月にリリースされる予定であるPHP 8は、強力な新機能と言語の大幅な改善をもたらすでしょう。

多くのRFCがすでに承認および実装されているため、今日は、PHPをより速くより信頼性の高いものにする最もエキサイティングな新しい機能についてご説明します。

PHP 8はまだ開発中であるため、最終版がリリースされるまでに複数の変更が加えられる可能性があります。私たちはこれらの変更を追跡し、本記事を定期的に更新するようにします。そのため、PHP 8について重要なことをお見逃しがないように、本記事を時々再確認してください。

では、PHP 8の新機能と改善点は何でしょうか? 次のメジャーリリースであるPHP 8の最大の特長は何でしょうか?

それでは参りましょう。

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実行プロセスを表したものです。

基本的なPHP実行プロセス

基本的なPHP実行プロセス

では、OPcacheはどのようにしてPHPを高速化しますか?そして、実行プロセスのJITによる変更点は何でしょうか?

OPcache拡張機能

PHPはインタプリタ言語です。つまり、PHPスクリプトが実行されると、インタプリタはリクエストごとにコードを構文解析してコンパイルして実行します。これにより、CPUリソースを無駄に使用して、追加の時間が発生する場合があります。

ここでOPcache拡張機能が登場します。

「OPcacheは、プリコンパイルされたスクリプトのバイトコードを共有メモリに保管する為、リクエストごとにスクリプトを読み込んでパースせずに済むことによりPHPのパフォーマンスを向上させます。」

OPcacheを有効にすると、PHPインタープリターは、スクリプトが初めて実行されるときにのみ、上記の4ステップのプロセスを実行します。PHPバイトコードは共有メモリに保存されるため、低レベルの中間表現としてすぐに利用でき、Zend VMですぐに実行できます。

OPcacheを有効にしたPHP実行プロセス

OPcacheを有効にしたPHP実行プロセス

PHP 5.5以降、Zend OPcache拡張機能はデフォルトで利用可能です。サーバー上のスクリプトからphpinfo()を呼び出すか、php.iniファイルをチェックするだけで、正しく構成されているかどうかを確認できます(OPcache構成設定を参照)。

phpinfoページのZend OPcacheセクション

phpinfoページのZend OPcacheセクション

プリロード

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は開発者に多くの利点をもたらす可能性があります。

Nikita Popovは次のように述べています。

「JITコンパイラーの利点は次の(RFCで既に概説されている)とおりです。

  • 数値コードのパフォーマンスが大幅に向上します。
  • 一般的なPHP ウェブアプリケーションコードのパフォーマンスがやや向上します。
  • PHPが十分に高速になるため、さらに多くのコードをCからPHPに移動できるようになります。」

したがって、JITはWordPressのパフォーマンスに大きな改善をもたらすことはありませんが、PHP自体が大幅に改善し、多くの関数を直接に書き込むことができる言語になります。

一方、より複雑なものになるため、 メンテナンス、安定性、およびデバッグのコストの増加につながる可能性があるという短所があります。Dmitry Stogovは次のように述べています。

「JITは非常に単純なものですが、いずれにしても、PHP全体の複雑さが高まる為、新しいバグのリスクと、開発と保守のコストが増加する可能性があります。」

JITの導入についての提案は50対2の投票で可決しました。

PHP 8 is coming later this year.🚀 Check out our deep dive into the new features!Click to Tweet

PHP 8の改善点と新機能

JIT以外にも、PHP 8には多くの新機能と改善が期待できます。次のリストは、PHPの信頼性と効率を高める追加と変更を厳選したものです。

トレイトの抽象メソッドの検証

トレイトは、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型 は、単一の型ではなく、複数の異なる型の値を受け入れます。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でサポートされている全ての型をサポートします。

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をご参照ください。提案は満場一致で承認されました。

あなたのサイトのための、高速で安全で開発者に優しいホスティング会社をお探しですか?Kinstaは、開発者を優先して構築されており、様々なツールとパワーフルなダッシュボードを提供しています。当社のプラン一覧をご確認ください。

パラメータリストの末尾のカンマ

末尾のカンマは、あらゆるコンテキストのアイテムのリストに追加されるカンマです。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::classget_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をご参照ください。属性v2の実装は現在保留中であることにご注意ください。

新しいPHP関数

PHP 8は、いくつかの新しい関数をもたらします。

str_contains

PHP 8以前のバージョンでは、開発者が特定の文字列内で needleを検索したときに最もよく使用されたものはstrstrstrposでした。一方、どちらの関数も直感的でないもので、その使用が新人の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 checks if $needle is found in $haystack and returns true or false accordingly.
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機能は非常に一般的に必要とされるものであるため、SymfonyLaravelYiiFuelPHPまたはPhalconなどの多くの主要なPHPフレームワークで既にサポートされています。」

これらのおかげで、substrstrposなどの不十分で直感的でない関数の使用を回避できるようになります。どちらの関数でも大文字と小文字が区別されます。

$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を対象としたRFCの一部はまだ下書き段階にあるか、実装が保留中です。 これらのRFCのステータスが「実装済み」に変化したら、その内容をすぐに本記事に追加します。

PHP 8ので導入されるその他の承認された改善点の一覧を以下に示します。

  1. StringableインターフェースこのRFCでは、__to String() メソッドを実装するクラスに自動的に追加されるStringableインターフェースの導入が提案されています。 ここでの主な目的は、string|Stringableunion型を使用することです。
  2. ext/domでのDOM Living Standardの新しいAPIこのRFCでは、新しいインターフェースとパブリックプロパティを導入することにより、現在の DOM Living StandardPHPのDOM拡張機能に組み込むことが提案されています。
  3. 静的な戻り型:PHP 8では、self型 とparent型の並びに、static型も戻り型として使用できるようになります。
  4. 可変構文の微調整このRFCは、PHPの可変構文の古くからの不整合をなくします。
PHP 8 is coming later this year and it'll bring lots of changes and improvements. 🚀Check out our deep dive into the new features!Click to Tweet

まとめ

うわぁ!本記事では、PHP 8のリリースで予想されるすべての主要なの変更点と改善点について説明しました。最も期待されている新機能は、確かにジャストインタイム・コンパイラーですが、PHP 8にはそれ以外にも多くの機能が含まれるようです。

面白いものが承認されるとすぐに本リストに追加しますので、本記事を必ずブックマークしてください。🤓

次はあなたの番です。PHPの予定されている機能を試験する準備はできていますか?お好みの新機能は何ですか?下記のコメント欄でお知らせください。


この記事が面白いと思った方は、KinstaのWordPressホスティングプラットフォームも大好きでしょう。ウェブサイトをスピードアップし、当社のベテランのWordPressチームからの24時間365日のサポートを是非ご利用ください。Google Cloudを使用したインフラストラクチャは、自動スケーリング、パフォーマンス、およびセキュリティに重点を置いています。Kinstaの魅力をご案内させてください。当社のプランをご確認ください。