346
247

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PHPAdvent Calendar 2019

Day 2

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

Last updated at Posted at 2019-12-01

PHP8.1 / PHP8.0 / PHP7.4

2019/11/28にPHP7.4.0がリリースされました
ということで、ここではドキュメント化されている新機能や変更点を片端から試してみます。

これら以外にもドキュメント化するほどでもない軽微な変更が多々入っているはずですし、単なるバグ修正も山ほどあるのですが、今回はそのあたりには触れません。
把握しきれていませんしね。

インストール

古いXAMPPが入っていたらディレクトリまるごと削除。
最新のXAMPPをインストール。
Windows版PHPからVC15 x64 Thread Safeをダウンロード。
解凍したディレクトリをpath\to\xampp\phpにまるごと上書きコピペ。
php.ini-developmentphp.iniにコピー。
php.iniextension_dirをエクステンションが入ってるディレクトリへのフルパスに変更し、mbstringやらgmpあたりの必要なエクステンションのコメントアウトを外す。
XAMPPコントロールパネルからApacheを起動してphpinfo()とかを表示してPHP7.4.0になっていたら成功。

Linux? Mac? Docker?
あなたなら環境構築くらい自力でできるっしょ。

新機能とか

プロパティ型指定

プロパティに型が指定できるようになりました。

class HOGE{
	public int $i = 0;
	public string $s = '';
	public ?object $obj;
}

$c = new HOGE();
$c->i = 1;
$c->s = 'string';
$c->obj = new stdClass();

$c->i = 'string'; // Uncaught TypeError: Typed property HOGE::$i must be int, string used

PHP7.4最大の特徴といっていいでしょう。

アロー関数

アロー関数が使えるようになりました。

$square = fn($x) => $x ** 2;
var_dump($square(2), $square(-5)); // 4, 25

ちょっとした使い捨て関数を書くときなどに便利。

$a = 1;
$hoge = fn() => ++$a;

echo $hoge(); // 2
echo $a; // 1

PHPではアロー関数の外にある変数は汚染されません。

また、アロー関数の実装に伴いfnが予約語になります。
今後function fn(){}といった文は書けなくなります。

FFI

PHP内に他言語を書けるようになりました。

$a = FFI::new("int[10]");
for ($i = 0; $i < 10; $i++) {
	$a[$i] = $i;
}

$p = FFI::cast("int*", $a);
var_dump($p[0]); // 0
var_dump($p[2]); // 2

脳が混乱する。
あと複雑な文を入れるとすぐエラーになるんだけどCのエラーなのかPHPのエラーなのかわからなくなる。

プリローディング

php.iniを設定。

php.ini
opcache.preload="path\to\preload.php"

opcache.preloadには"プリロードする対象ファイル"ではなく、"プリロードする対象ファイルを読み込むファイル"を指定します。

preload.php
// preload_cache.phpをプリロードする
opcache_compile_file('path\to\preload_cache.php');

プリロードさせたい中身はopcache_compile_fileで呼ばれているファイルに書きます。

preload_cache.php
function h(string $str):string{
	return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

Apacheを再起動すると、h()がサーバ上のどこからでも使えるようになります。

var_dump(h('a<b>c')); // a&lt;b&gt;c

function h(){} // Fatal error: Cannot redeclare h()

関数h()がネイティブ関数……のようなものになりました。

NULL合体代入演算子

NULL合体代入演算子??=が導入されました。

$arr = [
    null,  // "hoge"
    false, // false
    true,  // true
    '',    // ""
    0,     // 0
    [],    // []
];
foreach($arr as $i){
    $i ??= 'hoge';
    echo $i;
}

$x ??= 'hoge';
echo $x; // "hoge"

$x = $x ?? 'hoge'; // これと同じ

変数が未定義もしくはnullであれば、NULL合体代入演算子の後ろの値になります。
それ以外であれば元のままになります。
Undefined variableのE_NOTICEを出さない最低限の初期化が簡単に行えます。

HASHエクステンションの常時有効化

var_dump(hash_algos()); // ['md2', 'md4', 'md5', …]

PHP7.3までは--disable-hashオプションで無効にできました。
PHP7.4以降は無効にすることができません。

Password Hashing Registry

エクステンションが独自のハッシュアルゴリズムを追加できるようになりました。
それに伴い、現在使用可能なハッシュアルゴリズムを確認するpassword_algos関数が追加されました。

var_dump(password_algos()); // ['2y', 'argon2i', 'argon2id']

今後追加アルゴリズムは出てくるでしょうか。

openssl_random_pseudo_bytesの改善

openssl_random_pseudo_bytes関数が異常時に例外を発生するようになりました。

openssl_random_pseudo_bytes(-1); // Fatal error: Uncaught Error: Length must be greater than 0

PHP7.3までは例外が出ずにfalseが返ってきていました。

mb_str_split

mb_str_split関数が追加されました。

$x = mb_str_split('aAaA11!!あ𩸽', 1);
var_export($x);// ['a', 'A', 'a', 'A', '1', '1', '!', '!', 'あ', '𩸽']

半角全角入り交じりでも正常に分割してくれます。
これは素晴らしいですね。

$x = mb_str_split('がが👨‍👩‍👦‍👦', 1);
var_export($x);// ['が', 'か', '゙', '👨', '‍', '👩', '‍', '👦', '‍', '👦' ]

ZWJシーケンスもきれいに分割してくれる模様。

mbstring.regex_retry_limit

mbstring.regex_retry_limitディレクティブが追加されました。
デフォルト値は1000000です。

$pattern = '(.*)*^';
$subject = '1234567890123456789012345678901234567890';
mb_ereg($pattern, $subject, $regs); // false

マルチバイト正規表現において、マッチ回数が一定値を超えたら検索を打ち切ります。
pcre.backtrack_limitと同じく、ReDoS攻撃を防ぐためのものです。

ところでPCRE正規表現のエラーはpreg_last_errorで取れるんだけど、mb_ereg系のエラーはどうやって調べればいいんだろう。

ReflectionReference

ReflectionReferenceクラスが追加されました。

$ary = [0, 1, 2];
$ref1 =& $ary[1];
unset($ref1);
$ref2 =& $ary[2];

var_dump(ReflectionReference::fromArrayElement($ary, 0)); // null
var_dump(ReflectionReference::fromArrayElement($ary, 1)); // null
var_dump(ReflectionReference::fromArrayElement($ary, 2)); // ReflectionReference

リファレンスかどうかを調べることができます。
が、なんか思っていたのとちがって、何故か配列にしか使うことができないみたいです。

数値セパレータ

数値リテラルを_で区切れるようになりました。

<?php
echo 149_597_870_700; // 149597870700
echo 0x42_72_6F_77_6E; // 0x42726F776E
echo 0b01010100_01101000_01100101_01101111; // 0b01010100011010000110010101101111

echo 1__2; // Parse error: syntax error

人の目で見てわかりやすくするためのもので、値は_が無い状態と全く同じです。

_の使用条件は"数値に挟まれてないといけない"であり、先頭や末尾、小数点や進数記号の前後などには付けられません。
また__と連続させることもできません。

__toString()が例外を出せる

マジックメソッド__toString()が中から例外を出せるようになりました。

class HOGE{
	public function __toString()
	{
		throw new Exception('hoge');
	}
}

echo new HOGE(); // Fatal error: Uncaught Exception: hoge

PHP7.3まではMethod HOGE::__toString() must not throw an exceptionのFatal errorになっていました。

共変戻り値・反変パラメータ

子クラスでパラメータの型を広げ、返り値の型を狭めることができるようになりました。

class BASE{}
class EXTEND extends BASE{}

class A{
	public function make(EXTEND $param) : BASE{
		return new BASE();
	}
}

class B extends A{
	/** @Override */
	public function make(BASE $param) : EXTEND{
		return new EXTEND();
	}
}

(new A())->make(new EXTEND());
(new B())->make(new BASE());

PHP7.3までは親クラスと子クラスの型は全く同じでないとならず、異なっているとDeclaration must be compatibleのFatal errorが出ていました。

引数アンパック

$a = [1, 2];
$b = 3;
$c = new ArrayObject([4, 5]);

[$a, $b, $c]; // [[1, 2], 3, ArrayObject(4, 5)]

[...$a, $b, ...$c]; // [1, 2, 3, 4, 5]

可変長引数関数呼び出しという特殊な場所でだけ使えていた引数アンパックを、普通に配列中で使えるようになります。
余計なマージとかを行わず単純に配列をくっつけたいときに便利です。

数値キーは無視されて連番が振り直されます。
連想配列を渡すとCannot unpack array with string keysのFatal errorになります。

弱い参照

WeakReferenceクラスが追加されました。

$dummy = new stdClass();
$wr = WeakReference::create($dummy);

$wr->get(); // $dummy
unset($dummy);
$wr->get(); // null

毎回インスタンス使い捨てのPHPでどういうときに使えばいいのか、正直わかりません。
WeakRefとの違いもわかりません。

マジックメソッド__serialize/__unserialize

マジックメソッド__serialize/__unserializeが追加されました。
__sleepおよびSerializableにかわる、新たなシリアライズのメカニズムです。

unserializeに失敗するバグ
class ObjectWithReferences
{
    protected $var1;
    protected $var2;

    public function __construct()
    {
        $this->var1 = new StdClass();
        $this->var2 = $this->var1;
    }
}

class WrapperObject implements Serializable
{
    private $obj;

    public function __construct($obj)
    {
        $this->obj = $obj;
    }

    public function getObject()
    {
        return $this->obj;
    }

    public function serialize()
    {
        unserialize(serialize(new \StdClass)); // ???
        return serialize($this->obj);
    }

    public function unserialize($serialized)
    {
        $this->obj = unserialize($serialized);
    }
}

$wrapper = new WrapperObject(new ObjectWithReferences());
var_dump($wrapper->getObject()); // ObjectWithReferences

$wrapper = unserialize(serialize($wrapper)); // Notice: unserialize(): Error at offset 82 of 83 bytes
var_dump($wrapper->getObject()); // false

これはバグレポに上がっていた例です。
WrapperObject::serializeメソッドに何もしないserialize/unserializeがありますが、これが入っていると何故かunserializeに失敗します。
新たなシリアライズシステムではこのような問題が起こりません。

__serialize
class WrapperObject
{
    private $obj;

    public function __construct($obj)
    {
        $this->obj = $obj;
    }

    public function getObject()
    {
        return $this->obj;
    }

    public function __serialize(): array
    {
        return (array) $this->obj;
    }

    public function __unserialize(array $data)
    {
        return $this->obj = (object) $data;
    }
}

$wrapper = new WrapperObject(new ObjectWithReferences());
var_dump($wrapper->getObject()); ObjectWithReferences

$wrapper = unserialize(serialize($wrapper));
var_dump($wrapper->getObject()); // stdClass

こっちならとても簡単。
ただし受け渡しは配列で行わないといけないので、そのあたりは手動で実装が必要になります。
上記例は手抜きしているので元に戻りません。

なおSerializable__serialize/__unserialize両方を入れた場合は__serialize/__unserializeだけが動きます。

unserialize max_depth

unserialize関数にオプションmax_depthが追加されました。

$array = [ 1=>[ 2=>[ 3=>[ 4=>[ 5 ] ] ] ] ];

$ser = serialize($array);

unserialize($ser, []); // [ 1=>[ 2=>[ 3=>[ 4=>[ 5 ] ] ] ] ]
unserialize($ser, ['max_depth'=>1]); // false unserialize(): Maximum depth of 1 exceeded

名前からすると深い階層を無視するオプションのように見えますが、実際はmax_depthを超える階層が存在したらunserialize自体が失敗します。

PEAR

PEARがデフォルトでインストールされなくなります。

require_once("Auth/Auth.php"); // failed to open stream: No such file or directory

手動でインストールすれば当然ながら今後も使用可能です。
またコンパイルオプション--with-pearを指定することでもインストールできますが、このオプションは非推奨で、今後削除される可能性があります。

Curl

PHPというより、同梱されるCurlのバージョンに依るものです。

$cfile = new CURLFile('https://www.google.com/images/srpr/logo1w.png','image/png','testpic');

libcurlのバージョンが7.56.0以降であれば、CURLFileにURLを指定できます。

また定数CURLPIPE_HTTP1がE_DEPRECATEDになりました。
libcurlでdeprecateになったためです。
libcur7.62.0以降は使えなくなります。

FILTER_VALIDATE_FLOAT

検証フィルタFILTER_VALIDATE_FLOATがオプションmin_range/max_rangeに対応し、FILTER_VALIDATE_INTと同じ挙動になりました。

filter_var(10.1, FILTER_VALIDATE_FLOAT, [
	'options'=>[
		'min_range' => 1,
		'max_range' => 10,
	]
]); // false

むしろ何故今まで対応していなかったのだろう。

IMG_FILTER_SCATTER

GDに画像フィルタ定数IMG_FILTER_SCATTERが追加されました。

$img = imagecreatefrompng('image.png');
imagefilter($img, IMG_FILTER_SCATTER , 3, 5);
header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);

点描のようなかんじに画像をぼかします。
第三、第四引数でぼかし度合いを調整できます。

imagefilter($img, IMG_FILTER_SCATTER, 3, 10); // 左
imagefilter($img, IMG_FILTER_SCATTER, 1, 100); // 右

image01.png

正規表現フラグPREG_OFFSET_CAPTURE / PREG_UNMATCHED_AS_NULL

preg_replace_callbackおよびpreg_replace_callback_arrayが、正規表現フラグPREG_OFFSET_CAPTUREとPREG_UNMATCHED_AS_NULLを受け取るようになりました。

$subject = 'abcdedcba';
$pattern = '|.c.|';

$callback = function($matches){
	// 第5引数が無い場合、 $matches = ['bcd'] / ['dcb']
	// PREG_OFFSET_CAPTUREがある場合、 $matches = ['bcd', 1] / ['dcb', 5]
    return '';
};

preg_replace_callback($pattern, $callback, $subject, -1, $count, PREG_OFFSET_CAPTURE);

PREG_OFFSET_CAPTUREがあると、マッチした位置も一緒にコールバック関数に渡ってきます。
引数の形が変わることに注意しましょう。

PREG_UNMATCHED_AS_NULLは、サブパターンがマッチしなかったときにnullで埋めて送られてきます。

preg_match('/(A)|(B)|(C)/', 'B', $matches, PREG_UNMATCHED_AS_NULL); // $matches = ["B", null, "B", null]

preg_match('/(A)|(B)|(C)/', 'B', $matches); // $matches = ["B", "", "B"]

よくわからないので詳細はコメントを参照してください。

PDO DSN

PDOのDSNにユーザ名userとパスワードpasswordを書けるようになりました。

$dsn = 'mysql:dbname=test;host=127.0.0.1;user=testuser;password=testpass';
$pdo = new PDO($dsn);

元々Postgresだけ対応していたのが、MySQLなどその他のデータベースにも書けるようになったとのことです。
これは地味に便利では。

DSNとコンストラクタが両方指定された場合はコンストラクタが優先されます。

PDO ?のエスケープ

SQLの構文中において、???でエスケープできるようになりました。

$sql = "SELECT * FROM my_table WHERE my_col ?? 'my_key'";

SQL構文中で?と書くとプレースホルダと解釈されてしまいますが、それを回避することができます。
これまでPostgresの?演算子を書くことができなかったため、その対策です。

文字列値としては、これまでもこれからも普通に書けます。

strip_tags

strip_tags関数の第二引数を配列で渡せるようになりました。

$str = '<a><b><i><u>テキスト</u></i></b></a>';
strip_tags($str, ['b', 'u']); // <b><u>テキスト</u></b>

むしろ今までできなかったのかよ。

なお、配列で渡す場合はタグの括弧は不要です。

array_merge

array_merge関数とarray_merge_recursive関数の第一引数を省略できるようになりました。

array_merge(); // []

空の配列が返ります。
おそらくスプレッド構文に空の配列を渡してしまったとき用。

$arr = [];
array_merge(...$arr); // PHP7.3まではE_WARNING

proc_open

proc_open関数の第一引数を配列で渡せるようになりました。

proc_open(['php', '-r', 'echo "Hello World\n";'], $descriptors, $pipes);

何がうれしいのかってOSコマンドインジェクションを考えなくて済むようになります。

また、第二引数がリダイレクタとnullに対応しました。

proc_open($cmd, [2 => ['redirect', 1]], $pipes); // 2>&1
proc_open($cmd, [2 => ['null']], $pipes); // 2>null

わりとexecってやっちゃうタイプなので、個人的にはあまり使わない関数です。

pcntl_unshare

pcntl_unshare関数が追加されました。
他プロセスと共有しているコンテキストを分離するとかなんとからしいのだけど、Windowsでは動かないのでよくわかりません。

SplPriorityQueue

SplPriorityQueue::setExtractFlagsに0を渡すと即座に例外を出すようになりました。

$queue = new SplPriorityQueue();
$queue->setExtractFlags(0); // Fatal error: Uncaught RuntimeException: Must specify at least one extract flag
$queue->insert('A', 1);
$queue->top();

PHP7.3までは、setExtractFlagsした時点ではエラーは起こらず、その後topしたところでFatal errorが起きていました。

MB_ONIGURUMA_VERSION

定数MB_ONIGURUMA_VERSIONが追加されました。

echo MB_ONIGURUMA_VERSION; // 6.9.3

正規表現エンジン鬼車のバージョンがわかるようになります。

鬼車

鬼車がPHP本体にバンドルされなくなりました。
かわりにlibonigを導入しなければならないそうです。

手元のXAMPPにはそんなファイルがなかったのですが、mb正規表現は普通に動いていました。
どうして動いているのかはわかりません。

get_declared_classes

get_declared_classes関数が、まだインスタンス化されていない無名クラスを返さなくなりました。

$class1 = new class {};
var_dump(get_declared_classes()); // $class2は入ってない
$class2 = new class {};

PHP7.3までは一覧に$class1$class2も出てきていました。
PHP7.4以降は$class1しか出てきません。

imagecreatefromtga

imagecreatefromtga関数が追加されました。

$img = imagecreatefromtga('image.tga');
header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);

誰得にも程があるのではないか。

ファイル末尾の<?php

最後に改行がない
<?php

ファイル末尾に改行を入れずに<?phpとだけ書くと、これまでは<?phpという文字列と解釈されていました。
すなわち<?phpという文字列がHTMLに出力されていました。
PHP7.4以降はPHP開始タグと解釈されるようになります。
その後が何もないので、実質的には何もしません。

STREAM_OPTION_READ_BUFFER

includeやrequireをストリームで使用している場合、streamWrapper::stream_set_optionSTREAM_OPTION_READ_BUFFERオプション付きで呼ばれるようになりました。
カスタムストリームラッパーを自作している場合、それに対する実装が必要です。

定数PASSWORD_XXXの値変更

定数PASSWORD_XXXの定数値が変更になりました。

echo PASSWORD_ARGON2ID; // argon2id

例としてPASSWORD_DEFAULTは1からnullに、PASSWORD_ARGON2IDは3から"argon2id"になります。
正しい実装をしているかぎりは影響ありません。

fread / fwrite

fread / fwriteが失敗時にfalseを返すようになりました。

$fp = fopen('/path/to/dummy', 'a');
fread($fp, 100); // false

PHP7.3までは0や""が返ってきていました。

DateIntervalの曖昧な比較

DateIntervalの曖昧な比較ができなくなりました。

new DateInterval('P1D') == new DateInterval('P1D'); // Warning: Cannot compare DateInterval objects

E_WARININGが発生し、たとえ同じ値であろうとも常にfalseが返ってきます。
PHP7.3までは異なる値であろうが常にtrueとなっていました。

厳密な比較は常にfalseで、警告も発生しません。

$dti = new DateInterval('P1D');
$dti2 = $dti;
var_dump($dti == $dti2, $dti === $dti2); // true, true

同じオブジェクトであれば当然trueであり、E_WARININGも発生しません。

リフレクションのシリアライズ

リフレクションをserializeするとFatal errorが発生するようになりました。

$ref = new ReflectionClass(new stdClass());
serialize($ref); // Serialization of 'ReflectionClass' is not allowed

リフレクションのシリアライズはこれまでもサポートされておらず、壊れることがありました。
PHP7.4では明確に禁止されます。

get_object_vars

get_object_varsArrayObjectを突っ込んでも、値が取れなくなりました。

$arr = new ArrayObject([1, 2, 3]);
get_object_vars($arr); // []

PHP7.3までは[1, 2, 3]が返ってきました。
これによってReflectionObject::getPropertiesIterator等が影響を受けます。

(array)$arrキャストは影響を受けず、値を取得することができます。

get_mangled_object_vars

get_mangled_object_vars関数が追加されました。

class A {
    public $pub = 1;
    protected $prot = 2;
    private $priv = 3;
}
class B extends A {
    private $priv = 4;
}

$obj = new B;
$obj->dyn = 5;
$obj->{"6"} = 6;

get_mangled_object_vars($obj); // [ ["Bpriv"]=> int(4), ["pub"]=> int(1), ["*prot"]=> int(2), ["Apriv"]=> int(3), ["dyn"]=> int(5), [6]=> int(6) ]get_object_vars($obj); // [ ["pub"]=> int(1), ["dyn"]=> int(5), [6]=> int(6) ]

get_object_varsとだいたい同じですが、privateな値までナチュラルに取ってきます。
いいのかこれ?

なお、ArrayObjectの値はこちらを使っても取得できません。

Countable SimpleXMLElement

SimpleXMLElementCountableをimplementsしました。

$xmlstr = <<<XML
<a>
  <bs>
    <b>1</b>
    <b>2</b>
    <b>3</b>
  </bs>
</a>
XML;

$xml = new SimpleXMLElement($xmlstr);
echo count($xml); // 1
echo count($xml->bs); // 1
echo count($xml->b); // 3

実はずっと昔からCountableでもないのにcountできていたので、単に現状を実態に合わせたというものです。

sapi_windows_set_ctrl_handler

sapi_windows_set_ctrl_handler関数が追加されました。

sapi_windows_set_ctrl_handler(
    function (int $evt) {
        if ($evt === PHP_WINDOWS_EVENT_CTRL_C) {
            echo 'Ctrl+C pressed.';
            exit();
        } elseif ($evt === PHP_WINDOWS_EVENT_CTRL_BREAK) {
            echo 'Ctrl+Break pressed.';
            exit();
        }else{
            echo 'What pressed???' , $evt;
        }
    }
);

while (1) {
    usleep(100);
}

WindowsのCLIでCtrl+CCtrl+Breakをハンドリングできます。

WindowsのCLIでPHPを動かしてる人なんてどんだけ居るんだよ(鏡を見つつ)。

OpenSSL

openssl_x509_verify関数が追加されました。

$crt = <<<CRT
-----BEGIN CERTIFICATE-----
MIIDbDCCAtWgAwIBAgIJAK7FVsxyN1CiMA0GCSqGSIb3DQEBBQUAMIGBMQswCQYD
VQQGEwJCUjEaMBgGA1UECBMRUmlvIEdyYW5kZSBkbyBTdWwxFTATBgNVBAcTDFBv
cnRvIEFsZWdyZTEeMBwGA1UEAxMVSGVucmlxdWUgZG8gTi4gQW5nZWxvMR8wHQYJ
KoZIhvcNAQkBFhBobmFuZ2Vsb0BwaHAubmV0MB4XDTA4MDYzMDEwMjg0M1oXDTA4
MDczMDEwMjg0M1owgYExCzAJBgNVBAYTAkJSMRowGAYDVQQIExFSaW8gR3JhbmRl
IGRvIFN1bDEVMBMGA1UEBxMMUG9ydG8gQWxlZ3JlMR4wHAYDVQQDExVIZW5yaXF1
ZSBkbyBOLiBBbmdlbG8xHzAdBgkqhkiG9w0BCQEWEGhuYW5nZWxvQHBocC5uZXQw
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMteno+QK1ulX4/WDAVBYfoTPRTz
e4SZLwgael4jwWTytj+8c5nNllrFELD6WjJzfjaoIMhCF4w4I2bkWR6/PTqrvnv+
iiiItHfKvJgYqIobUhkiKmWa2wL3mgqvNRIqTrTC4jWZuCkxQ/ksqL9O/F6zk+aR
S1d+KbPaqCR5Rw+lAgMBAAGjgekwgeYwHQYDVR0OBBYEFNt+QHK9XDWF7CkpgRLo
Ymhqtz99MIG2BgNVHSMEga4wgauAFNt+QHK9XDWF7CkpgRLoYmhqtz99oYGHpIGE
MIGBMQswCQYDVQQGEwJCUjEaMBgGA1UECBMRUmlvIEdyYW5kZSBkbyBTdWwxFTAT
BgNVBAcTDFBvcnRvIEFsZWdyZTEeMBwGA1UEAxMVSGVucmlxdWUgZG8gTi4gQW5n
ZWxvMR8wHQYJKoZIhvcNAQkBFhBobmFuZ2Vsb0BwaHAubmV0ggkArsVWzHI3UKIw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCP1GUnStC0TBqngr3Kx+zS
UW8KutKO0ORc5R8aV/x9LlaJrzPyQJgiPpu5hXogLSKRIHxQS3X2+Y0VvIpW72LW
PVKPhYlNtO3oKnfoJGKin0eEhXRZMjfEW/kznY+ZZmNifV2r8s+KhNAqI4PbClvn
4vh8xF/9+eVEj+hM+0OflA==
-----END CERTIFICATE-----
CRT;

$rightKey = <<<KEY
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLXp6PkCtbpV+P1gwFQWH6Ez0U
83uEmS8IGnpeI8Fk8rY/vHOZzZZaxRCw+loyc342qCDIQheMOCNm5Fkevz06q757
/oooiLR3yryYGKiKG1IZIiplmtsC95oKrzUSKk60wuI1mbgpMUP5LKi/Tvxes5Pm
kUtXfimz2qgkeUcPpQIDAQAB
-----END PUBLIC KEY-----
KEY;

$wrongKey = <<<KEY
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArbUmVW1Y+rJzZRC3DYB0
kdIgvk7MAday78ybGPPDhVlbAb4CjWbaPs4nyUCTEt9KVG0H7pXHxDbWSsC2974z
dvqlP0L2op1/M2SteTcGCBOdwGH2jORVAZL8/WbTOf9IpKAM77oN14scsyOlQBJq
hh+xrLg8ksB2dOos54yDqo0Tq7R5tldV+alKZXWlJnqRCfFuxvqtfWI5nGTAedVZ
hvjQfLQQgujfXHoFWoGbXn2buzfwKGJEeqWPbQOZF/FeOJPlgOBhhDb3BAFNVCtM
3k71Rblj54pNd3yvq152xsgFd0o3s15fuSwZgerUjeEuw/wTK9k7vyp+MrIQHQmP
dQIDAQAB
-----END PUBLIC KEY-----
KEY;

openssl_x509_verify($crt, $rightKey); // 1
openssl_x509_verify($crt, $wrongKey); // 0
openssl_x509_verify($crt, 'hoge');    // -1

X.509証明書のチェックができます。
こんなことわざわざPHPでするなよって気もしますが。

idn_to_utf8

idn_to_utf8およびidn_to_ascii関数の引数variantのデフォルト値がINTL_IDNA_VARIANT_UTS46になりました。

echo idn_to_utf8('xn--2-kq6aw43af1e4y9boczagup'); // 中島第2駐輪場

そういえば日本語ドメイン最近見ませんね。

token_get_all

token_get_all関数が、不正な文字列にトークンシンボルT_BAD_CHARACTERを返すようになりました。

$tokens = token_get_all('<?php ' . chr(0));
echo token_name($tokens[1][0]); // T_BAD_CHARACTER

これまでは解釈できない文字は単に無視されていたのですが、そのような文字を含めて正確にパースできるようになりました。

正しくないソースを正しく解析しても仕方ない気がしますが。

pkg-config

設定をpkg-configに寄せていくために、多くのコンパイルオプションがディレクトリ指定できなくなります。
例としてCurlは、コンパイルオプション--with-curl=path/to/dirとすることでディレクトリを指定することができました。
PHP7.4以降は--with-curlにはオプションを設定できず、ディレクトリ指定することができません。

また-with-png-dirのような、直接的にディレクトリを指定するオプションは削除されます。

まあ、最近は自力でインストールとかあまりしませんけどね。

演算子+-.の優先順位変更

演算子+-.を並べて使うとE_DEPRECATEDが起こるようになりました。
PHP8において演算子.の優先順位を下げるための布石です。

$a = 1;
$b = 2;

echo "sum: " . $a + $b; // Deprecated: The behavior of unparenthesized expressions containing both '.' and '+'/'-' will change in PHP 8

PHP7.3までは前から順に("sum: " . $a) + $bと解釈され、答えは2になっていました。
PHP7.4では、解釈は同じですが同時にE_DEPRECATEDが発生します。

PHP8以降では"sum: " . ($a + $b)と解釈されて、答えは"sum: 3"になります。

三項演算子のネスト制限

解釈が一意に定まらない三項演算子のネストはPHP7.4でE_DEPRECATEDに、PHP8でエラーになります。

1 ? 2 : 3 ? 4 : 5; // Deprecated: Unparenthesized `a ? b : c ? d : e` is deprecated.
(1 ? 2 : 3) ? 4 : 5; // ok 解釈が一意
1 ? 2 : (3 ? 4 : 5); // ok 解釈が一意
1 ? 2 ? 3 : 4 : 5; // ok 解釈が一意
1 ?: 2 ?: 3; // ok 解釈が一意

その後の予定は未定ですが、おそらくPHP9あたりで他言語と同じ解釈に変更されると思われます。

波括弧による文字列アクセス

$string = 'ABCDEFG';

echo $string[1]; // B
echo $string{2}; // Deprecated: Array and string offset access syntax with curly braces is deprecated

文字列に波括弧でアクセスするとE_DEPRECATEDが発生します。
PHP7.3まではエラーが出ずにアクセス可能でした。

PHP7.4リリース時点では、今後削除される予定はありません。

角括弧による文字列以外へのアクセス

$int = 1234567980;
echo $int[1]; // Notice: Trying to access array offset on value of type int

$null = null;
echo $null[0]; // Notice: Trying to access array offset on value of type null

int、float、bool、null、resourceに角括弧でアクセスするとE_NOTICEが発生します。
これまではエラーは出ませんでしたが、値はnullでした。

配列やstringへの[]アクセスは当然ながら今後も可能です。

htmlentities

htmlentities関数に、UTF-8以外のマルチバイト文字列を渡すとE_NOTICEが発生するようになります。

htmlentities('abc', ENT_COMPAT, 'Shift_JIS'); // htmlentities(): Only basic entities substitution is supported for multi-byte encodings

PHP7.1.25以降はE_STRICTが発生し、それ以前は何のエラーも起こりませんでした。
どういう意図なのかはよくわかりません。

parent without parent

親クラスのないクラスでparentを使うと、そのメソッドが呼ばれなくてもコンパイル時にE_DEPRECATEDが発生するようになります。

class Dummy{
    public function hoge(){
        return parent::hoge();
    }
}

// Deprecated: Cannot use "parent" when current class scope has no parent

PHP7.3までは、メソッドを呼ばないかぎり何も起きませんでした。

メソッドを呼び出すと、PHP7.3でも7.4でも当然Fatal errorが発生します。

BCMath

BCMath関数に数値形式でない文字列を渡すとE_WARNINGが発生するようになりました。

echo bcadd("2", "3a"); // Warning: bcadd(): bcmath function argument is not well-formed

PHP7.3までは何も出ませんでした。

値そのものは、昔から0として扱われていました。
上記例でいうと、出力はPHP5時代からずっと"2"のままです。
解釈が通常のPHP関数と異なるので少々わかりにくいですね。

base_convert

base_convert関数に無効な文字が渡された場合、E_DEPRECATEDが発生するようになりました。

base_convert('012', 2, 10); // Deprecated: Invalid characters passed for attempted conversion, these have been ignored

PHP7.3までは何のエラーも出ませんでした。
いずれにせよ、途中に出てくる無効な値は単に無視されます。

base_convertのほか、hexdecoctdecbindecにも同じ変更が入ります。
単に全ての関数が同じ内部関数_php_math_basetozvalを使っているからという理由ですが。

socket_addrinfo_lookup

socket_addrinfo_lookup関数において、引数AI_IDN_ALLOW_UNASSIGNEDとAI_IDN_USE_STD3_ASCII_RULESがE_DEPRECATEDになりました。
そもそもマニュアルが日本語化されてすらいないほど、この関数自体が使われていません。

Deprecate LDAP

ldap_control_paged_result_responseldap_control_paged_resultがE_DERECATEDになりました。
かわりにldap_searchを使えということだそうです。

また、nsldapumich_ldapのサポートが削除されました。

何のことだかさっぱりわかりません。

Deprecate is_real

is_real関数および(real)キャストはPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

is_real(1); // Deprecated: Function is_real() is deprecated
is_float(1); // OK

is_realではなくis_floatを使いましょう。

Deprecate Magic quotes

get_magic_quotes_gpcおよびget_magic_quotes_runtime関数はPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

get_magic_quotes_gpc(); // Deprecated: Function get_magic_quotes_gpc() is deprecated
get_magic_quotes_runtime(); // Deprecated: Function get_magic_quotes_runtime() is deprecated

Magic quotesはPHP5.4で滅びました。

array_key_exists

array_key_exists関数がオブジェクトを受け付けなくなりました。

array_key_exists(1, new stdClass()); // Deprecated: array_key_exists(): Using array_key_exists() on objects is deprecated
array_key_exists(1, []); // OK

array_key_existsの引数はarrayですが、下位互換性のためにobjectも受け入れていました。
PHP7.4でE_DEPRECATEDになり、PHP8ではエラーになります。

Deprecate FILTER_SANITIZE_MAGIC_QUOTES

定数FILTER_SANITIZE_MAGIC_QUOTESはPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

filter_var('s', \FILTER_SANITIZE_MAGIC_QUOTES); // Deprecated: filter_var(): FILTER_SANITIZE_MAGIC_QUOTES is deprecated
filter_var('s', \FILTER_SANITIZE_ADD_SLASHES); // OK

FILTER_SANITIZE_MAGIC_QUOTESとFILTER_SANITIZE_ADD_SLASHESは全く同じですが、magic_quotesという不穏当な表現を消去するために削除されます。

Deprecate ReflectionFunction::export

Reflector::exportはPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

ReflectionFunction::export('_'); // Deprecated: Function ReflectionFunction::export() is deprecated
echo new ReflectionFunction('_'); // OK

Reflector::exportは引数がvoidなのに、継承した各クラスは引数を受け取ります。
これはおかしいのでどうにかしなければならなかったのですが、単純にexportが削除されることになりました。

mb_strrpos

mb_strrpos関数の第三引数に文字エンコーディングを渡せなくなります。

mb_strrpos('haystack', 'needle', 'UTF-8'); // Deprecated: mb_strrpos(): Passing the encoding as third parameter is deprecated
mb_strrpos('haystack', 'needle', 0, 'UTF-8'); // OK

mb_strrposは、歴史的経緯により第三引数でも文字エンコーディングを受け取ることが可能でした。
この書き方はPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

implode

implode関数の引数を逆にできなくなります。

implode([1, 2], ','); // Deprecated: implode(): Passing glue string after array is deprecated
implode(',', [1, 2]); // OK
implode([1, 2]); // OK ','区切りと同じ

implodeは、歴史的経緯により第一引数と第二引数を逆に渡すことが可能でした。
この書き方はPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。
引数がひとつでの場合は今後も受け付けますが、これも使用しない方が無難でしょう。

クロージャ

クロージャから$thisを削除できなくなります。


(new class{
    public function dummy(){
        return function () {isset($this);};
    }
})->dummy()->bindTo(null); // Deprecated: Unbinding $this of closure is deprecated

クラスメソッドでクロージャを定義すると勝手に$thisが入ってきます。
PHP7.3までは、これを無理矢理削除することができました。
PHP7.4でE_DEPRECATEDになり、PHP8でエラーになります。

Deprecate hebrevc

hebrevc関数はPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

hebrevc('a'); // Deprecated: Function hebrevc() is deprecated

テキストに直接HTMLタグを書き込むという不適切な仕様が入っているためです。
かわりに nl2br(hebrev($str))を使いましょう。

Deprecate convert_cyr_string

convert_cyr_string関数はPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

convert_cyr_string('str', 'k', 'i'); // Deprecated: Function convert_cyr_string() is deprecated

非常に古い関数であるため、文字コードの指定方法が他の関数と一貫していません。
今後はiconvやmb_convert_encodingを使いましょう。

Deprecate money_format

money_format関数はPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

money_format('%i', 123456789); // Fatal error: Uncaught Error: Call to undefined function money_format()

money_format() は Windows では 定義されていません。

おふぅ。

このようにmoney_formatは環境によって使用できなかったり、ロケールによって結果が変わったりします。
今後はNumberFormatter::formatCurrency等を使いましょう。

Deprecate ezmlm_hash

ezmlm_hash関数はPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

ezmlm_hash('test@example.com'); // Deprecated: Function ezmlm_hash() is deprecated

そもそもこの関数を使っている人はいるのだろうか。

Deprecate restore_include_path

restore_include_path関数はPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

restore_include_path(); // Deprecated: Function restore_include_path() is deprecated

set_xxx関数をを多重に積んだ場合、restore_error_handlerrestore_exception_handler等は変更をひとつ前の状態に戻すのに対し、restore_include_pathだけはいきなり初期値に戻します。
動作が異なっており混乱の元であるため削除されます。
今後はini_restore('include_path')を使いましょう。

Deprecate allow_url_include

allow_url_includeディレクティブはPHP7.4でE_DEPRECATEDになり、PHP8で削除されます。

allow_url_include = On

Apacheを起動すると、エラーログにPHP Deprecated: Directive 'allow_url_include' is deprecated in Unknown on line 0が入ります。
allow_url_includeはrequire_once('http://example.com/')と書けるようになるという、ヤバみしかない機能なので絶対にオンにしてはいけません。

感想

変更多過ぎぃ!

いやあPHP7.4やばいですね。
重量級だけでもプロパティ型指定・アロー関数・プリローディング・FFIなどがあって、これだけでも文法や書き方が大きく変化する勢いです。
さらに上記リストには、単なるバグ修正は含まれていません。
そこまで含めたら、とんでもない量の変更になってしまいますね。

PHPは、特にPHP7以降はモダンな書き方をごりごり取り入れていて、きちんと使えば相当に堅牢で厳格な記述を行うことが可能です。
ただ同時に古い書き方も相変わらず可能で、しかも古い書式のほうが圧倒的に文献が多いせいでなかなか新しい文法が広まらないという現状は残念なところです。
新機能を使おうにも未だにPHP5系しか使えないようなサーバも存在したりしますし、そもそもCentOS7のデフォルトPHPが未だに5.4という地雷ですからね1
もっと知識や環境を新陳代謝して使える仕組みが欲しいところです。
まあ、ほんのちょっとでも古い書き方をしようものなら全方位から銃弾が飛んでくるJavaScriptのような世界がいいかって言われたらそれも嫌ですけどね。

さすがにいきなり仕事でプロパティ型指定を書き始めたりはしませんが、一年後には大抵の場所で使えるようになっている、くらいに程々に普及するくらいには進んでほしいところです。

  1. 2019年9月リリースのCentOS8でようやくPHP7.2になった。

346
247
6

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
346
247

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?