30
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PHPAdvent Calendar 2023

Day 4

【PHP8.3】PHP8.3がリリースされたので新機能全部やる

Last updated at Posted at 2023-12-04

PHP8.4 / PHP8.3 / PHP8.2 / PHP8.1 / PHP8.0

2023/11/23にPHP8.3.0がリリースされました
大きな新機能については、PHP8.0以降公開されるようになったランディングページで見ることができます。
マイグレーションガイドも既に日本語化されています。

また、採用されたRFCについてはPHP8.3の新機能でまとめています。

ここでは、概要だけではなくUPGRADINGに載っている変更点を全部見て回ることにします。

Backward Incompatible Changes

下位互換性のない変更点。

Core

Programs that were very close to overflowing the call stack may now throw an Error

コールスタックの容量設定zend.max_allowed_stack_sizezend.reserved_stack_sizeが導入されました。
無限ループとかでスタック使用量がzend.max_allowed_stack_size - zend.reserved_stack_sizeバイトを超えるとエラーになります。

PHP Fatal error:  Maximum call stack size of 500 bytes reached during compilation. Try splitting expression in test.php on line 1

どうして単純なサイズ指定ではなくreserved_stack_sizeみたいなのがあるのかはよくわからない。
そういえばGC発生確率もsession.gc_probability / session.gc_divisorですね。

ちなみにini_setでは効かなかったので、PHP_INI_SYSTEMだと思います。

Executing proc_get_status() multiple times will now always return the right value on posix systems

POSIXで、proc_get_statusを複数回呼んだときに正しい値を返すようになりました。
またproc_get_statusしたあとにproc_closeするとこれまで常に-1を返してきていたのが、正しい値を返すようになりました。

とあるのですがWindowsは以前から正しい値だったので変化がわかりませんでした。

Zend Max Execution Timers is now enabled by default for ZTS builds on Linux

実行時間タイマーがLinux上のZTSビルドでも常に有効になりました。

むしろいままで有効じゃなかったってこと?

Uses of traits with static properties will now redeclare static properties inherited from the parent class

トレイトを使うと、親クラスから継承されるstaticプロパティが再定義されるようになりました。

これマイグレーションガイド原語からして何言ってるのかわからないのですが、この問題への対応です。

class A {
    static $test;
}
class B extends A {
    static $test;
}

A::$test = 'A';
B::$test = 'B';
var_dump(A::$test, B::$test);
  // 常にA, B


trait Foo {
    static $test;
}
trait Bar {
    static $test;
}
class A {
    use Foo;
}
class B extends A {
    use Bar;
}

A::$test = 'A';
B::$test = 'B';
var_dump(A::$test, B::$test);
  // PHP8.3  A, B
  // PHP8.2  B, B

static変数$testを直接クラスに記載するのとtrait経由で使う場合で、これまでは挙動が異なっていました。
今後はクラス内に書いたのと同じような動作になります。

Assigning a negative index n to an empty array will now make sure that the next index is n+1 instead of 0.

空の配列にマイナス値のキーを与えた場合、次のキーはこれまで0になっていたのが、普通にn+1になりました。

$a = [];
$a[-10] = 'x';
$a[] = 'y';

var_dump($a);
  // PHP8.3 [-10=>x, -9=>y]
  // PHP8.2 [-10=>x, 0=>y]

Class constant visibility variance is now correctly checked when inherited from interfaces

クラス定数の可視性が、インターフェイスを継承した際に正しくチェックされるようになりました。

とあるんだけど、そもそもインターフェイスにはpublic constしか書けないので、いったいなにを正しくチェックするのかよくわかりませんでした。

WeakMaps entries whose key maps to itself may now be removed

WeakMapのエントリは、foreachなどのイテレーション以外で到達不能な場合に循環参照コレクタによって削除されるようになりました。

こういうのどうやって検証したらいいんだ。

DOM

DOMChildNode::after() on a node that has no parent is now a no-op instead of a hierarchy exception

DOMChildNode::afterDOMChildNode::beforeDOMChildNode::replaceWithは、親ノードが存在しない場合に例外を出していたのが何もしなくなりました。
これはDOMの仕様だそうです。

$element = new DOMElement('div');
$ret = $element->before('hoge');
  // PHP8.3 エラー起きない
  // PHP8.2 PHP Fatal error:Uncaught DOMException: Hierarchy Request Error

Using the DOMParentNode and DOMChildNode methods without a document now works

同様に、ノードがない状態でDOMParentNodeDOMChildNodeを使ってもエラーが出なくなりました。

createAttributeNS() without specifying a prefix would incorrectly create a default namespace

プレフィックスを指定せずにDOMDocument::createAttributeNSを呼ぶとデフォルトのネームスペースが生成されていました。
これはバグであるため修正されました。

createAttributeNS() would previously incorrectly throw a NAMESPACE_ERR when the prefix was already used for a different uri

以前のバージョンでは、DOMDocument::createAttributeNS() は、prefix が既に異なる URI で使われている場合に DOM_NAMESPACE_ERRNAMESPACE_ERR DOMException をスローしていました。 このバージョンからは、prefix の名前が衝突していた場合でも、異なる prefix を正しく選べるようになりました。

と、マイグレーションガイドには書いてあるのですが、何を言ってるのかさっぱりわからない。
再現する例を作ることすらできなかったよ。

正直、DOM使い方が異常にややこしくて何が正しくて何が間違っているのかさっぱりわかりません。
SimpleXMLのほうが遥かに使い勝手がいいですし、なんならテンプレートエンジンとかpreg_replaceとかのほうがもっと手っ取り早いですからね。

New methods and properties were added to some DOM classes

いくつかのメソッドとプロパティが追加されました。
詳細は新機能のところで。

FFI

C functions that have a return type of void now return null

返り値がvoidの関数は、これまでFFI\CData:voidを返していましたが、nullを返すようになりました。

Opcache

The opcache.consistency_checks INI directive was removed

ini設定opcache.consistency_checksが削除されました。

キャッシュのチェックを行うタイミングを調整する機能らしいですが、JITなどとの組み合わせによって正しく動かないことがあったそうです。
修正案は複雑すぎたため、単純に機能自体が削除されました。

Phar

The type of Phar class constants are now declared

Pharクラス定数に型宣言がつきました。

$c = new ReflectionClassConstant('Phar', 'NONE');
var_dump($c->getType()->getName());  // int

クラス定数に型宣言できるのがそもそもPHP8.3からです。

Standard

The range() function has had various changes

range関数に様々な変更がありました。
基本的に、おかしな値を渡したらこれまで関数側が適当にどうにかしていたのをやめてエラーを出すようになります。
数値をプラスの$stepで使っている場合は動きは同じですが、変な使い方をしている場合は要注意です。

var_dump(range(1, 3, -1));
  // PHP8.3 ValueError
  // PHP8.2 [1, 2, 3]

var_dump(range([1], [3]));
  // PHP8.3 TypeError
  // PHP8.2 [1]

var_dump(range(NAN, 3, 1));
  // PHP8.3 ValueError
  // PHP8.2 [NAN]

var_dump(range(1, 3, 1.0));
  // PHP8.3 [int1, int2, int3]
  // PHP8.2 [float1, float2, float3]

var_dump(range('', 2));
  // PHP8.3 [0, 1, 2] E_WARNING 
  // PHP8.2 [0, 1, 2]

var_dump(range('1x', 2, 1));
  // PHP8.3 [1, 2] E_WARNING
  // PHP8.2 [1, 2]

var_dump(range('9', 'A'));
  // PHP8.3 [9, :, ;, <, =, >, ?, @, A] 文字コード順
  // PHP8.2 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] Aは0にキャスト

The file() flags error check now catches all invalid flags

関数fileの第二引数$flagが対応している値を厳密にチェックするようになります。

file('php://input', \FILE_APPEND);
  // PHP8.3  ValueError
  // PHP8.2  エラー出ない

読み込み専用関数にFILE_APPENDなど当然意味がありませんが、これまでは特にエラーも出ずに受け入れられていました。

SNMP

The type of SNMP class constants are now declared

SNMPクラス定数に型宣言が付きました。

New Features

新機能

Core

Anonymous classes may now be marked as readonly

無名クラスにreadonlyをつけられるようになりました。

$c = new readonly class {
  public function test($text){
      echo $text;
  }
};

そこまでして無名クラスを使う意味とは?

Readonly properties can now be reinitialized during cloning.

マジックメソッド__clone()内において、readonlyプロパティを一回だけ変更できるようになります。

class Foo {
  // コンストラクタ
  public function __construct(
      public readonly DateTime $bar
  ) {}

  // clone
  public function __clone()
  {
      $this->bar = new DateTime(); // OK
  }
}

$foo = new Foo(new DateTime());
$bar = clone $foo;

コメントによると、別にClone withのRFCがあって、これを実現するために必要な機能なので切り出されて先に入ったということのようです。

Class, interface, trait, and enum constants now support type declarations

クラス定数に型宣言できるようになりました。

interface I{
  const string TEST = 'test';
}
trait T{
  const int TEST = 1;
}
class C{
  const I|null TEST = null;
}

べんりですね。

プリミティブでない型も宣言するだけなら可能なのですが、PHPではconst object TEST = new stdClass()とか書けないので実質意味がありません。

Closures created from magic methods can now accept named arguments

マジックメソッドで作ったクロージャは、名前付き引数を受け入れるようになりました。

マイグレーションガイドにはあるのですが、意味がよく分かりませんでした。

$c = function($param1=null, $param2=null){
  var_dump($param1, $param2);
};
$c(param2:2, param1:1);

class C{
  public function __invoke($param1=null, $param2=null){
    var_dump($param1, $param2);
  }
}
$c = new C();
$c(param2:2, param1:1); // 1, 2

こういうことかと思ったんだけど両方ともPHP8.2以前でも動きます。
そもそもcreated from magic methodsってなんだよ具体的にメソッド名書けよ。

The final modifier may now be used when using a method from a trait

トレイトからメソッドを使う場合に、そのメソッドに対して final を指定できるようになりました。

マイグレーションガイドにはあるのですが、意味がよく分かりませんでした。

trait T{
  function foo(){
    $this->bar();
  }
  final function bar(){
    var_dump(__CLASS__);
  }
}

class C{
  use T;
}

(new C)->foo(); // "C"

foo()をfinalにするしない、bar()をfinalにするしない、bar()をclassに定義するtraitに定義する、8パターン全て試しましたが全てPHP8.2でも動作しました。
いったいどんな書き方がこれまでできなかったんだ?

Added the #[\Override] attribute to check that a method exists

Overrideアトリビュートです。

class C1{
  public function foo(){}
}

class C2 extends C1{
  #[\Override]
  public function foo(){}
}

class C3 extends C1{
  #[\Override]
  public function bar(){}
}
  // PHP8.3 Fatal error
  // PHP8.2 エラー出ない

Overrideを指定したメソッドが実はOverrideしていない場合、エラーになります。
これによって、親クラスをうっかり書き替えたといった場合の検出がより容易になります。

ちなみにこれは構文解析時にチェックされるため、C3をインスタンス化しなくてもエラーが発生します。

Class constants can now be accessed dynamically using the C::{$name} syntax

クラス定数を文字列から探せる機能です。

enum Suit:string{
  case Hearts = 'ハート';
  case Diamonds = 'ダイヤ';
  case Clubs = 'クラブ';
  case Spades = 'スペード';
}

echo Suit::{'Hearts'}->value;
  // PHP8.3 ハート
  // PHP8.2 syntax error

ENUMの使い勝手がぐっと上がりますね。

Static variable initializers can now contain arbitrary expressions

static変数に動的値を突っ込めるようになります。

function foo(int $start=0){
  static $i = $start;
  echo $i++;
}

foo(5);  // 5
foo();   // 6
foo(10); // 7

static変数は最初の一回だけ実行され、関数を抜けても値を保持するという便利なやつです。
しかしこの初期値として、どういうわけか今までは直接的な数値や定数しか使えませんでした。

PHP8.3以降は任意の式や変数値が使えるようになります。

CLI

It is now possible to lint multiple files

php -lで複数のファイルを文法チェックできるようになりました。

php -l a.php b.php

これまでは、2番目以降に指定したファイルは単に無視されていました。

DOM

Added properties DOMElement::className and DOMElement::id

Added properties DOMNode::isConnected and DOMNameSpaceNode::isConnected

Added properties DOMNode::parentElement and DOMNameSpaceNode::parentElement

DOMに各プロパティが追加されました。

なお、libxml2の仕様上バイナリセーフではないそうで、ヌルバイトを突っ込むとそこで切れます。

FFI

It is now possible to assign CData to other CData

FFI\CDataを別のFFI\CDataに代入できるようになりました。

Opcache

opcache_get_status()['scripts'][n]['revalidate'] now contains a Unix timestamp

opcache_get_status()の返り値に、次にキャッシュの更新確認を行う時刻が追加されました。

Posix

posix_getrlimit() now takes an optional $res parameter to allow fetching a single resource limit

posix_getrlimit()に第一引数$resが追加されました。
posix_getrlimit('core')coreだけ取得することができるようです。

posix_isatty() now raises type warnings for integers following the usual ZPP semantics

posix_isatty()の第一引数$file_descriptorの型はresource|intですが、int型を渡すと警告を出すようになりました。

そもそもint を指定した場合は、システムコールにそのまま渡せるファイル記述子だとみなしますという処理自体が謎すぎたので、今後はきちんとresourceを渡す必要があります。

posix_ttyname() now raises type warnings for integers following the usual ZPP semantics and value warnings for invalid file descriptor integers.

同じくposix_ttyname()も第一引数$file_descriptorにint型を渡すと警告を出すようになりました。

なおPOSIX関数はWindowsでは使えないのでよくわかりません。

Streams

Streams can now emit the STREAM_NOTIFY_COMPLETED notification

ストリームがSTREAM_NOTIFY_COMPLETEDを発するようになりました。

実はこれまで実装されていませんでした

Changes in SAPI modules

SAPIの変更。

$_SERVER['SERVER_SOFTWARE'] value from the built-in CLI server changed to make it compliant with RFC3875

ビルトインWebサーバで実行した際の$_SERVER['SERVER_SOFTWARE']の値がRFC3875に従うようになりました。

php -S localhost:8000

var_dump($_SERVER['SERVER_SOFTWARE']);;
  // PHP8.3
    string(33) "PHP/8.3.0RC6 (Development Server)"
  // PHP8.2
    string(28) "PHP 8.2.6 Development Server"

設定による可能性もありますが、Apache経由では"Apache"とだけ出てきてPHPバージョンなどは一切出力されませんでした。
CLIでの実行ではそもそも未定義でした。
あまりに共通項がないので、これに依存するコードなんて普通は書かないでしょうから、実際何らかの問題になるかというと絶対ならないと思います。

Deprecated Functionality

非推奨になる機能。
PHP8.3でE_Deprecatedとなり、PHP9.0で削除されます。
この機能・書き方は今後使ってはいけません。

Core

Using the ++ operator on empty, non-numeric, or non-alphanumeric strings is now deprecated

Using the -- operator on empty or non-numeric strings is now deprecated

インクリメント・デクリメント演算子の改善です。

数値以外に対する++--の挙動が変更になります。

$s = 'foo';
$s++; // 'fop'

$s = '<foo';
$s++; // '<fop'
  // PHP Deprecated:  Increment on non-alphanumeric string is deprecated 

$s = true;
$s++; // true
  // PHP Warning:  Increment on type bool has no effect

$s = null;
$s++; // 1

$s = '';
$s++; // '1'
  // PHP Deprecated:  Increment on non-alphanumeric string is deprecated 

$s = '1.5';
$s++; // float(2.5)

Deprecatedが今後使えなくなる記法です。
''には今後使えなくなるのにnullは0扱いのままだとか、いっぽうtrueやfalseには前から使えなかったりと、型によって挙動がバラバラです。
そもそも数値以外へのインクリメント・デクリメントは避けた方がいいでしょう。

Calling get_class() and get_parent_class() without arguments is now deprecated

get_class()およびget_parent_class()の引数なしでの呼び出しが非推奨になりました。

new class {
    public function __construct(){
        var_dump(get_class());
    }
}; // PHP Deprecated:  Calling get_class() without arguments is deprecated

get_class()は、引数があれば引数のクラス名を返す、引数がなければget_class()が書かれているクラス名を返す、という極めて分かりにくい挙動になっています。
そのため引数を必須にすることにします。
今後も自身のクラス名を知りたい場合は$thisを渡すようにしましょう。

DBA

Calling dba_fetch() with $dba as the 3rd argument is now deprecated

第三引数にresource $dbaを渡すdba_fetch()は非推奨になりました。

よく見たらexplode()と同じ問題じゃないか。

dba_fetch(string|array $key, resource $dba, int $skip = 0): string|false
dba_fetch(string $key, int $skip, resource $dba): string

PHP9.0以降は前者の書き方しかできなくなります。

FFI

Calling FFI::cast(), FFI::new(), and FFI::type() statically is now deprecated

FFI::cast()FFI::new()FFI::type()の静的メソッド呼び出しが非推奨になります。
今後はインスタンスメソッドで呼び出しましょう。

Intl

The U_MULTIPLE_DECIMAL_SEPERATORS constant had been deprecated

定数U_MULTIPLE_DECIMAL_SEPERATORSは非推奨になりました。
かわりにU_MULTIPLE_DECIMAL_SEPARATORSを使いましょう。

EとAの誤字です。
これは別にPHPが間違ったわけではなく、元々の仕様から間違っていたせいです。

The NumberFormatter::TYPE_CURRENCY has been deprecated

NumberFormatter::TYPE_CURRENCYが非推奨になりました。

実は元々サポートされていませんでした。
かわりにNumberFormatter::parseCurrency()を使いましょう。

LDAP

Calling ldap_connect() with separate hostname and port is deprecated

2引数を渡すldap_connect()非推奨になりました

ldap_connect(?string $uri = null): LDAP\Connection|false
ldap_connect(?string $host = null, int $port = 389): LDAP\Connection|false

今後は前者を使いましょう。

MBString

Passing a negative $width to mb_strimwidth() is now deprecated

mb_strimwidth()の第三引数$widthに負の数を渡すことが非推奨になりました。
文字列関数に負数を与えると大抵は末尾から数えた値になるのですが、mb_strimwidthはそもそも固定長にする関数であり、意図が合わないため削除されます。

Phar

Calling Phar::setStub() with a resource and a length is now deprecated

Phar::setStub()に引数stream $stub, int $lenを与える呼び出しが非推奨になりました。

この書式はそもそもシグネチャに書かれていないですね。
英語版には2023/11/06に追加されているのですが、わざわざシグネチャを追加したりせず最初からなかったことにしていた方がいいのではと思わないでもない。

Random

The MT_RAND_PHP Mt19937 variant is deprecated

mt_srandの第二引数MT_RAND_PHPが非推奨になりました
これはPHP7.1で修正された壊れたメルセンヌツイスターをわざわざ再現するというオプションなのですが、いいかげんもういいだろうということで削除されます。

Standard

The assert_options() function is now deprecated

The ASSERT_ACTIVE, ASSERT_BAIL, ASSERT_CALLBACK, ASSERT_EXCEPTION, and ASSERT_WARNING constants have been deprecated

関数assert_options()および関連の定数が非推奨になりました。
assertまわりのクリーンアップにより不要になりました。

SQLite3

Using exceptions is now preferred, warnings will be removed in the future

SQLite3拡張モジュールにおいて、今後例外がデフォルトになります
PHP8.3ではenableExceptions(false)するとE_DEPRECATEDが発生しますが、初期値はfalseのままです。
PHP9.0では初期値がtrueとなり、enableExceptions(false)することもできなくなります。
最後にPHP10.0でenableExceptions()メソッド自体が削除されます。

Zip

The ZipArchive::FL_RECOMPRESS constant is deprecated

定数ZipArchive::FL_RECOMPRESSが非推奨になりました。
これはlibzipに従ったものです。

Changed Functions

変更のあった関数。

Core

gc_status() has added the following 8 fields

関数gc_status()の返り値に要素がたくさん追加されました。

var_dump(gc_status());

/* 例
array(12) {
  ["running"]=>
  bool(false)
  ["protected"]=>
  bool(false)
  ["full"]=>
  bool(false)
  ["runs"]=>
  int(5)
  ["collected"]=>
  int(100002)
  ["threshold"]=>
  int(50001)
  ["buffer_size"]=>
  int(131072)
  ["roots"]=>
  int(0)
  ["application_time"]=>
  float(0.0540216)
  ["collector_time"]=>
  float(0.0392877)
  ["destructor_time"]=>
  float(0)
  ["free_time"]=>
  float(0.0065847)
}
*/

Add four extra fields to gc_status()Expose time spent collecting cycles in gc_status()の改修によるものです。

class_alias() now supports creating an alias of an internal class

class_alias()がビルトインクラスにも使えるようになりました。

class_alias('stdClass', 'HOGE');
$c = new HOGE();
  // PHP8.3 $cはstdClass
  // PHP8.2 Uncaught ValueError: class_alias(): Argument #1 ($class) must be a user-defined class name, internal class name given

これまではユーザが自作したクラスにしかエイリアスをつくることができませんでした。

Setting open_basedir at runtime using ini_set('open_basedir', ...); no longer accepts paths containing the parent directory (..)

ini_set('open_basedir')で、php.iniopen_basedirより上に遡ることができなくなりました。

まず前提としてphp.iniopen_basedirが設定されている必要があります。
そのうえで、ini_set('open_basedir', '../foo/');という設定は当然ながら禁止されているのですが、チェックが甘くてini_set('open_basedir', './../foo/');で遡ることができていたとかいうぶっちゃけバグです。

これ、ドキュメントでは

Setting open_basedir at runtime using ini_set('open_basedir', ...); no longer accepts paths containing the parent directory (..). Previously, only paths starting with .. were disallowed. This could easily be circumvented by prepending ./ to the path.

ini_set('open_basedir', ...); を実行して実行時に open_basedir を設定する場合、親ディレクトリ (..) を含んだパスを受け入れなくなりました。これより前のバージョンでは、.. で始まるパスだけを拒否していました。.. で始まるパスのみを拒否する制限は、パスの先頭に ./ を付加することで簡単に回避できてしまっていました。

としか書かれていなくてphp.iniの記載がないのでかなり意味不明です。

User exception handlers now catch exceptions during shutdown

ユーザ定義例外がシャットダウン中の例外をキャッチできるようになりました。

set_exception_handler(function($excxeption){
    echo $excxeption->getMessage();
    return $excxeption;
});
register_shutdown_function(function(){
    throw new Exception('hoge', 999);
});

  // PHP8.3 hoge
  // PHP8.2 Fatal error:  Uncaught Exception: hoge

シャットダウン中はmax_execution_timeの対象外になったりするなど色々と例外なところなので、この中から例外を出すのもやめておいたほうが基本的にはよいと思います。

The resultant HTML of highlight_string and highlight_file has changed

関数highlight_stringおよびhighlight_fileの出力に変更がありました。

highlight_string('<html>  a</html>');

  // PHP8.3
  <pre><code style="color: #000000">&lt;html&gt;  a&lt;/html&gt;</code></pre>

  // PHP8.2
  <code><span style="color: #000000">
  &lt;html&gt;&nbsp;&lt;span&gt;foo&lt;/span&gt;&lt;/html&gt;</span>
  </code>

括りが<code><span>から<pre><code>になり、スペースや改行が&nbsp;<br />に変換されずそのまま出力されるようになりました。
ハイライト自体は変わっていないみたいです。

理由はこのあたりで、HTML standardに従った変更のようです。

Calendar

easter_date() now supports years from 1970 to 2,000,000,000 on 64-bit systems

関数easter_dateが1970年から2000000000年まで対応しました。
これまでは2037年まででした。

そもそもeaster_dateなんて関数初めて知ったわ。

Curl

curl_getinfo() now supports two new constants

関数curl_getinfoの第二引数$optionが定数CURLINFO_CAPATHとCURLINFO_CAINFOに対応しました。

Curlに最近追加された定数です。

Dom

Changed DOMCharacterData::appendData() tentative return type to true

DOMCharacterData::appendData()はtrueを返すようになりました。
PHP7.2まではvoidでした。

DOMDocument::loadHTML(), DOMDocument::loadHTMLFile(), DOMDocument::loadXML() and DOMDocument::loadXMLFile() now have a tentative return type of bool

同様にDOMDocument::loadHTML()DOMDocument::loadHTMLFile()DOMDocument::loadXML()DOMDocument::loadXMLFile()もbool型を返すようになりました。
これまでは場合によってDOMDocumentが返ることがありました。

Gd

Changed imagerotate signature, removed the ignore_transparent argument

関数imagerotateから、第四引数$ignore_transparentが削除されました。
ずっと前からこの引数は使われていませんでした。

Intl

datefmt_set_timezone now returns true on success

datefmt_set_timezoneはbool型を返すようになりました。
これまでは成功時にnull、失敗時にfalseというよくわからない返り値でした。

IntlBreakiterator::setText() now returns false on failure

IntlBreakiterator::setText()はbool型を返すようになりました。
これまでは常にnullでした。

IntlChar::enumCharNames is now returning a boolean

IntlChar::enumCharNames()はbool型を返すようになりました。
これまでは成功時にnull、失敗時にfalseでした。

IntlDateFormatter::construct throws an U_ILLEGAL_ARGUMENT_ERROR exception

IntlDateFormatter::__construct()は、ロケールが不正な場合にU_ILLEGAL_ARGUMENT_ERRORの例外を出すようになりました。
ERRORって名前なのに実際はIntlExceptionです。

MBString

mb_strtolower, mb_strtotitle, and mb_convert_case implement conditional casing rules for the Greek letter sigma

mb_strtolowerほかのマルチバイト関数が、ギリシャ文字シグマに対応しました。

とあるんだけど、実際はだいぶ前から対応してるみたいなんですよね。
このGreek letter sigmaってのは、Σではなく何かダイアクリティカルマーク的な意味だったりするんですかね?

mb_decode_mimeheader interprets underscores in QPrint-encoded MIME encoded words

関数mb_decode_mimeheaderが、QPrintエンコードのアンダースコア_RFC2047に従って正しく解釈するようになりました。

しかしQPrintはPHP8.2で非推奨になっているので、どうして今さらって感じはしますね。

In rare cases, mb_encode_mimeheader will transfer-encode its input string where it would pass it through as raw ASCII in PHP 8.2.

関数mb_encode_mimeheaderにおいて、PHP8.2ではASCIIのまま通っていたものが、稀にtransfer-encodeされるようになることがあります。

ということらしいですがそもそもtransfer-encodeってなんだよ。

mb_encode_mimeheader no longer drops NUL (zero) bytes when QPrint-encoding the input string

関数mb_encode_mimeheaderにおいて、QPrintエンコードする際にヌルバイトを削除しないようにしました。
このせいで、かつてはUTF-16UTF-32の文字列を渡したときに文字が壊れてしまっていたそうです。

文字コードはほんと大変ですね。

mb_detect_encoding's "non-strict" mode now behaves as described in the documentation

関数mb_detect_encodingの第三引数$strictにfalseを渡したときの動作を改善しました。

マニュアルの表記は"最も近いものを返す"なのですが、実際の動作は"先頭〇バイトが合っていたらそれを正解とする"となっていました。
そのため、先頭が合わなかったらfalseが返るし、先頭の何バイトかさえ合っていればその後が全部正しくなくてもそのエンコーディングが返っていました。
こちらで実際に、最も近いものを返すように修正されました。

まあそもそもmb_detect_encodingに頼るのはやめようって話だな。

mysqli

mysqli_fetch_object now raises a ValueError instead of an Exception

関数mysqli_fetch_object()は、第二引数$constructor_argsを渡しているのにクラスにコンストラクタがない場合にValueErrorを出すようになりました。

class C{}
((new mysqli())->query($sql))->fetch_object('C', ['dummy']);
  // PHP8.3  Fatal error: Uncaught ValueError
  // PHP8.2  Fatal error: Uncaught Exception

これまではExceptionでした。

ちなみにコンストラクタが存在するのに引数を渡さない場合はToo few argumentsのArgumentCountErrorが発生します。

mysqli_poll now raises a ValueError when the read nor error arguments are passed

関数mysqli_poll()は、第一引数&$readと第二引数&$errorが両方必須になりました。
なんで?

mysqli_field_seek and mysqli_result::field_seek now specify return type as true instead of bool

関数mysqli_field_seek()の返り値がtrue型になりました。

これまでは定義上はbool型でしたが、実際はPHP8.0以降常にtrueを返していました。

ODBC

odbc_autocommit() now accepts null for the $enable parameter

関数odbc_autocommit()の第二引数$enableがnull許容型になりました。

これまでは必須でした。
未指定時は現在の設定が返ってきます。

なお、指定時は指定が成功したかどうかが返ってきます。
指定時と未指定時で挙動がかわるのやめようよ。

PGSQL

pg_fetch_object now raises a ValueError instead of an Exception when the constructor_args argument is non empty with the class not having constructor.

関数pg_fetch_object()は、第四引数$constructor_argsを渡しているのにクラスにコンストラクタがない場合にValueErrorを出すようになりました。

上記mysqli_fetch_objectと同じです。

pg_insert now raises a ValueError instead of a WARNING when the table specified is invalid

関数pg_insert()は、テーブルが正しくない場合にValueErrorの例外を出すようになりました。

これまではWarningでした。

pg_insert and pg_convert raises a ValueError or a TypeError instead of a WARNING when the value/type of a field does not match properly with a PostGreSQL's type

関数pg_insert()pg_convert()は、フィールドの型が正しくない場合にValueErrorの例外を出すようになりました。

これまではWarningでした。

The $row param of pg_fetch_result(), pg_field_prtlen() and pg_field_is_null() is now nullable

関数pg_fetch_result()pg_field_prtlen()pg_field_is_nullの引数$rowがnull許容型になりました。

これまでは引数の省略が可能で、複数のシグネチャを持つという謎の関数でしたが、おそらく今後は前者のシグネチャに統一されていくことになると思います。

// PHP8.3
pg_fetch_result(PgSql\Result $result, int $row, mixed $field): string|false|null
pg_fetch_result(PgSql\Result $result, mixed $field): string|false|null

// PHP9か10か
pg_fetch_result(PgSql\Result $result, ?int $row, mixed $field): string|false|null

Random

Changed mt_srand() and srand() to not check the number of arguments to determine whether a random seed should be used

関数mt_srand()の挙動がRandom\Engine\Mt19937::__construct()と同じになりました。

対象のIssueはこちら変更はこちらです。
これマイグレーションガイドがnot check the number of arguments引数の数をチェックしなくなりましたとか書いてあるんだけど、正直何を言いたいのかよくわかりません。

どちらかというと単にマニュアルの間違いな気がする。

seed  0 を指定した場合はランダムな値が設定されます
with a random value if seed is 0.

実際は0は0というシード値として扱われ、nullはランダムな値が設定されます。

Reflection

Return type of ReflectionClass::getStaticProperties() is no longer nullable

ReflectionClass::getStaticProperties()の返り値が?array型からarray型になりました。

これまでもstaticプロパティが無い場合は[]を返していたので、単にシグネチャが変わっただけです。

Calling ReflectionProperty::setValue() with only one parameter is deprecated

ReflectionProperty::setValue()について、引数がひとつだけの呼び方が非推奨になりました。

public ReflectionProperty::setValue(object $object, mixed $value): void
public ReflectionProperty::setValue(mixed $value): void

Standard

E_NOTICEs emitted by unserialize() have been promoted to E_WARNING

unserializeの改善です。
関数unserializeで発生していたE_NOTICEは全てE_WARNINGになります。
また、それ以外にも様々な改善がなされています。

unserialize() now emits a new E_WARNING if the input contains unconsumed bytes

関数unserializeは、不正な入力にE_WARNINGを出すようになりました。

unserialize('i:5;i:6;');
  // PHP8.3 PHP Warning:  unserialize(): Extra data starting at offset 4 of 8 bytes
  // PHP8.2 エラー出ない

CVE-2023-21036への対応だそうです。

array_pad() is now only limited by the maximum number of elements an array can have

関数array_pad()で追加することのできる配列要素数の上限がなくなりました。
これまでは1048576個という謎の上限がありました。

もちろん配列要素数を増やしすぎるとメモリが死ぬので、実質的な上限は存在します。

strtok() raises a warning in the case token is not provided when starting tokenization

関数strtok()は、初回コール時に第二引数$tokenが存在しない場合にE_WARNINGを出すようになりました。

strtok('a;b;c');
  // PHP8.3  PHP Warning:  strtok(): Both arguments must be provided when starting tokenization
  // PHP8.2  エラー出ない

そもそもstrtok()はなんというか非常に気持ち悪い挙動をする関数なので、普通にexplode()とかpreg_split()とか使った方がいいと思います。

password_hash() will now chain the underlying Random\RandomException as the ValueError's $previous Exception when salt generation fails

関数password_hash()において、SALTの生成が失敗した場合に原因のRandom\RandomExceptionが例外の$previousに入ってくるようになりました。

とあるのですが、そもそもどんなときにSALT生成が失敗するのかよくわからない。
なんか適当に['cost'=>999]とか入れるとSALT生成まで進まずNGされるみたいで、messageにInvalid bcrypt cost parameterが入ってきて$previousは空でした。

proc_open() $command array must now have at least one non empty element

関数proc_open()の第一引数$commandに配列を渡す場合、少なくともひとつは空でない値を渡さないと動かなくなりました。

proc_open(['', null, false, true], $descriptorspec, $pipes);
  // Uncaught ValueError: First element must contain a non-empty program name

proc_open() returns false if $command array is invalid command

関数proc_open()の第一引数$commandが正しくない場合、リソースではなくfalseが返るようになりました。
これまではリソースが返ってくるものの、その後実行するとエラーになっていました。

Windowsでは元々そのような動作でしたが、Linux等でもそのようになったみたいです。

array_sum() and array_product() now warn when values in the array cannot be converted to int/float

関数array_sum()およびarray_product()の引数にint/float型に変換できない値が入っていた場合、警告が出るようになりました。

array_sum([1, 2, 'a']);
  // PHP8.3  Warning: array_sum(): Addition is not supported on type string
  // PHP8.2  エラー出ない

これまでは単に無視されていました。
今のところ計算結果は同じです。

number_format() $decimal parameter handles rounding to negative places

関数number_format()の第二引数$decimalsにマイナス値を渡した場合、小数点を遡って丸め込まれるようになりました。

number_format(34567, -2)
  // PHP8.3  "34,600"
  // PHP8.2  "34,567"

これまではマイナス値は無視されていました。
これは誰得なんだ…?

The $before_needle argument added to strrchr()

関数strrchr()に第三引数$before_needleが追加されました。

これはstrstr()stristr()と同じ引数になります。

str_getcsv() and fgetcsv() return empty string instead of a string with a single zero byte for the last field which contains only unterminated enclosure

関数str_getcsvおよびfgetcsvは、閉じていないクォートにヌルバイトを返していたのを返さなくなりました。

str_getcsv('"')
  // PHP8.3  [string(0) ""]
  // PHP8.2  [string(1) ""]

そもそも入力が不正なので、これが問題になることはないでしょう。

新しい関数

Date

DatePeriod::createFromISO8601String()

DatePeriod::__construct()オーバーロード削除に伴い追加されました。

DOM

DOMNode::contains()DOMNameSpaceNode::contains()DOMElement::getAttributeNames()DOMNode::getRootNode()DOMParentNode::replaceChildren()DOMNode::isEqualNode()DOMElement::insertAdjacentElement()DOMElement::insertAdjacentText()DOMElement::toggleAttribute()

各種DOM関数。

Intl

IntlCalendar::setDate()IntlCalendar::setDateTime()

IntlCalendar::set()オーバーロード削除に伴い追加されました。

IntlGregorianCalendar::createFromDate()IntlGregorianCalendar::createFromDateTime()

IntlGregorianCalendar::__construct()オーバーロード削除に伴い追加されました。

JSON

json_validate()

JSONが正しいかどうか判定できる関数です。

LDAP

ldap_connect_wallet()

ldap_connect()オーバーロード削除に伴い追加されました。

ldap_exop_sync()

ldap_exop()オーバーロード削除に伴い追加されました。

MBString

mb_str_pad()

マルチバイト版str_padです。

Posix

posix_sysconf()posix_pathconf()posix_fpathconf()posix_eaccess()

各種POSIX関連情報を取得できるみたいです。
Windowsは未対応なので不明。

PGSQL

pg_set_error_context_visibility()

pg_result_error()が返すメッセージにCONTEXTフィールドを入れるかどうかを制御できるみたいです。
よくわかりませんが単にPostgresのPQsetErrorContextVisibilityのラッパーみたい。

Random

Randomizer::getBytesFromString()

ランダムな文字列生成です。

Randomizer::nextFloat()Randomizer::getFloat()

ランダムな数値生成です。

$randomizer = new \Random\Randomizer();

// ランダムな文字列
echo $randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16);
// aが75%、bが25%で出現するランダム文字列
echo $randomizer->getBytesFromString('aaab', 10);

// [-90, 90]
echo $randomizer->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed);
// (-180, 180)
echo $randomizer->getFloat(-90, 90, \Random\IntervalBoundary::OpenClosed);

// getFloat(0, 1, ClosedOpen)のショートカット
echo $randomizer->nextFloat();
// 10%の確率
var_dump($randomizer->nextFloat() < 0.1);

何も考えずに実装すると実はランダムになってなかったというようなことがよくあるランダム文字列・数値生成が容易にできるようになりました。
また0は含むが1は含まない、という端処理が簡単にできるのも便利ですね。

Reflection

ReflectionMethod::createFromMethodName()

ReflectionMethod::__construct()オーバーロード削除に伴い追加されました。

Sockets

socket_atmark()

ソケットに帯域外マークが付けられているかを調べます。

なんのことかさっぱりわかりませんが、実体は単なるsockatmarkのラッパーです。

Standard

str_increment()str_decrement()

文字列のインクリメント・デクリメント関数です。

PHPはPerl由来の加算子による文字列インクリメントに対応しているのですが、デクリメントは効かなかったり算術演算子と異なる結果になったりと、色々半端な挙動になっています。
そこで専用の文字列関数を追加して、挙動を切り離すことにしました。

$s = 'foo';
var_dump(str_decrement($s)); // "fon"
var_dump(str_increment($s)); // "fop"

$s++; // エラー出ない

加算子によるインクリメントは、当面削除される予定はありませんが推奨されなくなります。

stream_context_set_options()

stream_context_set_option()の設定値をまとめて与えることができるようになりました。
まあcurl_setopt()curl_setopt_array()みたいなものです。

Zip

ZipArchive::setArchiveFlag()ZipArchive::getArchiveFlag()

グローバルフラグの値を設定・取得します。

よくわかりませんがlibzipのラッパーみたいです。
いまのところZIP_AFL_IS_TORRENTZIPZIP_AFL_CREATE_OR_KEEP_FILE_FOR_EMPTY_ARCHIVEに対応しているようです。

Other Changes to Extensions

Core

WeakMaps now have ephemeron-like behavior

WeakMapはEphemeronのような挙動になりました。

どういうことかというと、foreachで反復している最中に消えることがあります。
これまでは、反復開始後は終わるまで消えることはありませんでした。

The ++ and -- operators now emit warnings when attempting to increment values of type bool

インクリメント・デクリメント演算子の改善です。

基本的にint・float型以外に使用できなくなります。

$n = null;
var_dump(--$n);
  // PHP8.3  PHP Warning:  Decrement on type null has no effect, this will change in the next major version of PHP
  // PHP8.2  エラー出ない

文字列についてはかわりにできたstr_incrementを使いましょう。

DOM

The DOM lifetime mechanism has been reworked such that implicitly removed nodes can still be fetched

先に削除されたDOMノードもfetchできるようになりました。
これまでは例外が発生していました。

これ必要ある?

SQLite3

The SQLite3 class now throws \SQLite3Exception (extends \Exception) instead of \Exception

SQLite3が発生する例外が\SQLite3Exceptionになりました。
これまでは\Exceptionでした。

The SQLite error code is now passed in the exception error code instead of being included in the error message

SQLiteのエラーコードが、例外のエラーコードに出力されるようになりました。
これまではエラーメッセージをパースしないと取り出すことができませんでした。

New Global Constants

新しい定数。

基本的には使用しているライブラリの定数をそのまま取り込んでいるだけです。

Curl

CURLINFO_CAPATH
CURLINFO_CAINFO
CURLOPT_MIME_OPTIONS
CURLMIMEOPT_FORMESCAPE
CURLOPT_WS_OPTIONS
CURLWS_RAW_MODE
CURLOPT_SSH_HOSTKEYFUNCTION
CURLOPT_PROTOCOLS_STR
CURLOPT_REDIR_PROTOCOLS_STR
CURLOPT_CA_CACHE_TIMEOUT
CURLOPT_QUICK_EXIT
CURLKHMATCH_OK
CURLKHMATCH_MISMATCH
CURLKHMATCH_MISSING
CURLKHMATCH_LAST

Intl

MIXED_NUMBERS
HIDDEN_OVERLAY

OpenSSL

OPENSSL_CMS_OLDMIMETYPE
PKCS7_NOOLDMIMETYPE

PCNTL

SIGINFO

PDO_ODBC

PDO_ODBC_TYPE

PGSQL

PGSQL_TRACE_SUPPRESS_TIMESTAMPS
PGSQL_TRACE_REGRESS_MODE
PGSQL_ERRORS_SQLSTATE
PGSQL_SHOW_CONTEXT_NEVER
PGSQL_SHOW_CONTEXT_ERRORS
PGSQL_SHOW_CONTEXT_ALWAYS

Posix

POSIX_SC_ARG_MAX
POSIX_SC_PAGESIZE
POSIX_SC_NPROCESSORS_CONF
POSIX_SC_NPROCESSORS_ONLN
POSIX_PC_LINK_MAX
POSIX_PC_MAX_CANON
POSIX_PC_MAX_INPUT
POSIX_PC_NAME_MAX
POSIX_PC_PATH_MAX
POSIX_PC_PIPE_BUF
POSIX_PC_CHOWN_RESTRICTED
POSIX_PC_NO_TRUNC
POSIX_PC_ALLOC_SIZE_MIN
POSIX_PC_SYMLINK_MAX

Sockets

SO_ATTACH_REUSEPORT_CBPF
SO_DETACH_BPF
SO_DETACH_FILTER
TCP_QUICKACK
IP_DONTFRAG
IP_MTU_DISCOVER
IP_PMTUDISC_DO
IP_PMTUDISC_DONT
IP_PMTUDISC_WANT
IP_PMTUDISC_PROBE
IP_PMTUDISC_INTERFACE
IP_PMTUDISC_OMIT
AF_DIVERT
SOL_UDPLITE.
UDPLITE_RECV_CSCOV.
UDPLITE_SEND_CSCOV.
SO_RERROR (NetBSD only).
SO_ZEROIZE (OpenBSD only).
SO_SPLICE (OpenBSD only).
TCP_REPAIR
SO_REUSEPORT_LB
IP_BIND_ADDRESS_NO_PORT

Zip

ZipArchive::ER_DATA_LENGTH
ZipArchive::ER_NOT_ALLOWED
ZipArchive::AFL_RDONLY
ZipArchive::AFL_IS_TORRENTZIP
ZipArchive::AFL_WANT_TORRENTZIP
ZipArchive::AFL_CREATE_OR_KEEP_FILE_FOR_EMPTY_ARCHIVE
ZipArchive::FL_OPEN_FILE_NOW
ZipArchive::LENGTH_TO_END
ZipArchive::LENGTH_UNCHECKED

Changes to INI File Handling

iniファイルの変更

assert.*

以下のini設定は削除されました。
assert.active
assert.bail
assert.callback
assert.exception
assert.warning

zend.max_allowed_stack_size

最大スタックサイズを設定します。
デフォルトは0で、0もしくは-1もしくは正の値を設定します。

zend.reserved_stack_size

予約済みスタックサイズを設定します。

スタックサイズが何なのか正直ちょっとよくわからない。

Windows Support

サポートされるWindowsの最小バージョンがWindows8・Windows Server 2012になりました。

Other Changes

Core

An Error is now thrown when the process call stack exceeds a certain size

プロセス呼び出しのスタックサイズが一定値を超えるとエラーが出るようになりました。
最大許容サイズはini設定zend.max_allowed_stack_size、およびzend.reserved_stack_sizefiber.stack_sizeで決まります。

FFI

FFI::load() is now allowed during preloading when opcache.preload_user is the current system user

実行ユーザがopcache.preload_userである場合、プリロード中にFFI::load()できるようになりました。

これまではopcache.preload_userが設定されていた場合は一律不可でした。

FPM

FPM CLI test now fails if the socket path is longer than supported by OS

php-fpmのソケットパスが、OSのサポートしているものより長い場合にエラーを出すようになりました。

とあるのですが、このソケットパスってファイル長のことですかね?
composerファイルをWindowsローカルに持ってきたらファイルパスが長すぎて動かねえみたいなことがたまにありますが、それと同じようなかんじ?

Opcache

In the cli and phpdbg SAPIs, preloading does not require the opcache.preload_user directive to be set anymore when running as root.

CLIからrootで実行した場合、opcache.preload_userを設定しなくてもrootユーザでのプリロードが許可されます。

Web等からPHPを実行したときにrootユーザでプリロードされるとセキュリティ的に危ないので、opcache.preload_user=rootを明示しないかぎり禁止されています。

Streams

Blocking fread() on socket connection returns immediately if there are any buffered data instead of waiting for more data

ソケットに対するfread()をブロックすると、続きを待たずにバッファリングデータを即座に返すようになりました。

とあるのだけどどういう場合に起きるのかさっぱりわかりません。

Memory stream no longer fails if seek offset is past the end

メモリストリームに対してファイル終端を超えるオフセットを指定した場合、これまでは失敗していましたが失敗しなくなりました。
さらにその状態で書き込みを行うと、その間が0で埋められます。

stat() access operations like file_exists() and similar will now use real path instead of the actual stream path

stat()は、ファイルパスとしてストリームのパスではなくリアルパスを使うようになりました。
このアクセスはfile_exists()などと同じ方法です。

とあるのですが、ストリームのパスって何だ?

Performance Improvements

パフォーマンスの改善。

DOM

Looping over a DOMNodeList now uses caching

DOMNodeListのループにキャッシュを使うようになりました。
これまではループに二次関数オーダーの時間がかかっていました。

Getting text content from nodes now avoids an allocation

ノードからテキストを取得する際に、メモリを確保しなくなりました。

DOMChildNode::remove() now runs in O(1) performance

DOMChildNode::remove()O(1)で動くようになりました。

Standard

The file() flags error check is now about 7% faster

関数file()の第二引数$flagsのエラーチェックが7%高速化しました。

って何そのピンポイントな高速化。
おそらく他の関数にもこのように手が加えられているとは思いますが、そのあたりはUPGRADINGに記載がありません。

SPL

RecursiveDirectoryIterator now performs less I/O when looping over a directory

RecursiveDirectoryIteratorを使った際のディスクアクセスを減少しました。

感想

めちゃ多い!

けど致命的に互換が崩れるほどの変更は見当たらないように思えます。

今回は劇的に書き心地が変わるような大きな変更というのは見当たらず、少し便利になる小さな変更がいくつもあるというかんじですね。
移行にそこまで負担はなさそうなので、とりあえず試してみる勢いで行ってしまっていいのではないでしょうか。

次バージョンのPHP8.4で導入される機能も早々に幾つか決まっており、これからもPHPの進化は続いていきます。

30
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?