# ShowNote:【オンライン】PHPerによるPHPerのための「PHPのニュースや記事を語り合う」TechCafe @ RAKUS 10月
## イベントページはこちら
https://rakus.connpass.com/event/189017/
## PHP8の変更点まとめ
- **[What's new in PHP 8](https://stitcher.io/blog/new-in-php-8)**
### 前回の資料
https://hackmd.io/@hrxVDayfRGeTR-1JBV3QLA/Hk5oOkp4w
# 先に言っておきます。今回は結構ニッチなネタが多いです。
# が、できるだけ皆さんに満足してもらえるように頑張るので温かい目で見守ってください。
### 新機能(前回触れなかったものの)
- **[Constructor property promotion](https://stitcher.io/blog/new-in-php-8#constructor-property-promotion-rfc)**
- クラスのプロパティ定義・コンストラクタをシンプルに記述できるようになる
- https://stitcher.io/blog/constructor-promotion-in-php-8
- VOやDTOなど、コンストラクタでの処理が引数で初期化するだけになりがちなシンプルなクラスの場合、コード量が減る
- コードの視認性が上がるので良さそう
- **[Throw expression](https://stitcher.io/blog/new-in-php-8#throw-expression-rfc)**
- アロー関数や三項演算子、NULL合体演算子などの式しか許されない場所から例外を投げれるようにする
- match式と一緒に使うと、特定の条件の場合に例外を投げる、といった処理もシンプルに記述できるようになる
- **[New ``static`` return type](https://stitcher.io/blog/new-in-php-8#new-static-return-type-rfc)**
- [遅延静的束縛](https://www.php.net/manual/ja/language.oop5.late-static-bindings.php)の考え方では、「self」はそのメソッドが属するクラス、「static」はそのメソッドが実際に実行される際のクラスを指すが、戻り値にはこれまで「self」しか指定できなかった。
- 遅延性的束縛とは
```php
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test_self() {
self::who();
}
public static function test_static() {
static::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test_self(); // A
B::test_static(); // B
?>
```
- 今回提案されたのは、次のような矛盾に対する解決策
```php
<?php
class A {
public static function createNewInstance() : self {
return new static();
}
}
class B extends A {
}
function some_func(B $x) {}
var_dump(A::createNewInstance()); // Aのインスタンス
var_dump(B::createNewInstance()); // Bのインスタンス(selfの定義通りだとAになるはずだが…)
some_func(B::createNewInstance());
// selfの定義通りだとAなのでIDEなどで静的解析する際に、警告になってしまうことがあった
?>
```
- **[New ``Stringable`` interface](https://stitcher.io/blog/new-in-php-8#new-stringable-interface-rfc)**
- ``__toString()``を実装していることを示すインタフェース
- ログ出力やメッセージ出力を担うメソッドにオブジェクトを渡したい場合など、``strict_types=1``を指定している時は適切な記述方法が無かった
```php
declare(strict_types=1);
class Foo
{
public function __toString(): string
{
return 'foo';
}
}
function bar(string $stringable) { }
function baz(string | Stringable $stringable) { }
bar(new Foo()); // Uncaught TypeError
baz(new Foo()); // OK
```
- **[Weak maps](https://stitcher.io/blog/new-in-php-8#weak-maps-rfc)**
- キーにオブジェクトを持つことができるKey-Value型のデータ構造
- PHPの配列(および連想配列)は、キーには数値か文字列しか持てない
- キーに対するメモリの管理の仕方が「弱参照」と呼ばれる持ち方をしており、GCを妨げない
- …っていうとわかりにくいので、「キーに指定したオブジェクトが使用されなくなる(スコープから抜ける、unsetされる…)と、valueもろとも解放してくれるもの、と思うとわかりやすいかも
- APIからの取得値、DBからの取得値などを一連の処理の中で何度も使いまわす際(特にバッチ処理など)に、一度取得した値をキャッシュしておいて使いまわす際などに、シンプルに実装できる。
- ↓WeakMapの挙動例
```php
<?php
$cache_array = [];
$cache_weakmap = new WeakMap;
function proc() {
global $cache_array;
global $cache_weakmap;
$cache_array[1] = "x";
$keyObj = new stdClass;
$cache_weakmap[$keyObj] = "x";
var_dump($cache_array);
// array(1) { [1]=> string(1) "x" }
var_dump($cache_weakmap);
// object(WeakMap)#1 (1) {
// [0]=>
// array(2) {
// ["key"]=>
// object(stdClass)#2 (0) {
// }
// ["value"]=>
// string(1) "x"
// }
// }
}
proc();
var_dump($cache_array);
// array(1) { [1]=> string(1) "x" }
var_dump($cache_weakmap);
// object(WeakMap)#1 (0) {
// }
```
- **[Trailing comma in parameter lists](https://stitcher.io/blog/new-in-php-8#trailing-comma-in-parameter-lists-rfc)**
- 引数リストの末尾にカンマを付ける事が許容された
- 引数が多い場合など改行して記述することがあるが、新しく引数を追加した場合に元々最後に定義されていた行にカンマを付ける必要があるので、Diffを見た時に不要な差分が見えてしまっていた
```php
class Foo {
private function __construct(
$arg1,
$arg2,
$arg3, ←これが許容されるようになった
) {}
}
```
- **[New ``str_contains()`` function](https://stitcher.io/blog/new-in-php-8#new-str_contains()-function-rfc)**
- 文字列に特定のワードが含まれているかどうかを判定するメソッド(が、**いまさら**登場)
- これまでは、下記のように記述していた
- ``strpos('Hello world', 'world') !== false``
- しかしこれは直感的ではない
- strposは「文字列の出現位置を調べる関数」なので
- 従来はフレームワークなどでラップしたメソッドなどが用意されていた
- 今後は、``str_contains('Hello world', 'world') ``と、意味的にも分かりやすい記述ができるようになる
- マルチバイト文字でも期待通り動作する
- **[New ``str_starts_with()`` and ``str_ends_with()`` functions](https://stitcher.io/blog/new-in-php-8#new-str_starts_with()-and-str_ends_with()-functions-rfc)**
- 文字列が特定のワードで始まる/終わるかどうかを判定するメソッド(が、**いまさら**登場)
- 従来はこんな書き方をしていた
- 始まる判定
- ``substr($haystack, 0, strlen($needle)) === $needle``
- ``strpos($haystack, $needle) === 0``
- 終わる判定
- ``substr($haystack, -strlen($needle)) === $needle``
- ``strpos(strrev($haystack), strrev($needle)) === 0``
- これからはこう書ける
- ``str_starts_with($haystack , $needle)``
- ``str_ends_with ($haystack , $needle)``
- **[Allowing ``::class`` on objects](https://stitcher.io/blog/new-in-php-8#allowing-::class-on-objects-rfc)**
- 地味に便利になる変更
- オブジェクトのクラス名を取得する方法
- これまでは``get_class($object)``と記述
- これからは``$object::class``と記述できる
- **[New ``get_debug_type()`` function](https://stitcher.io/blog/new-in-php-8#new-get_debug_type()-function-rfc)**
- これも地味に便利になる変更
- 引数の種類によらず、型を返してくれるメソッド
- これまでは、次のような記述をしなければならなかった
- ``is_object($bar) ? get_class($bar) : gettype($bar)``
- これからはこう書ける
- ``get_debug_type($bar)``
- などなど
### Breaking Changes(旧バージョンとの互換性の無い変更。主催の主観で重要そうなものをピックアップ)
- ちなみに、PHP8が正式リリース後にマイグレーションガイドが作成されるはずなので、実際にバージョンアップされる際はそちらを参考されたし
- PHP7.3 -> PHP7.4
- https://www.php.net/manual/ja/migration74.php
- **当たり前ですが、``match``が予約語になります。**
- ``$foo->match();``みたいな関数は結構あるんじゃないでしょうか?
- **当たり前ですが、``#[``から始まる記述はコメントとみなされなくなります。**
- ``#``はコメント接頭辞
- もしこんなコメント書いてたらエラーになるようになります(レアケースだろうけど)
- ``#[TODO] ここ修正``
- **[Consistent type errors](https://stitcher.io/blog/new-in-php-8#consistent-type-errors-rfc)**
- ``strlen``などの組み込み関数に、不正な型の値を渡すと、**常にTypeError例外が発生する**ようになる変更
- 元々は、``declare(strict_types=1);``を指定した時のみ上記の動きになっていた
- strict_typesを指定しない場合は、Warningと共にNULLが返っていただけ
- **[Reclassified engine warnings](https://stitcher.io/blog/new-in-php-8#reclassified-engine-warnings-rfc)**
- 様々な警告、エラーのレベル見直し
- 今まではWarningやNoticeで済んでいたものが、エラー落ちするようになったりする
- 一覧は下記RFC中に記載
- https://wiki.php.net/rfc/engine_warnings
- ちょっとザワついたが、実際に実装されたものはRFCで採択されたものと異なっているようで、少し優しめの変更になっている…?(正式版リリースまでに変わるのかもしれない)
- https://github.com/php/php-src/blob/master/UPGRADING#L105
- PHPコミュニティの**過去の負債を消し去ろうとする強い意志を感じる**
```
現時点のUPGRAGDINGの翻訳
いくつかの警告がエラー例外に変換されました:
*非オブジェクトのプロパティに書き込もうとしています。以前これ
null、false、および空の文字列に対してstdClassオブジェクトを暗黙的に作成しました。
* PHP_INT_MAXキーが指定されている配列に要素を追加しようとしています
はすでに使用されています。
*無効なタイプ(配列またはオブジェクト)を配列キーとして使用しようとした、または
文字列のオフセット。
*スカラー値の配列インデックスに書き込もうとしています。
*非アレイ/トラバーサブルをアンパックしようとしています。
多くの通知が警告に変換されました:
*未定義の変数を読み取ろうとしています。
*未定義のプロパティを読み取ろうとしています。
*未定義の配列キーを読み取ろうとしています。
*非オブジェクトのプロパティを読み取ろうとしています。
*非配列の配列インデックスにアクセスしようとしています。
*配列を文字列に変換しようとしています。
*リソースを配列キーとして使用しようとしています。
* null、ブール値、または浮動小数点数を文字列オフセットとして使用しようとしています。
*範囲外の文字列オフセットを読み取ろうとしています。
*空の文字列を文字列オフセットに割り当てようとしています。
RFC:https://wiki.php.net/rfc/engine_warnings
```
- **[The @ operator no longer silences fatal errors](https://stitcher.io/blog/new-in-php-8#reclassified-engine-warnings-rfc)**
- エラー制御演算子``@``が、Fatalエラーを抑制しなくなる
- 古いライブラリやソースコードなどで``@``でエラーを握りつぶしている場合があるが、通用しなくなるよ、ということ
- PHPコミュニティの**過去の負債を消し去ろうとする強い意志を感じる**
- **[Default error reporting level](https://stitcher.io/blog/new-in-php-8#default-error-reporting-level)**
- デフォルトのエラーレベルが
- ``E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED``(通知や非互換性、非推奨などを除く)
- や
- ``E_ALL & ~E_NOTICE & ~E_STRICT``(注意や非互換性を除く)
- ではなく、
- ``E_ALL``(全てのエラーを出力する)
- になる
- PHPコミュニティの**過去の負債を消し去ろうとする強い意志を感じる**
- **[Saner numeric strings](https://stitcher.io/blog/new-in-php-8#saner-numeric-strings-rfc)**
- 数値文字列(``"123 "``や``"123abc"``など)に対する仕様に奇妙な動きや不整合があり、それを見直した
- 分かりやすいものだけピックアップすると
- int型の引数を受け取るメソッドに対して``"123abc"``を渡した時…
- PHP7.4以前:int(123)として動く
- PHP8.0以降:TypeError
- ``in_numeric``に対して``"123 "``を渡すと…
- PHP7.4以前:false
- PHP8.0以降:true
- ``"123" == "123 "``が…
- PHP7.4以前:false
- PHP8.0以降:true
- ブラウザからのリクエスパラメータを処理する部分などで影響が出る可能性がある(数値文字列を受け取るので)
- **[Saner string to number comparisons](https://stitcher.io/blog/new-in-php-8#saner-string-to-number-comparisons-rfc)**
- こちらは、数値文字列を緩い比較演算子(``==``)を使用した場合の比較結果に関する仕様の見直し
- ``==``だけでなく、``in_array``やswitch文でも同様の比較が行われる
|比較 | 7.4以前 | 8.0|
|--- | --- | ---|
|0 == "0" | TRUE | TRUE|
|0 == "0.0" | TRUE | TRUE|
|0 == "foo" | TRUE | FALSE|
|0 == "" | TRUE | FALSE|
|42 == " 42" | TRUE | TRUE|
|42 == "42foo" | TRUE | FALSE|
- **[Fatal error for incompatible method signatures](https://stitcher.io/blog/new-in-php-8#fatal-error-for-incompatible-method-signatures-rfc)**
- 継承元と異なるシグネチャで定義されたメソッドがある場合にエラーになるようになった
- PHP7.4以前は、Warningが発生するだけだった
## PHP8関連その他
- **PHP8をとりあえず触ってみたい方はこちら**
- 各バージョンのPHP実行結果をみることができるサイト
- https://3v4l.org/
- [Testing PHP 8 With Docker](https://www.colinodell.com/blog/202008/testing-php-8-with-docker)
- [オフィシャルのDockerイメージ](https://github.com/docker-library/docs/blob/master/php/README.md#supported-tags-and-respective-dockerfile-links)がすぐに公開されるのでこちらを使うのもアリ
- [Microsoftが「PHP」サポートを縮小 ~「PHP 8.0」バイナリは公式提供せず](https://forest.watch.impress.co.jp/docs/news/1264900.html)
- MicrosoftによるPHPの公式サポートはPHP7.4までとなり、PHP8.0からはサポートしなくなる。
- 今後はWindowsでのPHP開発はWSL2に移行していくと思われる
- [Steps to Install Apache, MySQL and PHP in WSL 2 -Windows 10](https://www.how2shout.com/how-to/steps-to-install-apache-mysql-and-php-in-wsl-2-windows-10.html)
- ちなみにWSL2ではDockerも動くので、Dockerイメージ使うのも一手