PHP Manual
/
新バージョン

PHP 8がリリースされました - 完全な概要

26. 11. 2020

Obsah článku

本日2020年11月26日、数年ぶりにメジャーバージョンアップした「PHP 8」がリリースされ、大胆な新機能が盛り込まれました。これは久しぶりの大きなアップデートであり、特別な記事に値する。

今回は、主な新機能と、旧バージョンとの構文やオプションの違いをまとめます。新機能のほとんどは後方互換性があり、お客様が楽しめる動作改善をもたらしています。

重要な情報: PHP 8 は現在、「機能凍結」フェーズにあり、新しい動作は追加されず、バグの修正のみが行われています。そのため、互換性に期待し、アプリケーションを完全にデバッグすることができます。

ユニオンタイプ

一般にPHPは近年、どんな変数にも何でも入る純粋な動的言語から、どの変数、パラメータ、引数、プロパティにどんなデータ型が入るかをあらかじめ知っている厳格な形式に移行しつつあります。data-types](/datove-types)の使用はまだ任意ですが、私は強いタイピングの使用を推奨し、私自身もすべてのプロジェクトでそれを使用しています。

ユニオン型は、複数の型の集まりを表現するもので、その中の任意の引数やプロパティを受け入れることができます。

例えば、こんな感じです。

function validatePsc(string|int $psc): bool
{
// 実装
}

変数 $pscvalidatePsc() 関数は、 string (文字列) と int (整数) のデータ型を受け付けます。

以前のバージョンの PHP 7.4 では、この表記は不可能で、通常 comment によって回避されていました。

/**
* @param string|int $psc
*/
function validatePsc($psc): bool
{
// 実装
}

しかし、このアノテーションコメントはPHPでは無視されるため(結局はコメントです)、PhpStanなどの外部ツールで追加チェックを行う必要があり、多くの開発者が無視していました。現在では、チェックはランタイム(アプリケーションの実行時)に直接行われるため、回避することはできません。

しかし、PHP ではバージョン 7 以降、ある種の共用型が知られており、主型も nullable にできる、つまり、主型に null という値を加えたものを受け入れることができるようになっていました。

これは二通りの書き方があり、それぞれ意味が異なる。

function setPhone(?string $phone): void
{
// 実装
}
// または
function setPhone(string $phone = null): void
{
// 実装
}
// または組み合わせ
function setPhone(?string $phone = null): void
{
// 実装
}

すべてのエントリで、電話機 int (整数) は string または null であるとされています。

  • 最初の表記では、常に値を渡す必要があります
  • 2番目の記法では、値を渡す必要はありません。何も渡されない場合、デフォルト値は null です(これはオプションの引数です)。
  • 3つ目の項目は、オプションの組み合わせで、2つ目の項目と同じような動作になります

ユニオン型を使用する場合、クエスチョンマーク付きの表記は使用できなくなり、例えば null データ型を厳密に定義しなければならなくなります。

function setPhone(string|int|null $phone = null): void
{
// 実装
}

電話番号は string, int または null でなければならなくなった。

ユニオン型にはまだ多くの使い道があり、上級開発者は特定のライブラリのドキュメントや実装を読むことになるでしょう。

JIT - スクリプト処理の高速化

JIT(ジャストインタイム)コンパイラは、スクリプトの複雑化(パースと理解)性能に大幅な改善をもたらす。ただし、この動作はウェブリクエストのコンテキストによって異なる場合があります。

Netteフレームワーク内のTracyバーでJITが有効になっているかどうかを確認できるようになりましたので、詳しくは【別記事】(https://stitcher.io/blog/php-jit)をご覧ください。

コンパイルについて一般的に言えることは、PHP はコードを前もって処理しようとするので、 特定のリクエストを処理する際に、物理的なスクリプトファイルを調べ、 それをパースし、解釈する必要がないということです。従来は、OPCacheという拡張機能(サーバーやホストがデフォルトで利用可能)で処理していましたが、処理速度が約半分に向上しました。

一般的な経験則として、遅いアプリケーションがある場合、コードに微細な最適化を行うよりも、特定のタスクを処理するのに適したアルゴリズムを選択する方が常に良い。通常、大きな遅延は、データベースとその遅いクエリの待ち時間、セッションの保存、ハードディスクの空き容量待ち、その他のハードウェア操作によって発生します。

Nullsafe演算子(オプションで連鎖)。

実際のアプリケーションでは、あるメソッドの戻り値の存在(nullではないこと)を確認した上で、別のメソッドを条件付きで呼び出す必要がある場合が非常に多いのです。三項演算子](/ternary-operator)はこれに最適ですが、1つの条件でしか動作しないため、入れ子にすることはできません。nullsafe演算子により、ネイティブにネストが可能です。

TIP: 実質的に同じ動作がすでに Latte テンプレートシステムでサポートされていますが、 ネイティブの PHP コードでこの種の構文を上書きしているので、 古いバージョンの PHP (PHP 7 以降) でも nullsafe 演算子を使用できます。 この修正を行った David に拍手です!

そのため、使い勝手が良い。

$orderId = $order?->getId();

orderId変数にはgetId()メソッドが返す値、または$order変数にnullという値が含まれていてgetId()メソッドが呼び出せなかった場合にはnull` が格納されます。

この種の問題は、PHP 7 では三項演算子による以下の構文で回避されました。

$orderId = isset($order) ? $order->getId() : null;

条件の可能性もある。

if (isset($order)) {
$orderId = $order->getId();
} else {
$orderId = null;
}

エントリーは、さらに呼び出しを書き込むことができます。ラテの資料】(https://latte.nette.org/cs/syntax#toc-volitelne…)からサンプルを取りましたが、完璧に表現されていますね。

$orderName = $order->item?->name;
// と同じです。
$orderName = isset($order->item) ? $order->item->name : null;

典型的な使い方は、より複雑な構造をテンプレートに列挙するときで、例えばLatteでは次のようになります(ドキュメントからサンプルを引用)。

{$user?->address?->street}
// means approx ($user !== null) && ($user->address !== null) ?$user->address->street : null
{$items[2]?->count}
// 置換約 ($items[2] !== null) ?アイテム[2]->count : null
{$user->getIdentity()?->name}
// replace approx $user->getIdentity() !== null ?$user->getIdentity()->name : null

実際のコードではこのようになります。たとえば、顧客のプロフィールを読んでその国を調べたい場合 (そして、本来であればセッションを介してデータベースにデータをうまく保存している場合)、 古い PHP ではこのようになります。

$country = null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}

これで1行に短縮することができるようになりました。

$country = $session?->user?->getAddress()?->country;

また、nullsafe演算子を使用することで、PHP 7では経験の浅い開発者が容易に発見できなかった様々なバグを防ぐことができます。

例えば、このエントリーは致命的なエラーを発生させます。

var_dump($invoice->getDate()->format('ワイエムディー') ?? null);
// return: Fatal error: uncaught Error: call to member function format() on null

正しい構文は以下の通りです。

var_dump($invoice->getDate()?->format('ワイエムディー'));
// 戻り値: null

名前付き引数

古き良き時代の PHP では、引数を持つ関数呼び出しは、対象となる関数で定義されたとおりの順序で引数を渡して記述する必要がありました。特に問題はありませんが、同じような値を持つパラメータを多数使用する場合、可読性が低下する可能性があります。あるいは、順番にn番目のパラメータまで渡そうとすると、すべてのオプションパラメータを前に渡さなければならず、可読性や前方互換性に悪影響が出る可能性があった。

例えば、多くの引数を持つネットー社の setCookie() 関数を想像してください。

public function setCookie(
string $name,
string $value,
$time,
string $path = null,
string $domain = null,
bool $secure = null,
bool $httpOnly = null,
string $sameSite = null
)

最初の3つの引数($name, $value, $time)は必須ですが、もし $httpOnly 引数を渡したい場合は、前の引数をすべて渡し、順番を正しく計算する必要がありました。

$http->setCookie(
'マイクッキー',
'馬が好きなデヴィッド',
'此処に於て',
null, // パス
null, // ドメイン
null, // セキュア
true
);

必要なければやりたくないだけでしょう。

エレガントな文章は、その後、次のようになります。

$http->setCookie(
name: 'マイクッキー',
value: '馬が好きなデヴィッド',
time: '此処に於て',
httpOnly: true
);

このタイプの構文では、ターゲット関数の引数の名前を決して変更しないことが要求される。なぜなら、引数は呼び出されたときにも書き込まれるからである。少なくとも開発者は、より良い名前をつけることができるようになるでしょう。

引数のうち1つだけを使いたい場合は、構文を組み合わせて、1行に凝縮することができます。

$http->setCookie('マイクッキー', '馬が好きなデヴィッド', '此処に於て', httpOnly: true);

最初の3つの引数は元の方法で渡され、次にオプションの引数 httpOnly が渡されます (名前が付けられているため)。

属性

JavaやC#などの主要な言語には、すでにいわゆるアノテーションと呼ばれる、他の言語構成要素にメタ情報を付加するための構文がネイティブで含まれています。

PHP では、この種の構文は長い間欠落しており、DOC コメントを使用することで回避してきました。これは、/** アスタリスクが 2 つあることを除けば、メソッドに対する古典的なコメントと言えます。

これらのコメントはスクリプト処理中は無視され、実行時にリフレクションによって解析し解釈するための特別なユーザーロジックを追加する必要があります。さらに、コメントの構文は必須ではなく、コンパイル時(スクリプトが実行される前に処理される時)に確認するのは非常に難しく、また、これを行うには通常のPHPツールキット以外の追加のツールを使用しなければなりません。

後方互換性を保つために、PHP は代替コメント表記に似た構文の属性を提供し、レガシーな PHP でスクリプトを実行しても壊れません。

オリジナルの表記法(例えば、Nette PresenterのInject依存関係で使用されている)。

final class HomepagePresenter extends BasePresenter
{
/** @inject */
public EntityManager $entityManager;
}

これで、コメントを削除してネイティブ属性を使用することができます。

use App\Attributes\Inject;
final class HomepagePresenter extends BasePresenter
{
#[Inject]
public EntityManager $entityManager;
}

属性はもはや単なるコメントの文字列ではなく、有効なPHPコードである物理的なクラスであることが非常に重要なのです。

これは素晴らしいことで、属性への入力を安全に検証することができます。また、属性を使用すると、実際にはそのコンストラクタを呼び出すことになり、そこで他のロジックを使用することができます。何でもかんでもアノテーションを使うDoctrineで、これがネイティブにサポートされることを期待しています。

そうすると、属性自体の実装は次のようになります。

#[Attribute]
class Inject
{
public string $value;
public function __construct(string $value)
{
$this->value = $value;
}
}

引数データ型やユニオン型などの言語機能のチェックなど、再び属性内で厳密なロジックを使用することができます。

マッチング表現

新しい言語構造体である match() は、古き良き switch() (私は使わないようにしています) を現代的に改良したもので、多くのクールな機能をもたらします (そのおかげで私は再び使い始めることができるのです)。

例えば、入力に基づいて変数の値を変更したい。

$pozdrav = match(bool $formal) {
true => 'こんにちは',
false => 'ハイ',
};

この構文の重要な新機能は、(古い switch のように) break を使う必要がないことで、この構文は一般にずっと経済的です。

同時に、条件内で複数の入力を一度に検証し(カンマで区切る)、場合によっては(何も満たされない場合)デフォルト値を返すことができる。

これは、例えばHTTPのステータスコードをエラーメッセージに書き換えるときに便利です(例外コードを扱うときに必ず役に立ちます)。

$message = match ($statusCode) {
200, 300 => null,
400 => 'みあたらない',
500 => 'サーバエラー',
default => 'ふめいステータスコード',
};

値の比較は === 演算子によって厳密に行われます (switch は == しか使いません)。このことからも、PHP が厳密な設計方針に従っていることがわかります。したがって、先ほどの場合、入力 '200' (数字を含む文字列) は受け付けられません。

defaultに値を指定せず、かつマッチするものがない場合は、UnhandledMatchError` がスローされる。

新しい構文では、式や関数呼び出しをマッチングに使用することもできます(条件のように動作します)。エラーが発生した場合には、例外を投げることができます (throw トークンが式になり、このように使用できるようになったためです)。

$message = match ($statusCode) {
200 => null,
$this->checkServerError($statusCode) => throw new ServerError(),
default => 'ふめいステータスコード',
};

コンストラクタへのプロパティの伝搬

これは、コンストラクタで直接エンティティやそのプロパティをすばやく簡単に定義するのに便利な構文上の糖類に過ぎません。

例えば、原体。

final class User
{
public string $name;
public function __construct(
string $name,
) {
$this->name = $name;
}
}

と略すしかない。

final class User
{
public function __construct(
public string $name
) {}
}

nameプロパティはstring` データ型に対して検証され、その値はパブリックプロパティであるためインスタンスから直接読み取ることができます。さらに、ネットでSmartObjectを使う場合(PHP 8ではむしろお勧めしません)、最初にそのゲッターメソッドを呼び出すことでプライベートプロパティにアクセスでき、この構文によって再び物事が単純化されます。

リターンタイプ static

以前は、メソッドの戻り値として self データ型を使うことができましたが、これは、まさにそれが定義されているクラスのインスタンスを返します。静的なデータ型は,一般に継承の場合でもこれを行うことができ,祖先ではなく,インスタンスが実行されたクラスのデータ型を返します.

例えば、こんな感じです。

class BaseEndpoint
{
public function getInstance(): static
{
return new static();
}
}

データ型が混在

mixed` 型を関数やメソッドの引数として使用できるようになりました。つまり、このメソッドは常に何らかの入力を受け付けなければならない(つまり、必須の引数である)。

少しでも可能なら、必ず直接データ型か、少なくともユニオンを使ってください。Mixedは、その関数が本当に何でも受け入れる場合にのみ有効です。実際には、任意の入力を受け付け、それを表示できなければならない様々なダンプ関数などで、この使い方は有用である。

mixed型は以下の型を受け付ける:stringintfloatnullboolarraycallableobjectresource`。

そして、Davidはその混合型を自分の関数に使用することになる。

function bdump(mixed $var): mixed
{
Tracy\Debugger::barDump($var);
return $var;
}

式としてのトークン投げ

これは、実際には fn() ラムダ関数が切り詰められたときや、三項演算子がチェックされたときに例外をスローすることができることを意味します。

$error = fn () => throw new \InvalidArgumentException('これは常にエラーを投げます。');
$userName = $user['名前'] ?? throw new \LogicException('ユーザーには名前が必要です。');

str_contains()関数

PHP はついに、デフォルトの文字列が部分文字列を含むかどうかをチェックするネイティブ関数を搭載しました。

例えば、こんな感じです。

if (str_contains('ホンジクは猫が好きだ。', 'キャッツ')) {
echo 'この機能では、猫を扱います。';
}

従来は、部分文字列の出現をstrpos関数で検証していた。

if (strpos('ホンジクは猫が好きだ。', 'キャッツ') !== false) {
echo 'この機能では、猫を扱います。';
}

関数 str_starts_with(), str_ends_with()

文字列が部分文字列で始まるか終わるかをチェックするための新しい関数のペアです。

str_starts_with('ホンジクは猫が好きだ。', 'ホンツィク'); // 真
str_ends_with('ホンジクは猫が好きだ。', 'のネコがいます。'); // 真

関数 get_debug_type()

既存の gettype 関数の出力を強化し、渡された変数の汎用型のみを返すようにしました。この関数は、例えば例外を投げるとき、有効でない入力があり、実際に何を渡したかをユーザーに伝えたいときに使用される。

クラス AppAppUser のインスタンスを含む変数で gettype() 関数を呼び出すと、関数は object を返すので、それがどのクラスなのかは分かりません。新しい関数 get_debug_type() は、クラスの名前を返します。

関数 get_resource_id()

説明: 変数から外部リソースの識別子を返す。

例えば、MySql データベースへの接続は、PHP では特別な resource データ型を使用して処理されますが、このデータ型に割り当てられた ID を知ることができるようになります。

**Historical note:**の略。

PHP の resource 型は、まだオブジェクトの使い方を知らない時期に作られたもので、 data 型 のようなものへの参照をどう渡すかを考えなければなりませんでした。将来的には、 resource は言語から完全に削除されることが予想されるので、この機能は使わない方がよい。

ext-jsonエクステンションは常に利用可能です

以前は、PHPはjsonをサポートせずにコンパイルすることができました。これで、json は常に利用できるようになります。そのため、composer.json ファイルから ext-json 依存関係を削除すれば、常に json が利用できることを知ることができるのです。

連結の優先順位

みたいなのを想像してください。

echo '総決算です。' . $a + $b;

数字の足し算が先に行われるのか、それとも変数 $a が先に文字列に追加され、その後新しい文字列全体が $b に追加されるのでしょうか?

足し算が先に行われると思われがちですが、それはいい加減な思い込みです。PHPでは実際にこのようなことが行われています。

echo ('総決算です。' . $a) + $b;

PHP 8 は予測可能な動作をするようになりました。

echo '総決算です。' . ($a + $b);

しかし、一般的には、式の区切りには常に括弧を使用したほうがよい。

安定した順序

PHP 8 より前のバージョンでは、文字列のソートはいわゆる不安定なアルゴリズムで行われており、同じ値 (あるいは同等の値) を持つ要素が入れ替わらないことを PHP が保証していませんでした。新バージョンでは、すべてのソート関数の動作が安定するように変更され、ソートが常に決定論的に行われ、常に同じ出力が得られるようになりました。

これにより、例えば、ユーザーの評価を関連性の高い順にランキングしていたのに、同じスコアの評価がある場合などが解決されます。これで、ソートするたびに同じ順番で表示され、連続的にスキップされることはありません。

その他の新機能

PHPは、その他にも多くの細かい新機能や改良が施されています。例えば、エラーの投げ方が違ってきます(でも、エラーのないコードを書いている私たちには関係ありませんよね)。

変更点の全リストは、公式ドキュメントやRFCポストでいつでも確認することができます。

新しいPHPに求めるもの

例えば、あるメソッドが識別子の配列を返すとき、まだ getIds(): array とだけ指定しなければならず、 getIds(): int[] のようなものがあればよりよいでしょう。もしかしたら、近いうちにこれが見えてきて、強力な型チェックが完成するかもしれませんね。

その他のリソース

David Grudlは、Posobotの新しいものについて、素晴らしい講演を行いました。録画で見ることをお勧めします。

.

これは、Davidの講義に感謝するためで、この記事のために、彼の講義からいくつかの情報を引き出したからである。特に、ネッテのPHP8への移行や、PHPの裏技的なものを紹介します。

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

V jiných jazycích

1.
11.
Status:
All systems normal.
2024