PHPerのための「PHP8.2の新機能を語り合う」PHP TechCafe =================================================== # PHPer's NEWS ::: info **[【入門】結局getter/setterは悪なのか](https://zenn.dev/kumackey/articles/c3acbd928d1d510268ab)** 「良いコード悪いコードで学ぶ設計入門」を最近読み、getter/setterは果たしてほんとに悪なのかと少し疑問を持っていました。 この記事では実際の例を用いて解説されており、PHP8.0で書かれていたので容易に理解することができました。 ::: ::: info **[PhpStorm 2022.2 の新機能](https://www.jetbrains.com/ja-jp/phpstorm/whatsnew/##)** PHP8.2サポートされるのは2022.3かな?2023.1かな? ::: ::: info **[IDE、.NET ツール、および All Products Pack のサブスクリプションの価格改定](https://blog.jetbrains.com/ja/blog/2022/06/29/increased-subscription-pricing-for-ides-net-tools-and-the-all-products-pack/)** ::: ::: info **[「PHP8技術者認定初級試験」を2023年春に開始](https://it.impress.co.jp/articles/-/23590)** ::: ::: info **[ソフトウェアの真の自由を実現するサービス、forgefriendsとは何か!!](https://qiita.com/rana_kualu/items/3804aa96b7d7ca355736)** ::: ::: info **[PHPによるWebアプリケーションフレームワーク「Laravel 9.25」が提供開始](https://laravel-news.com/laravel-9-25-0)** モデル クエリの touch() メソッドが便利そう ::: ::: info **[PHP言語向けパッケージマネージャー「Composer 2.4」が登場](https://php.watch/articles/composer-24)** ::: ::: info **[PHPカンファレンス沖縄2022開催](https://phpcon.okinawa.jp/)** ステーキおいしそうだった。 ::: ::: info **[PHP Foundation Update, July 2022](https://thephp.foundation/blog/2022/08/04/php-foundation-update-july-2022/)** DateTimeImmutable クラス について PHP9.0 で動きがありそう ::: # 特集:「PHP8.2の新機能」について語り合う ## 参考資料 * [PHP.net](https://wiki.php.net/rfc#php_82) * [stitcher.io](https://stitcher.io/blog/new-in-php-82) * [【PHP8.2】PHP8.2の新機能](https://qiita.com/rana_kualu/items/fc4b02e2daaf102aa92f) * [PHP Sandbox](https://onlinephp.io/) ## [Deprecate dynamic properties](https://wiki.php.net/rfc/deprecate_dynamic_properties) 未定義のプロパティに値を代入した時の動作が変更されます。 - v8.1まで: プロパティが生成される - v8.2: Warningが発生するが、プロパティが生成される。 - v9.0: 例外が発生する。 意図しないプロパティが生成されるとバグの原因になりそうなのでこの変更は良いと思います。 (静的解析で検知できるから変更しなくてもいいじゃんという意見もあったようですが...) ```php= class User { public $name; } $user = new User; // Assigns declared property User::$name. $user->name = "foo"; // Oops, a typo: $user->nane = "foo"; // PHP <= 8.1: Silently creates dynamic $user->nane property. // PHP 8.2: Raises deprecation warning, still creates dynamic property. // PHP 9.0: Throws Error exception. ``` ただし、`#[AllowDynamicProperties]` を付けることで例外を回避できます。 ```php #[AllowDynamicProperties] class User() {} $user = new User(); $user->foo = 'bar'; ``` ## [Readonly classes](https://wiki.php.net/rfc/readonly_classes) ```php= readonly class Foo { public int $bar; public function __construct() { $this->bar = 1; } } $foo = new Foo(); $foo->bar = 2; // Fatal Error: プロパティの上書きはダメ $foo->baz = 1; // Fatal Error: もちろん動的プロパティもダメ ``` Deprecate dynamic properties(動的プロパティの廃止)によって追加されるアトリビュート `#[AllowDynamicProperties]` はエラーになる ```php= #[AllowDynamicProperties] readonly class Foo { } // Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo ``` * プロパティへの型宣言は必須 * staticなプロパティは不可 * readonlyなクラスを継承して上書き可能なクラスを定義するのはNG * 上書き可能なクラスを継承してreadonlyなクラスを定義するのもNG 不変な値オブジェクトとして利用可能? ## [Constants in Traits](https://wiki.php.net/rfc/constants_in_traits) トレイトで定数を定義できるようになる ```php= trait Foo { public const FLAG_1 = 1; protected const FLAG_2 = 2; private const FLAG_3 = 2; public function doFoo(int $flags): void { if ($flags & self::FLAG_1) { echo 'Got flag 1'; } if ($flags & self::FLAG_2) { echo 'Got flag 2'; } if ($flags & self::FLAG_3) { echo 'Got flag 3'; } } } ``` * これまではトレイトをクラスかインターフェースで定義された定数を利用するしかなかった * クラスに定数を定義する場合の難点 * トレイトの実装がクラスに漏れてしまっている * トレイトを利用するための定数定義がトレイト自体によって提供されていない * インターフェースで定数が定義されている場合の難点 * インターフェース側で定義していることは実装上自然 * しかし、トレイトを利用するクラスが特定のインターフェースを実装していることを判別することはできない * クラスにもインターフェースにも依存しない状態でトレイトを利用できるようになれば、モジュールとしてのトレイトの完全性が向上することからこの提案が出された * 使いやすさだけが向上した素敵なアップデート * もともとトレイトで定数が利用できなかった理由は[特にない模様](https://externals.io/message/118039#118059) ## [Deprecate ${} string interpolation](https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation) 文字列で変数展開する際の ${} 表記の挙動を非推奨にする ```php= $foo = "bar"; $bar = "foo"; var_dump("$foo"); // OK var_dump("{$foo}"); // OK var_dump("${foo}"); // 非推奨: 文字列での ${} の使用は非推奨 var_dump("${$foo}"); // 非推奨: 文字列での ${} (可変変数) の使用は非推奨 ``` * ${} の表記を許容しているがゆえに動作が理解しづらくなっている パターン1:配列 ```php= $foo = ['bar' => 'bar']; var_dump("$foo[bar]"); var_dump("{$foo['bar']}"); var_dump("${foo['bar']}"); // すべて "bar" が出力されるが最後の挙動も "bar" になることが理解しづらい // しかも、可変変数は利用できない ``` パターン2:オブジェクトのプロパティ ```php= $foo = (object)['bar' => 'bar']; var_dump("$foo->bar"); var_dump("{$foo->bar}"); // このパターンでは ${} パターンが利用できない ``` パターン3:メソッドコール ```php= class Foo { public function bar() { return 'bar'; } } $foo = new Foo(); var_dump("{$foo->bar()}"); // メソッドコールで許容されているのは上記のみ ``` * ${} が許容されていることによって、どの挙動が正しく動作するのかが分かりにくくなっている * PHP8.2 で非推奨 * PHP9.0 でエラーになる ## [Deprecate partially supported callables](https://wiki.php.net/rfc/deprecate_partially_supported_callables) call_user_func(\$callable_func)では実行できるが、$callable_func()では実行できない構文が将来的に廃止されます。 例: ```php= "self::method" "parent::method" "static::method" ["self", "method"] ["parent", "method"] ["static", "method"] ["Foo", "Bar::method"] [new Foo, "Bar::method"] ``` v8.2でこれらの構文が非推奨になり、v9.0で廃止されます。こちらは32対0で廃止が可決されました。 ## [MySQLi Execute Query](https://wiki.php.net/rfc/mysqli_execute_query) MySQLiに新しい関数mysqli_execute_queryを追加 ### mysqli_execute_queryとは 下記3つの関数をまとめた関数です。 1. mysqli_prepare() 2. mysqli_execute() 3. mysqli_stmt_get_result() ### 使い方 今まで(PHP8.1以降) ```php= $statement = $db->prepare('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)'); $statement->execute([$name, $type1, $type2]); foreach ($statement->get_result() as $row) { print_r($row); } ``` これから ```php= foreach ($db->execute_query('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)', [$name, $type1, $type2]) as $row) { print_r($row); } ``` - [PDO](https://www.php.net/manual/ja/class.pdo.php)にはないのだろうか ## [Remove support for libmysql from mysqli](https://wiki.php.net/rfc/mysqli_support_for_libmysql) MySQLのモジュールはmysqlndとlibmysqlの2種類が存在します。 このうちlibmysqlを削除しようという話 理由 - mysqlndはPHP5.4以降ずっとデフォルト - PHP同梱、libmysqlに存在しない機能がある、パフォーマンス上有利 - libmysqlを選ぶ利点がほぼない ## [Fetch properties of enums in const expressions](https://wiki.php.net/rfc/fetch_property_in_const_expressions) enum:列挙型。性別とか独自の型を定義する時に便利。可読性の向上とタイプヒントで使うイメージ。 enumのcaseはオブジェクトの為、連想配列のキーに使うことができなかった。 `->`を使って定数式の中でenumのプロパティを取得できるようになった。 配列のキーなど、enumオブジェクトを使用できない部分でenumのnameやプロパティを取得できるようにしたいことが出発点。 ```php= enum A: string { case B = 'B'; // 現在コレはダメ const C = [self::B->value => self::B]; } enum E: string { case Foo = 'foo'; } // 'Foo'が取れる const C = E::Foo->name; ``` ```php= enum E { case Foo; } const C = E::Foo->c; // 未定義のプロパティへのアクセスはnullになります(※php9でerror扱い) // 警告: Attempt to read property "e" on null const C = (null)->e; // NULL // 警告: Attempt to read property "" on null // Fatal error: Uncaught Error: Object of class A could not be converted to string const C = (null)->{new A}; // Error: Fetching properties on non-enums in constant expressions is not allowed const C = (new A)->foo; ``` - イマイチ使いどころが分からない [issue](https://github.com/php/php-src/issues/8344) [実装](https://github.com/php/php-src/pull/8625) ## [Make the iterator_*() family accept all iterables](https://wiki.php.net/rfc/iterator_xyz_accept_array) iterator:反復可能なオブジェクト。foreachに渡せる。 iterator_to_arrayとiterator_countにiterable型の変数を渡せるようなる。 ```php= function before(iterable $foo) { if (!is_array($foo)) { $foo = iterator_to_array($foo); } return array_map(strlen(...), $foo); } function after(iterable $foo) { $foo = iterator_to_array($foo); return array_map(strlen(...), $foo); } ``` * iterator_to_array ```php= iterator_to_array($array, true) == $array iterator_to_array($array, false) == array_values($array) ``` * iterator_count ```php= iterator_count($array) == count($array) ``` 変更点 [iterator_to_array](https://www.php.net/manual/ja/function.iterator-to-array.php) ```php= - iterator_to_array(Traversable $iterator, bool $preserve_keys = true): array + iterator_to_array(iterable $iterator, bool $preserve_keys = true): array ``` [iterator_to_count](https://www.php.net/manual/ja/function.iterator-count.php) ```php= - iterator_count(Traversable $iterator): int + iterator_count(iterable $iterator): int ``` 引数の型がTraversableのためiterable型の変数を渡すとエラーとなっているが 8.2からは`Traversable|array`を渡せばよくなる。 https://github.com/php/php-src/pull/8819 - メソッド名に反してiterable型を受け付けなかったが、命名通りの動作をするようになった。 - イテレータをarrayに変換する独自メソッドを書く必要がなくなった。 - 後方互換性が保たれている。 ## [Allow null and false as stand-alone types](https://wiki.php.net/rfc/null-false-standalone-types) 型宣言が許される位置全てにおいて、false型とnull型を使用可能にする。 ```php= class Nil { public null $nil = null; public function foo(null $v): null { /* ... */ *} } class Falsy { public false $nil = false; public function foo(false $v): false { /* ... */ *} } ``` - もともとはUNION型のみで利用可能だったが、今回から単独で使用できるように - 静的解析で便利になる? - 関数がエラーであることを示すために、返り値として歴史的にfalseが使われてきました - レガシーシステムと相性がいい? - あまりfalseのみ、nullのみの型チェックをしたい機会が思いつかない…… * [Expand deprecation notice scope for partially supported callables](https://wiki.php.net/rfc/partially-supported-callables-expand-deprecation-notices) ## [Redacting parameters in back traces](https://wiki.php.net/rfc/redact_parameters_in_back_traces) * スタックトレースに引数を出力しないようにするアトリビュート * DBの接続情報などが画面に表示されることによる情報流出を防ぐ * 本番ではそもそも出力しないようにしたいが... * 比較的新しい機能"アトリビュート"がバンバン使われていく感じがすごい ```php= function test( $foo, #[\SensitiveParameter] $bar, $baz ) { throw new \Exception('Error'); } ``` ## [Random Extension 5.x](https://wiki.php.net/rfc/rng_extension) PHPで乱数を使用する関数の実装に問題があることが以前から指摘されていた。 具体的には * メルセンヌ・ツイスター(疑似乱数ジェネレーター)の実装が壊れている * メルセンヌ・ツイスターの状態は PHP のグローバル領域に暗黙的に格納される。そのため外部ライブラリで乱数が乱れたりといった問題が発生する。 * `shuffle()`, `str_shuffle()`, `array_rand()`,`random_int()`といった組み込み関数ではメルセンヌ・ツイスターが乱数ソースとして使用されるため、暗号的に安全な乱数が必要な場合は危険。 * PHP での乱数の実装は、歴史的な理由から標準モジュール内に散らばっている。 など そのためさまざまなランダム化メソッドを提供する単一のクラス、`Random\Randomizer`クラスが追加されます。 ```php= __construct(Random\Engine $engine = new Random\Engine\Secure()) getInt(): int // replaces mt_rand() getInt(int $min, int $max) // replaces mt_rand() and random_int() getBytes(int length): string // replaces random_bytes() shuffleArray(array $array): array // replaces shuffle() shuffleString(string $string): string // replaces str_shuffle() __serialize(): array __unserialize(array $data): void ``` 現状のPHPの乱数は危険なようなので、使用する際は注意した方がよさそう。 ## [Random Extension Improvement](https://wiki.php.net/rfc/random_extension_improvement) 上で記載した`Random Extension`の補足。 後に発覚した問題の修正が行われています。 * `array_rand()`に相当するメソッドの追加(`pickArrayKeys()`) * `shuffleString()`をより適切な名称である`shuffleBytes()`に変更 など ```php= $engine = new Random\Engine\Mt19937(1234, MT_RAND_PHP); $randomizer = new Random\Randomizer($engine); $array = ['foo', 'bar', 'baz']; $randomizer->pickArrayKeys($array, 1)[0]; // (int) 0 $randomizer->shuffleBytes('abc'); ``` ## [Add true type](https://wiki.php.net/rfc/true-type) 現在、型宣言が許可されている全ての場所において、true型を使用可能にします。 ```php= class Truthy { public true $truthy = true; public function foo(true $v): true { /* ... */ } } ``` - false型が追加されたのでtrue型も一緒に追加 - trueだけを返す関数があるので、そういった関数の戻り値をチェックしたいときに使える? - エラー時にfalseを返す関数を考えると、型チェックでExceptionを出せる……? ## [Deprecate and remove utf8_decode() and utf8_encode()](https://wiki.php.net/rfc/remove_utf8_decode_and_utf8_encode) utf8_decodeとutf8_encodeを削除します。 この関数は、任意の文字列をUTF-8にエンコードデコードできるかと思いきや、変換相手はLatin 1固定です。 非常に誤解を招く関数名であり、容易に間違った実装をしてしまいがちです。 ```php= function isUTF8($string) { return (utf8_encode(utf8_decode($string)) == $string); } ``` ## [Locale-independent case conversion](https://wiki.php.net/rfc/strtolower-ascii) strtolowerなどの関数は、なんとロケールの影響を受けます。 ```php= strtolower(string $string):string ``` マニュアルには『たとえばデフォルトの「C」ロケールである場合は、 A ウムラウト (Ä) のような文字は変換されません。』とありますが、そもそもロケールを変えたら変更できる、という挙動のほうがわかりにくくて危険です。 そのため、これらの関数はロケールの影響を受けないようにします。 ## [Disjunctive Normal Form Types](https://wiki.php.net/rfc/dnf_types)