Quantcast
Browsing Latest Articles All 29 Live
Mark channel Not-Safe-For-Work? (0 votes)
Are you the publisher? or about this channel.
No ratings yet.
Articles:

【PHP8.0】PHPでunion型が使えるようになる

Union Types 2.0というRFCが投票中です。
提案者はまたまたのNikita。
2019/10/25開始、2019/11/08終了で、受理には2/3+1の賛成が必要です。
2019/11/04時点で賛成55反対5であり、ほぼ導入確定です。

PHPのunion型って何かというと、TypeScriptのunion型とだいたい同じです。
int|string $aと書いたら$aint型もしくはstring型ですよ、ということです。

ちなみに別途RFCをGitHubで管理しようという実験が進行中で、このRFCの詳細はGitHub上に存在します
このRFCはまだNikitaの個人GitHub上にしかないのですが、本決まりになったらPHP公式に移動になると思います。
まあGitHubのほうが管理とか更新とか楽ですからね。
ただGitHubはURLがすぐ404になるのだけはどうにかしてほしい。

Union Types 2.0

Introduction

union型は、単一の型ではなく、複数の異なる型の値を受け入れます。
PHPは既に、2種類の特別なunion型をサポートしています。

・null許容型。?Tで使える。
・array型もしくはTraversable型。iterableで使える。

しかし任意のunion型はサポートされていません。
現在はかわりにphpdoc注釈を書くくらいしかできません。

classNumber{/**
     * @var int|float $number
     */private$number;/**
     * @param int|float $number
     */publicfunctionsetNumber($number){$this->number=$number;}/**
     * @return int|float
     */publicfunctiongetNumber(){return$this->number;}}

Statisticsセクションで、オープンソースでunion型がどれだけ普及しているかを示しています。

言語でunion型をサポートすることにより、より多くの型情報をphpdocに頼ることなく関数シグナチャに移動することができ、多数の利点が得られます。

・型は実際に強制されるため、ミスを早期に発見できる。
・エッジケースを見逃したり、仕様変更の際にドキュメントを更新し忘れたりする可能性が減る。
・継承時にもリスコフの置換原則を適用できる。
・リフレクションから利用できる。
・phpdocより分量が減らせる。

union型は、ジェネリクスと並んで型システムに残っている最大の穴です。

Proposal

union型を構文T1|T2|...で表し、型を書くことができる全ての位置でunion型を使用可能とする。

classNumber{privateint|float$number;publicfunctionsetNumber(int|float$number):void{$this->number=$number;}publicfunctiongetNumber():int|float{return$this->number;}}

Supported Types

union型は、現在PHPでサポートされている全ての型をサポートしますが、一部の型については注意が必要です。

void型

void型は、union型の一部となることは決してできません。
T|voidのような型は、戻り値を含む全ての位置で不正です。

void型は関数に戻り値がないことを表します。
あらゆる非void型と互換がありません。

かわりにnull許容型?Tがあり、これはT型もしくはnullを返すことができます。

null許容型

T1|T2|nullとしてnullを許容するunion型を定義することができます。
既存の?Tは、T|nullの省略形と見做されます。

このRFCの草稿では、null許容型の文法が2種類になることを防ぐため、?(T1|T2)という文法を提唱していました。
しかしこの構文は都合が悪く、さらにphpdocで確立されているT1|T2|null構文とも異なっています。
議論の結果は、T1|T2|nullの文法が圧倒的に優勢でした。
?TT|nullの省略形として今後も有効な構文で、推奨も非推奨もされず、当分は廃止される予定もありません。

null型は、union型の一部としてのみ有効な型で、単独で使うことはできません。

union型と?T表記を混ぜて使用することはできません。
?T1|T2T1|?T2?(T1|T2)は全て不正な文法で、この場合はT1|T2|nullを使う必要があります。

false疑似型

現在では、エラーや不正が起きた際の関数の戻り値はnullにすることが推奨されていますが、歴史的理由から多くの内部関数はfalseを返してきます。
Statisticsセクションで示すように、union型を返す内部関数は大部分がfalseを含んでいます。

一般的な例としてはint|falseを返すstrposなどです。
これを正しく表記するとint|boolですが、これは関数がtrueを返すこともあるという誤った印象を与えます。

そのため、このRFCにはfalseのみを表すfalse疑似型が含まれています。
trueのみを表すtrue疑似型は、それが必要となる歴史的理由が存在しないため、このRFCには含まれません。

false疑似型は、union型の一部としてのみ有効で、単独やnull許容型として使うことはできません。
falsefalse|null?falseは全て無効な構文です。

Duplicate and redundant types

クラスのロードを行わずに検出できる冗長な記述は、コンパイルエラーになります。
これは以下のような例を含みます。

・同じ型の重複。int|string|INTは不可。
boolにはfalseを追加できない。
objectには個別のクラス型を追加できない。
iterableにはarrayTraversableを追加できない。

これは、型が最小であることを保証はしません。
たとえばクラスAとBがクラスエイリアスや継承関係にある場合、A|Bは有効です。

functionfoo():int|INT{}// ×functionfoo():bool|false{}// ×useAasB;functionfoo():A|B{}// × 構文解析時点でわかるclass_alias('X','Y');functionfoo():X|Y{}// 許可 実行時までわからない

Type grammar

特殊なvoid型を除くと、型の構文は以下のようになります。

type: simple_type
    | "?" simple_type
    | union_type
    ;

union_type: simple_type "|" simple_type
          | union_type "|" simple_type
          ;

simple_type: "false"          # union型でのみ有効
           | "null"           # union型でのみ有効
           | "bool"
           | "int"
           | "float"
           | "string"
           | "array"
           | "object"
           | "iterable"
           | "callable"       # プロパティ型指定では無効
           | "self"
           | "parent"
           | namespaced_name
           ;

Variance

union型は、既存の型ルールに従います。
・戻り値は共変。
・パラメータ型は反変。
・プロパティ型は不変。

唯一の変更点は、union型が派生型と相互作用する点であり、そのため3つの追加ルールがあります。

・全てのU_iV_jのサブタイプであった場合、U_1|...|U_nV_1|...|V_mのサブタイプである。
iterablearray|Traversableと同じと見做す。
・false疑似型はboolのサブタイプと見做す。

以下において、許可されているものと許可されないものの例を幾つか示します。

Property types

プロパティの型は不変です。
すなわち、継承しても型は同じである必要があります。
ただし、この"同じ"は意味が同じということを表します。
これまでもクラスエイリアスで同じクラスを表す別名を付けることができました。

union型はこの"同じ"の範囲を広げ、たとえばint|stringstring|intは同じとして扱います。

classA{}classBextendsA{}classTest{publicA|B$prop;}classTest2extendsTest{publicA$prop;}

この例では、親クラスのA|B型と子クラスのA型は明らかに異なる型であるにもかかわらず、これは正当な文法です。
内部的には、以下のようにこの結果に到達します。
まず、それはAのサブタイプであるため、AA|Bのサブタイプです。1
次にAAのサブタイプであり、BAのサブタイプであるため、A|BAのサブタイプです。

Adding and removing union types

戻り値からunion型の一部を削除し、パラメータに一部の型を追加することは正しい文法です。

classTest{publicfunctionparam1(int$param){}publicfunctionparam2(int|float$param){}publicfunctionreturn1():int|float{}publicfunctionreturn2():int{}}classTest2extendsTest{publicfunctionparam1(int|float$param){}// OK: パラメータの型追加は許可publicfunctionparam2(int$param){}// NG: パラメータの型削除は禁止publicfunctionreturn1():int{}// OK: 返り値の型削除は許可publicfunctionreturn2():int|float{}// NG: 返り値の型追加は禁止}

Variance of individual union members

同様に、戻り値の型を狭めたり、パラメータの型を広げることは許可されます。

classA{}classBextendsA{}classTest{publicfunctionparam1(B|string$param){}publicfunctionparam2(A|string$param){}publicfunctionreturn1():A|string{}publicfunctionreturn2():B|string{}}classTest2extendsTest{publicfunctionparam1(A|string$param){}// OK: BをAに広げたpublicfunctionparam2(B|string$param){}// NG: AをBに狭めたpublicfunctionreturn1():B|string{}// OK: AをBに狭めたpublicfunctionreturn2():A|string{}// NG: BをAに広げた}

もちろん同じことを複数のunion型に同時に行ったり、型の追加削除と型の拡縮を組み合わせることもできます。

Coercive typing mode

strict_typesが有効でない場合、スカラー型宣言は暗黙の型変換の対象となります。
これは一部のunion型において、変更先の型を一意に決められないため問題となります。
たとえばint|stringfalseを渡すと、0""の両方が暗黙の型変換の候補になります。

従って、引数に正しくないスカラー値が渡ってきた場合、以下の優先順位で変換することにします。

1.int
2.float
3.string
4.bool

PHPの既存の型変換機構で型変換が可能である場合、その型が選ばれます。

例外として、値が文字列であり、union型がint|floatである場合、優先される型は引数の数値文字列の中身によって決まります。
すなわち、"42"はint型となり、"42.0"はfloat型となります。

上記のリストに含まれない型には自動型変換されません。
特にnullfalseへの自動型変換は起きないことに注意しましょう。

// int|string42-->42// 正しい型"42"-->"42"// 正しい型newObjectWithToString-->"__toString()の結果"// objectはint型にならない42.0-->42// floatはint型になる42.1-->42// floatはint型になる1e100-->"1.0E+100"// floatの上限を超えたらstring型になるINF-->"INF"// floatの上限を超えたらstring型になるtrue-->1// boolはint型になる[]-->TypeError// 配列はint|string型にならない// int|float|bool"45"-->45// 整数っぽいのでint型になる"45.0"-->45.0// 小数っぽいのでfloat型になる"45X"-->45+Notice:Nonwellformednumericstring// 有効部分は整数っぽいのでint型になり、E_NOTICEが出る""-->false// 数値形式文字列でないのでbool型になる"X"-->true// 数値形式文字列でないのでbool型になる[]-->TypeError// 配列はint|float|bool型にならない

Alternatives

自動型変換については、別案が2種類ありました。

ひとつめはunion型は常に厳密な型指定とすることで、複雑な強制型変換を完全に排除することです。
これは2つの欠点があります。
まず、strictでないときに型をfloatからfloat|intにすると、直感に反して有効な入力が減ります。
第二に、float型float|int型のサブタイプと言えなくなるため、union型のモデルが崩壊します。

二つ目が、変換の優先順位を型の順番にすることです。
これは即ちint|stringstring|intが異なる型になることを意味します。
この変換は直感的ではなく、継承関係に非常に不明瞭な影響を及ぼします。

Property types and references

union型プロパティへの参照は、プロパティ型指定RFCに書かれた挙動に従います。
プロパティ型指定とunion型の組み合わせによる影響は、当時から考慮されていました

classTest{publicint|string$x;publicfloat|string$y;}$test=newTest;$r="foobar";$test->x=&$r;$test->y=&$r;// $rと$test->xと$test->yは同じもので、型は{ mixed, int|string, float|string }の論理積になる$r=42;// TypeError

複数のリファレンスが紐付けられている場合、型の強制変換が行われた後の最終的な型は全ての型と互換する必要があります。
上記例の場合、$test->xはint型の42になり、$test->yはfloat型の42.0になります。
これは同じ型ではないため、TypeErrorが投げられます。

この場合は共通の型であるstring型にキャストすることでエラーは出なくなりますが、自動型変換による優先順位と異なるため、型がどうなるかわからないという欠点があります。

Reflection

union型をサポートするReflectionUnionTypeクラスが追加されます。

classReflectionUnionTypeextendsReflectionType{/** @return ReflectionType[] */publicfunctiongetTypes();/* Inherited from ReflectionType *//** @return bool */publicfunctionallowsNull();/* Inherited from ReflectionType *//** @return string */publicfunction__toString();}

getTypes()メソッドは、ReflectionTypeクラスの配列を返します。
この型は、元の型宣言と順番が異なる可能性があり、また等価である別の型になる可能性があります。

たとえばint|string型が["string", "int"]の順で要素を返す場合があります。
またiterable|array|string型は["iterable", "string"]になるかもしれないし["Traversable", "array", "string"]になるかもしれません。
Reflection APIが保証するのは、論理的に同じものであるということです。

allowsNull()メソッドは、union型の要素にnull型が含まれるか否かを返します。

__toString()メソッドは、型宣言を有効なコード表現として返します。
元の型宣言と必ずしも同じではありません。

後方互換性のため、null許容型?TT|nullのunion型は、ReflectionUnionTypeではなくReflectionNamedTypeを返します。

// getTypes()や__toString()の結果は順番が異なることもあるfunctiontest():float|int{}$rt=(newReflectionFunction('test'))->getReturnType();var_dump(get_class($rt));// "ReflectionUnionType"var_dump($rt->allowsNull());// falsevar_dump($rt->getTypes());// [ReflectionType("int"), ReflectionType("float")]var_dump((string)$rt);// "int|float"functiontest2():float|int|null{}$rt=(newReflectionFunction('test2'))->getReturnType();var_dump(get_class($rt));// "ReflectionUnionType"var_dump($rt->allowsNull());// truevar_dump($rt->getTypes());// [ReflectionType("int"), ReflectionType("float"), ReflectionType("null")]var_dump((string)$rt);// "int|float|null"functiontest3():int|null{}$rt=(newReflectionFunction('test3'))->getReturnType();var_dump(get_class($rt));// "ReflectionNamedType"var_dump($rt->allowsNull());// truevar_dump($rt->getName());// "int"var_dump((string)$rt);// "?int"

Backwards Incompatible Changes

このRFCには、後方互換性のない変更はありません。
ただしReflectionTypeを利用しているコードは、union型に関する処理を追加する必要があります。

Future Scope

この項目は今後の展望であり、このRFCには含まれていません。

Intersection Types

交差型は論理的にunion型と似ています。
union型では少なくとも一つの型が満たされる必要がありますが、交差型では全ての型が満たされる必要があります。

たとえばTraversable|CountableTraversableCountableのうち少なくともどちらかである必要がありますが、Traversable&CountableTraversableでありなおかつCountableである必要があります。

Mixed Type

mixed型は、任意の値が受け入れ可能であることを表します。
型指定が無い場合と見た目の動きは同じですが、型指定が無いと、それが本当に自由であるのか、単に型指定を書き忘れただけなのかが区別できません。

Literal Types

このRFCで導入されたfalse疑似型は、TypeScriptでサポートされているリテラル型の特殊なケースです。
リテラル型は、列挙型のように一部特定の値のみを許可できる型です。

typeArrayFilterFlags=0|ARRAY_FILTER_USE_KEY|ARRAY_FILTER_USE_BOTH;array_filter(array$array,callable$callback,ArrayFilterFlags$flag):array;

列挙型ではなくリテラル型を使用する利点は、元の文法をそのまま維持できることです。
そのため、後方互換性を壊すことなく後付けすることができます。

Type Aliases

型が複雑になると、型宣言の再利用が必要になります。
その一般的な方法は2種類が考えられます。
ひとつめは次のようなエイリアスです。

useint|floatasnumber;functionfoo(number$x){}

このnumber型はソースコード上でのみ現れる型で、コンパイル時に元のint|floatに解決されます。

もうひとつは型宣言を定義することです。

namespaceFoo;typenumber=int|float;// \Foo\numberをどこからでも使える

Statistics

上位2000パッケージの@param@returnにおいて、野生のunion型がどれだけ使われているかを分析しました。

@paramのunion型:25k。一覧
@returnのunion型:14k。一覧

PHPの内部関数を調べたところ(調査が不完全なので最低2倍はあるはず)

・336関数がunion型を返す。
・そのうち213関数がfalse型を返す。

多くの内部関数が、戻り値の型を表すためにfalse疑似型が必要であることを示しています。

感想

元々型に厳密なTypeScriptがunion型を導入する理由はまあわかるんですよ。
しかしですね、元々フリーダム型だったPHPがわざわざ型宣言やらプロパティ型指定やらで狭めてきた上でのunion型って、なんというかこうマッチポンプ感とかそんな感じを感じざるをえない。
いやまあ違う話だってのは理屈ではわかるんですけど感覚的にね。

PHPにおいてこの機能の使い道は、DateTime|falsearray|nullといった、失敗時に例外を出さず無効値を返す関数の呼び出しにほぼ限られるでしょう。
あとは$_REQUESTを受け取るときのint|stringくらいでしょうか。
PDO|GMPなんて書いてきた人がいたら、設計をどうにかしろと突っ返します。

PHPでunion型をうまく使う方法、正直あまり思いつきません。
誰かがきっといいサンプルを考えてくれるはず。


  1. itがどれにかかってるのかわからなかった。というかここの文の意味がわからん。 

CentOS8で、PHP8のインストール。

こんにちは。
BitByteの伊井です。

今日は、CentOS8上で、PHP8をソースからビルドして
インストールする方法をご紹介します。

まず、CentOS8環境を、Vagrantでも、普通にクラウドでも良いので、
用意します。

そして、おもむろに以下のコマンドを実行していきます。

dnf install update
dnf install git sqlite
dnf groupinstall "Development Tools"

groupinstall〜〜で、PHPビルドに必要なライブラリを、
まとめてCentOS上にインストールしてくれます。

次に、PHP8のソースを取ってきましょう。

cd ~
git clone https://github.com/php/php-src.git
cd php-src

その後、以下のコマンドを実行します。

./buildconf

そして、configureコマンドを実行します。

./configure --prefix=/opt/php/php8 --enable-opcache --with-zlib
--enable-zip --enable-json --enable-sockets --without-pear

この際に、ライブラリが足りなかったりすると、そこで止まって、
失敗してしまいます。
手順で必要なライブラリのインストールコマンドは冒頭で記載しましたが、
sqliteなどでエラーが出る可能性があります。何とか成功させてください。

configureコマンドに成功すると、以下のような表示がされ、正常終了します。
+--------------------------------------------------------------------+
| License: |
| This software is subject to the PHP License, available in this |
| distribution in the file LICENSE. By continuing this installation |
| process, you are bound by the terms of this license agreement. |
| If you do not agree with the terms of this license, you must abort |
| the installation process at this point. |
+--------------------------------------------------------------------+

Thank you for using PHP.

次に、CentOS環境で有効なCPUのコア数を表示します。
nproc

nprocで表示された数値を、-jオプションに付けて、makeコマンドを
実行します。ここでは、コア数1を想定します。
make -j1

ビルドが完了すると、以下のような表示がされて、正常終了します。
Build complete.
Don't forget to run 'make test'.

次に、
make test

を実行します。
もしかすると、errorコード1などとなり、英語で何かしらの問題が
あるかもしれませんなどと、表示されるかもしれませんが、あくまで
開発バージョンですので、無視して完了しましょう。

最後に、インストールを行います。
make install

バージョンを確認しましょう。
/opt/php/php8/bin/php -v

PHP 8.0.0-dev (cli) (built: Apr 5 2019 11:19:45) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
It starts to get interesting.

PHP8がインストールされています。

インストール時には、OPcacheは、無効となっています。
/opt/php/php8/libに、iniファイルを作成して、1行追加します。

cd /opt/php/php8/lib
touch php.ini

zend_extension=opcache.so

PHP8では、JITの効果が先立って、体験できます。
ここでは、割愛しますが、プログラムをネイティブコードに変換し、
それをキャッシュして再利用するというプログラム実行が高速化される機能となっています。

JITを有効にするには、以下のようなコマンドで実行する必要があります。
/opt/php/php8/bin/php -d opcache.enable_cli=1 (オプション)PHPファイルパス

このあたりは、ネットに転がっていると思いますので、時間があればJITに関しても、色々と
研究してみてください。

それでは、長文失礼しました。

【PHP8.0】マイナススタートの配列インデックスが使えるようになる

PHP7.4
$arr=[];$arr[-10]=1;$arr[]=1;

どうなるかというと[-10=>1, 0=>1]です。

これはマニュアルにも明記されています。

1.png

しかしこの動作はPHP8.0で変更になります。
Arrays starting with a negative indexというRFCにおいて変更が決定しました。

Arrays starting with a negative index

Introduction

array_fillのドキュメントには、『start_index が負の場合は、 返される配列の最初のインデックスが start_index となり、それ以降のインデックスはゼロから始まります』とあります。

明示的に負のキーを使用し、その後キーを省略した場合は常にこのような動作になります。

Proposal

現在の動作は、最後の数値キーがnであった場合、次の暗黙キーはn>=0であればn+1n<0であれば0ということになります。

このRFCでは、nの符号に関係なく常にn+1とすることによって、この動作を一貫させることを目指しています。

配列のドキュメントにもわざわざ注釈として書かれているように、これは例外的な動作であり、学習コストとなります。

このプロポーザルはPHP8.0を対象としています。
この変更により問題が発生する可能性がある場合、移行を容易にするために非推奨の通知を出すことも提案します。

以下のコードは全て同じ結果になります。

$a=array_fill(-2,3,true);$b=[-2=>true,true,true];$c=["string"=>true,-2=>true,true,true];unset($c["string"]);$d[-2]=true;$d[]=true;$d[]=true;

現在は全て以下のようになります。

~PHP7.4
array(3){[-2]=>bool(true)[0]=>bool(true)[1]=>bool(true)}

このRFCにより、以下の動作に変更になります。

PHP8.0~
array(3){[-2]=>bool(true)[-1]=>bool(true)[0]=>bool(true)}

非推奨を出力する投票が受理された場合、移行フェーズ中は以下のE_DEPRECATEDを発生させます。

Deprecated: In the next major version of PHP the implicit keys of this array will start from -1 instead of 0 in ...

Backward Incompatible Changes

配列を負のインデックスから開始し、その後暗黙のキーを使い、そして明示的なキーで配列要素にアクセスした際に影響が発生します。

$a[-2]=true;// 現行:キーは-2 / RFC:キーは-2$a[]=true;// 現行:キーは0 / RFC:キーは-1$a[]=true;// 現行:キーは1 / RFC:キーは-0if($a[1]===true){echo'Accessing key 1 explicitly';}

PHP8.0以降、'Accessing key 1 explicitly'が出力されなくなります。

Proposed PHP Version(s)

PHP8.0。

Unaffected PHP Functionality

明示的なキー、文字列のキーには影響しません。
また、-1以上の数値インデックスを持つ配列も影響を受けません。
最初から暗黙のキーを使う配列のインデックスは0から始まります。

キーを使用しないforeachも影響を受けません。

投票

このRFCは賛成17反対2で可決されました。

E_DEPRECATEDを出す提案は賛成8反対14で却下されました。

すなわち、PHP8.0で前触れなく突然動作が変更になります。

感想

PHP4.2から長い時を経て、元の鞘に戻ることになりました。
さすがに当時のことは知らないので、どうして変更されたのかは全くわかりません。

しかし、この仕様に依存する実装をしている人なんていないでしょう。
すなわち実質的な影響はほぼゼロです。
そもそもネガティブスタートの配列って何に使うんだろうか?

ただ、MySQLのAUTO_INCREMENTマイナスは使えないですし、あえて変更する必要があるのかは少し疑問です。

あと配列をコピーしたら何かおきそうで少し楽しみですね。

php8 JIT vs いろんな言語

php8 JITと他のサーバーサイド言語を使ってパフォーマンステストをしました。

php8 JITが早くなったということで、他の主要なサーバーサイドの言語とパフォーマンステストして比較する。
どの言語が一番速いかどうかの検証

パフォーマンステスト用メソッド (フィボナッチ数列の総数計算)

$n=35;functionfibonacci(int$n):int{return(($n<2)?1:fibonacci($n-2)+fibonacci($n-1));}

php8 JIT + 比較する言語

12345
php8-dev JITgo 1.13Java 11.0.4python 3.8.1Node v13.6.0(javascript)

テスト環境

各言語の実行環境は、AlpineをベースのDockerイメージをDockerHubのものを使用しています。

実行結果

言語速度
php8-dev JIT0.444秒
go 1.130.052秒
Java 11.0.40.038秒
Python 3.8.12.449秒
Node v13.6.00.2001秒

総括

当たり前ですが、コンパイラ型言語のgoとJavaが圧倒的に早かった
同じインタプリタ言語である、php, python, nodeの中でもpythonが圧倒的に遅かったですが、
Pythonが特別遅いというより、php 8 JITが早くなったという認識のほうが正しいかも。

コード

https://github.com/okeita0805/performance_test

PHP8にstr_contains関数が追加されることになったというお話

strposstrstrを使わずに済むようになる・・・かもしれません( 日本語対応にやや懸念あり)。

以下、 RFCをもとに紹介します。

まだ投票中ですが、3月8日現在、賛成42/反対5という圧倒的多数の支持を得ているため、追加される公算が高いです。

既存関数の問題点

ある文字列が別の文字列に含まれているかをチェックする際、これまで strposstrstrといった関数が使われてきました。

しかし、これらの関数は、以下の問題を抱えています。

  • 読み手にとってあまり直感的でない
  • 間違いやすい
  • 新しく PHP に触れる開発者にとって覚えにくい

そのため、多くの PHP フレームワークが、同様の振る舞いをするヘルパー関数を提供しています。それだけ、この機能は、重要だし、必要とされているということです。

提案

概要

str_contains ( string $haystack , string $needle ) : bool

$needle$haystackに含まれているかをチェックし、$needleが見つかったかを真偽値( true/false)で返します。

例えば、以下のようになります。

str_contains("abc","a");// truestr_contains("abc","d");// false// $needle が空文字だった場合str_contains("abc","");// truestr_contains("","");// true

$needleが空文字だった場合については「空文字は文字列のどの位置にも出現する」と考えて trueを返すことにします。

マルチバイト版

内部メーリングリストでの議論を踏まえて、この関数にはマルチバイト版(例えば mb_str_contains)を提供する必要はないとの結論に至りました。

というのも、マルチバイト版と非マルチバイト版の動作に差がないと考えられるからです。文字列が見つかったオフセットや位置が関係する場合、マルチバイト版は異なる振る舞いをします。

しかし、この関数には、その必要はありません。

大文字小文字を区別しない版

大文字小文字を区別しない版については、今回は実装しません。

提案者は、最初は小さくはじめて、徐々に大きくしていくほうが良いと考えているからです。1

後方非互換性

PHP 自体には後方非互換性はありません。
ただし、ユーザのコードで、この関数を実装している場合、後方非互換が発生するかもしれません。

感想

strposは文字列の位置を返す変数ですが、1文字目に含まれている場合は 0を返します。そして、どこにも含まれていない場合には falseを返します。

PHP では 0 == falseです。そのため、以下のような書き方をすると、バグになってしまいます。

// 1文字目に含まれている場合は 0 が返る$pos=strpos("abc","a");// 間違った書き方if(!$pos){die("含まれていない");}// 正しい書き方if($pos===false){die("含まれていない");}

そもそも strposは「文字列の位置を返すための関数」ですし、 strstrは「部分文字列を抽出するための関数」です。
どちらも文字列が含まれているかをチェックするための関数ではない

strstrってなんだよ。str s ubs tr ing のつもりか? 覚えられん!

この使いづらさから、例えば Laravel には Str::containsという メソッドが用意されていたりしますが、とうとう、PHP 本体にも入ることになったようです。万歳!

気になるのは、「日本語文字コードでも正しく動くか?」ということです。
strposには mb_strposがあり、同様に mb_str_containsも必要なのではないでしょうか。

実際、内部メーリングリストの議論を見ると、「UTF-8 ではちゃんと動くが Shift_JIS では動かないのでは?」という 意見もあり、怪しい気がします。2

This is not true for all character encodings. For UTF-8 it is correct,
but consider for example the Japanese encoding Shift_JIS, where the
second byte of a multi-byte character can be a valid first byte of a
single-byte character. str_contains() would have incorrect behaviour for
this case.

日本語対応にやや懸念は残るものの、他の言語には普通に備わっている関数が PHP にも追加されること自体は良いことなのではないか、と思います。


  1. 内部メーリングリストの議論を見ると、 str_starts_withstr_ends_withを追加する RFCが最初から大文字小文字の区別やマルチバイト版を用意した結果、否決されてしまったショックが大きいようです。  

  2. これに対して、 Nikita Popov が 返答していますが、 "That's of course true, but I consider it ultimately unimportant." と一蹴されちゃっています(苦笑) なんとなく「細かいことはあとでいいから、とりあえず導入したいんだよ!」という雰囲気を感じます。この辺の議論は内部実装の話になっていて、あまり理解できている自信がありませんが。 

【PHP8】もう`strpos($haystack, $needle)!==false`って書かなくていいんだ

ある文字列中に特定の文字列が存在するかを調べる方法としてstrposが存在します。

しかし、そもそもstrposは『ある文字列中で特定の文字列が何文字目に出てくるか』を調べる関数であり、第一に使用目的が異なる関数です。
そしてこちらも有名な話ですが、先頭が一致すると0が返ってくるので、緩やかな比較ではfalseと区別されません。
PHPのよくある落とし穴のひとつです。

if(strpos('放課後アトリエといろ','アトリエ')){echo'"放課後アトリエといろ"には"アトリエ"が含まれる';// 表示される}if(strpos('放課後アトリエといろ','放課後')){echo'"放課後アトリエといろ"には"放課後"が含まれる';// 表示されない!!}if(strpos('放課後アトリエといろ','放課後')!==false){echo'"放課後アトリエといろ"には"放課後"が含まれる';// 表示される}

マニュアルにも大きく警告が出されています。
01.png

この状況がついにPHP8で変わります

PHP RFC: str_contains

Introduction

str_containsは、ある文字列に特定の文字列が含まれているかをチェックし、見つかったか否かによってtrue/falseいずれかのbooleanを返します。

ある文字列に特定の文字列が含まれているかをチェックするために、一般的にはstrposstrstrが使用されます。
str_containsは凡そあらゆるプロジェクトで非常によく使われるユースケースなので、独自の関数を追加するに値するはずです。
strposstrstrには、いくつかの欠点が存在します。

・あまり直感的ではない
・間違いをしやすい(特に!==の場合)
・新規PHP開発者が覚えにくい

このため、多くのPHPフレームワークは独自のヘルパ関数を提供しています。
このことこそがstr_containsの重要性や必要性をよく示しています。

Proposal

このRFCは、新しい関数str_containsを提唱します。

str_contains(string$haystack,string$needle):bool

引数$haystack$needleを取り、$haystackの中に$needleが存在するか否かをbooleanで返します。

str_contains("abc","a");// truestr_contains("abc","d");// false// 空文字列はtrueになるstr_contains("abc","");// truestr_contains("","");// true

空文字列の扱いについて

As of PHP 8, behavior of '' in string search functions is well defined, and we consider '' to occur at every position in the string, including one past the end. As such, both of these will (or at least should) return true. The empty string is contained in every string.

PHP8において、文字列検索関数において''の動作は明確に定義されており、終端を含む文字列全ての位置にマッチします。そのため、これらは両方ともtrueを返すべきです。空文字列はあらゆる文字列に含まれています。 - Nikita Popov

Case-insensitivity and multibyte strings

internalメーリングリストなどの議論において、この関数のマルチバイト対応版(mb_str_contains)は必要ないという結論に達しました。
理由として、この関数のマルチバイト版は非マルチバイト版と全く同じ動作になります。
文字列の見付かった位置によって動作が異なる場合は、マルチバイト版は異なる動作になります。
この関数は見付かった位置によって動作は変わらないので、マルチバイト版の必要はありません。

大文字と小文字を区別しない版については、需要が大文字と小文字を区別する版よりはるかに低いため、このRFCには含まれません。
区別しない版を取り込むと、有効なバリアントはstr_contains/mb_str_icontainsだけになります。
このように中途半端なバリアントをいきなり提供するとPHP開発者が混乱する可能性があるので、最初は小さく始めた方がよいでしょう。

Backward Incompatible Changes

PHP自体に後方互換性のない変更はありません。

ユーザランドに同じ関数が実装されている場合、非互換の可能性があります。
しかし、そのようなグローバル関数はアプリケーションの起動プロセスのかなり早い段階で追加されるので、開発者はこの問題にすぐ気付くでしょう。

Proposed PHP Version(s)

PHP 8

Implementation

str_contains関数は、このプルリクで実装されています。
https://github.com/php/php-src/pull/5179

投票

投票は2020/03/16まで、受理には2/3の賛成が必要です。
2020/03/09時点では賛成43反対5で、まず間違いなく受理されます。

感想

よく考えたらずっと前からあってもおかしくない関数なのに、これまで存在してなかったのは不思議ですね。

実際のところ、文字数としてはわずか2字の差にすぎません。

if(strpos($heystack,$needle)!==false){echo'$heystackに$needleが含まれている';}// 同じif(str_contains($heystack,$needle)){echo'$heystackに$needleが含まれている';}

しかし、読みやすさ、理解のしやすさという点では圧倒的にstr_containsに分がありますね。

以下はマルチバイト対応関数が用意されない点の補足です。
マルチバイト対応strposとしてmb_strposがありますが、文字が存在するか否かをチェックするだけであれば実はstrposでも問題ありません。

echostrpos('あい','い');// 3echomb_strpos('あい','い');// 1echostrpos('あい','あ');// 0echomb_strpos('あい','あ');// 0echostrpos('あい','う');// falseechomb_strpos('あい','う');// false

値が変わるのは『存在したときの文字数の数え方』だけです。
文字列の有無を確認するだけであれば、存在しない場合は常にfalse、存在する場合は常にint型となって差が出ないわけですね。

同様にstr_containsも、存在しない場合は常にfalse、存在する場合は常にtrueなるため、あえてmb_str_containsを用意する必要はないというわけです。

【PHP8.0】gettypeとget_classの悪魔合体

ワレハget_debug_type、コンゴトモヨロシク…

PHPにはプリミティブ型名を取得するgettypeと、オブジェクトのクラス名を返すget_classという関数が存在します。
_があったりなかったりと命名の不統一も気になりますが、それよりgettypeはオブジェクトに使うとobjectしか返さず、get_classをプリミティブ型に使うとE_WARNINGが発生します。
いや、プリミティブ型であればintとかの型が欲しいし、オブジェクトならPDOとかの型が欲しいんだ、という問題に対する答えはありませんでした。

というわけで両者を合体させたget_debug_typeというRFCが提出されました。

PHP RFC: get_debug_type

proposal

このRFCは、指定された変数の型を返す新しい関数、get_debug_typeを追加する提案です。

これは、配列で来る値など、変数の型に基づいた既存のチェック方法では対応できないパターンを置き換えるためのものです。

$bar=$arr['key'];if(!($barinstanceofFoo)){// もっとも単純な例。しかしgettypeは"integer"を返すので、正しい型にしたいなら"int"に変換するなどが必要。thrownewTypeError('Expected '.Foo::class.' got '.(is_object($bar)?get_class($bar):gettype($bar)));}// 今後はこう書けるif(!($barinstanceofFoo)){thrownewTypeError('Expected '.Foo::class.' got '.get_debug_type($bar));}$bar->someFooMethod();

この関数は、正しい型名を返すという点でgettypeと異なります。
"integer"ではなく"int"を返し、クラスもクラス名に変換します。
次の表は、いくつかの値に対してgettypeget_debug_typeが返す値を比較したものです。

get_debug_type()gettype()
0intinteger
0.1floatdouble
trueboolboolean
falseboolboolean
"hello"string
[]array
nullnullNULL
Foo\BarFoo\Barobject
無名クラスclass@anonymousobject
リソースresource (xxx)resource
閉じたリソースresource (closed)

Backward Incompatible Changes

なし。

Proposed PHP Version(s)

PHP8.0

Implementation

https://github.com/php/php-src/pull/5143

投票

投票は2020/03/26まで、2/3の賛成で受理されます。
このRFCは賛成42、反対3で受理されました。

感想

Mark Randallは最初はgettypeがクラス名も返すようにしようとしたものの、Nikitaから「新しい関数にしてくれ」と言われてget_debug_typeを作ったようです。
まあ、これまでobjectとしか言わなかったgettypeがいきなり色々な型を喋り出したら困るところも出そうですからね。

ということで、今後は型の取得はget_debug_typeに一本化できそうです。

手間を省くための定型処理を言語機能に取り込むことは、他の言語でも多々起きていることです。
たとえばJavaScriptのasync/awaitPromiseの糖衣にすぎず、async/awaitができることは全てPromiseでもできるので、究極的にはasync/awaitは不要です。
しかし非同期処理を楽に書けるようにするために言語仕様に取り込まれました。
糖衣構文の取り込み自体は、このようにさほど珍しいことでもありません。

しかし、str_containsとかis_countableとか、他言語であれば「自分で書け」と言われそうな極端に簡単な構文まで言語仕様に取り込んでしまう言語は、PHP以外にはそうそう無いのいではないかと思います。

【PHP8.0】throw文がthrow式になる

throw expressionというRFCが投票中です。

最初のアイデアは2019/12/06のSebastiaan Andewegによるツイート

それに対して2020/03/19にCarusoが反応し、そしてその日のうちにiluuu1994が最初のプルリクを出しました
はえーよ。

throw expression

Introduction

PHPのthrowは文であるため、アロー関数や三項演算子、NULL合体演算子などの式しか許されない場所から例外を投げることができません。
このRFCでは、それらを可能にするためthrow文を式に変更することを提案します。

Proposal

式を記述可能なあらゆるコンテキストでthrowが利用可能になります。
以下は思いついた便利そうな例です。

// アロー関数$callable=fn()=>thrownewException();// nullチェック$value=$nullableValue??thrownewInvalidArgumentException();// trueっぽいチェック$value=$falsableValue?:thrownewInvalidArgumentException();// 空ではない配列チェック$value=!empty($array)?reset($array):thrownewInvalidArgumentException();

他にも、議論の余地のある使用方法があります。
このRFCでは、以下のような記述も許可されています。

// ifを使った方が意図が明確になる$condition&&thrownewException();$condition||thrownewException();$conditionandthrownewException();$conditionorthrownewException();

Operator precedence

throwが式になると、優先順位を決める必要があります。
以下は現時点で有効な書式の例です。

throw$this->createNotFoundException();// こうなるthrow($this->createNotFoundException());// こうではない(throw$this)->createNotFoundException();throwstatic::createNotFoundException();// こうなるthrow(static::createNotFoundException());// こうではない(throwstatic)::createNotFoundException();throw$userIsAuthorized?newForbiddenException():newUnauthorizedException();// こうなるthrow($userIsAuthorized?newForbiddenException():newUnauthorizedException());// こうではない(throw$userIsAuthorized)?newForbiddenException():newUnauthorizedException();throw$maybeNullException??newException();// こうなるthrow($maybeNullException??newException());// こうではない(throw$maybeNullException)??newException();throw$exception=newException();// こうなるthrow($exception=newException());// こうではない(throw$exception)=newException();throw$exception??=newException();// こうなるthrow($exception??=newException());// こうではない(throw$exception)??=newException();throw$condition1&&$condition2?newException1():newException2();// こうなるthrow($condition1&&$condition2?newException1():newException2());// こうではない(throw$condition1)&&$condition2?newException1():newException2();

共通して言えるのは、全てがthrowキーワードより高い優先順位を持つということです。
このため、このRFCではthrowキーワードの優先順位を可能な限り低くすることを提案します。
現在有効なコードは、たとえ直感に反する動作だったとしても、今後も同じ動作をし続けます。
なぜなら、一般的にthrowは最後に使用するべき演算子であり、それ以降に記述した式は評価されないからです。

低い優先順位の唯一の欠点は、短絡評価のために括弧が必須になることです。

$condition||thrownewException('$condition must be truthy')&&$condition2||thrownewException('$condition2 must be truthy');// こうなる$condition||(thrownewException('$condition must be truthy')&&$condition2||(thrownewException('$condition2 must be truthy')));// こうではない$condition||(thrownewException('$condition must be truthy'))&&$condition2||(thrownewException('$condition2 must be truthy'));

もっとも、こんなコードはほぼ使われていないでしょう。

Backward Incompatible Changes

後方互換性のない変更はありません。

Other languages

C#では同じ文法が2017年に実装されました。

このような言語は他にはあまりありません。
ECMAScriptにプロポーザルがありますが、これは同じ問題を抱えているからです。

Proposed PHP Version(s)

PHP8。

投票

投票は2020/04/19まで、2/3の賛成で受理されます。
2020/04/06時点では賛成14、反対1で、受理される見込みです。

過去のML

9年前とか15年前にも同じ発想があったようですが、そのときは立ち消えになりました。
当時とはPHPのおかれた環境やRFCの出し方などがだいぶ異なることと、そしてなにより実物のプルリクがあるというのは大きいでしょう。

感想

ややこしいよね文と式。
全てが式になればいいのに。

というわけで、今後はもっと気軽にthrowすることができるようになります。
それどころかアロー関数で引数によって値を返したり例外Aを出したり例外Bを出したりすることもできちゃいますよ。
まあ正直、throwを出すようなややこしい式をアロー関数に書くんじゃないよと思ったりはするわけですが。

【PHP8.0?】PHPに名前付き引数が実装されるかもしれない

PHPのソースを眺めていたら、先日2020/04/07にNikitaがなんか面白そうなプルリクを出していました。
Named Parametersという2013年に提出されたまま忘れ去られたRFCがあるのですが、その機能を実装したものです。

どういう機能ってこういうのです。

functionhoge($foo,$bar){echo"foo=$foo,  bar=$bar";}hoge(bar=1,foo=2);// foo=2, bar=1

PythonCSharpなんかで実装されてるやつですね。

Nikita本人は機能が幾つも不足しているよと言っているのですが、不足の内容はOpcache対応や引数アンパックといった周辺機能で、基本的な機能は既に実装されているみたいです。

PHP RFC: Named Parameters

State of this RFC

これは名前付き引数についての準備的なRFCです。
このRFCの目的は、次のPHPバージョンで名前付き引数をサポートすべきか、またサポートするときはどのように実装すべきかを確認することです。
ここで解説している構文や動作は最終的なものではなく、詳細を詰めていく必要があります。

このRFCの実装はまだ完全なものではありません。
これは非常に複雑な機能なので、この機能が必要でなかったのに時間をかけて実装したくはありません。

Update 22-05-2014

私は他のことで忙しく、このRFCには取り組めていません。
このRFCはPHP6に間に合うように復活させるつもりです。
それまでの間、未解決の問題に対する議論の結果の一部をここにまとめておきます。

・名前付き引数のコンセンサスはまだ得られていません。
・名前付き引数のアンパックと、名前のない引数のアンパックは...構文に統合されます。アンパックのRFCは既に決定しているため、ここについて選択肢はあまりありません。
・継承時のパラメータ名チェックは強制されません。これはあまりにも大きなBC breakになるとはんだんされました。

主な未解決の実装上の問題は"Patch"セクションにリストアップされています。

What are named arguments?

名前付き引数とは、パラメータの順番ではなくパラメータ名を使って関数に引数を渡す方法です。

// 通常の引数array_fill(0,100,42);// 名前付き引数array_fill(start_index=>0,num=>100,value=>42);

名前付き引数に渡す引数の順番は自由です。
上記例では関数シグネチャと同じ順番で引数を渡していましたが、異なる順番で渡すこともできます。

array_fill(value=>42,num=>100,start_index=>0);

名前付き引数と名前のない引数を組み合わせることも可能であり、オプション引数の一部のみを名前付き引数で渡すことも可能になります。

htmlspecialchars($string,double_encode=>false);// ↓と同じhtmlspecialchars($string,ENT_COMPAT|ENT_HTML401,'UTF-8',false);

What are the benefits of named arguments?

名前付き引数の明白な利点の一つは、上のコードサンプルを見ればわかります。
変更したい引数までの間にある引数に対して、それぞれデフォルト値を指定する必要から解放されます。
名前付き引数があれば、変更したい引数だけを直接指定することができます。

これはデフォルト引数のRFCでも可能ですが、名前付き引数を使えば意図がより明白になります。
構文を比較してみてください。

htmlspecialchars($string,default,default,false);// vshtmlspecialchars($string,double_encode=>false);

ひとつめのコードを見ても、たまたまhtmlspecialcharsの引数を丸暗記していないかぎりfalseが何を意味するのかは分からないでしょう。

コードの自己文書化の利点は、オプション引数をスキップしないときにおいても明らかです。

$str->contains("foo",true);// vs$str->contains("foo",caseInsensitive=>true);

名前付き引数を使うことで、新しい方法で関数を使うことができるようにbなります。
引数を順番に並んだリストとしてだけではなく、キーと値のペアのリストとしても扱えるということです。
以下のような使い方が考えられます。

// 現在の構文$db->query('SELECT * from users where firstName = ? AND lastName = ? AND age > ?',$firstName,$lastName,$minAge);// 名前付き引数$db->query('SELECT * from users where firstName = :firstName AND lastName = :lastName AND age > :minAge',firstName=>$firstName,lastName=>$lastName,minAge=>$minAge);

Implementation

Internally

名前付き引数は、他の引数と同じくVM stackを通して渡されます。
これらの引数の違いは、位置引数は常にスタックの先頭に渡されるのに対し、名前付き引数は任意の順番でスタックに挿入することができるということです。
使用されないスタックの位置にはNULLが入り、引数カウントはNULLも数えます。

Errors

位置引数と名前付き引数を混在させることが可能ですが、名前付き引数は位置引数の後に配置しなければなりません。
そうしなければコンパイルエラーが発生します。

strpos(haystack=>"foobar","bar");// Fatal error: Cannot pass positional arguments after named arguments

可変長引数ではない関数に存在しない引数名を渡した場合、致命的エラーが発生します。

strpos(hasytack=>"foobar",needle=>"bar");// Fatal error: Unknown named argument $hasytack

同じ名前の引数を複数回渡した場合、新しい方で古い方が上書きされ、警告が発生します。

functiontest($a,$b){var_dump($a,$b);}test(1,1,a=>2);// 2, 1// Warning: Overwriting already passed parameter 1 ($a)test(a=>1,b=>1,a=>2);// 2, 1// Warning: Overwriting already passed parameter 1 ($a)

Collecting unknown named arguments

可変長引数の...$args構文を使った場合、余った名前付き引数は$argsに集められます。
名前付き引数は常に位置引数より後ろとなり、渡された順番が保持されます。

functiontest(...$args){var_dump($args);}test(1,2,3,a=>'a',b=>'b');// [1, 2, 3, "a" => "a", "b" => "b"]

使用例としては前述の$db->query()があります。

これはPythonで**kwargsと呼ばれている機能です。

Unpacking named arguments

引数アンパックのRFCは名前付き引数のアンパックにも対応します。

$params=['needle'=>'bar','haystack'=>'barfoobar','offset'=>3];strpos(...$params);// int(6)

文字列キーを持つ任意の値は、名前付きパラメータとして展開されます。
それ以外のキーは通常の位置引数として扱われます。

位置引数と名前付き引数をひとつの配列にまとめることも可能ですが、その場合でも引数の順番は守らなければなりません。
名前付き引数より後に位置引数が出てきた場合は警告がスローされ、アンパックは中止されます。

func_* and call_user_func_array

名前付き引数を使って、スタックから引数としてNULLが渡ってきた場合、func_*関数の挙動は以下のようになります。

func_num_args()はNULLを含んだ引数の個数を返す。
func_get_arg($n)はデフォルト値を返す。デフォルト値がない場合はNULL。
func_get_args()はデフォルト値を返す。デフォルト値がない場合はNULL。

3関数とも、未定義の引数名は無視します。
func_get_argsは値を返さず、func_num_argsはカウントに含めません。

call_user_func_arrayは名前付き引数をサポートしません。
文字列キーを持つ配列を渡すコードが壊れるからです。

Open questions

Syntax

現在の実装および提案では、名前付き引数について以下2種類の構文をサポートしています。

test(foo=>"oof",bar=>"rab");test("foo"=>"oof","bar"=>"rab");

ふたつめの構文は、引数名が予約語である場合のためにサポートされています。

test(array=>[1,2,3]);// syntax errortest("array"=>[1,2,3]);// works

この構文の選択は恣意的なもので、特に深く考えずに採用しました。
以下に、いくつか代替構文の提案があります(ほとんどはPhil Sturgeonによる提案です)。

// currently implemented:test(foo=>"oof",bar=>"rab");test("foo"=>"oof","bar"=>"rab");// キーワードを使えるtest($foo=>"oof",$bar=>"rab");test(:foo=>"oof",:bar=>"rab");test($foo:"oof",$bar:"rab");// キーワードを使えないtest(foo="oof",bar="rab");test(foo:"oof",bar:"rab");// 既に有効なコードなので不可test($foo="oof",$bar="rab");

どの構文で決定するかは議論次第です。

Collection of unknown named args into ...$opts

現在のRFCでは、位置引数と名前付き引数の両方がまとめて可変長引数の...$optsに入ってきます。
Pythonでは前者を*argsに、後者を**kwargsに入れるというアプローチをとっています。

Pros:PHPでは、Pythonではできない配列と辞書の混在ができます。
Cons:位置引数と名前付き引数を別にすることで、意図がより明確になります。必ずしも両方の引数をサポートしたいとはかぎらず、片方だけを強制したいかもしれません。

どのように扱うのが適切か、意見や議論を歓迎します。

Unpacking named args

引数アンパックについても同じ疑問が出てきます。
...$fooは位置引数と名前付き引数を一緒にできますが、*$foo**$fooに分けるべきでしょうか。

この決定は、可変長引数と同じに揃えるべきでしょう。

Signature validation allows changing parameter names

現在のところ、引数名はシグネチャに含まれていません。
位置引数しか使わない場合、これは合理的です。
引数名は関数の呼び出しに関係ないからです。

名前付き引数はこの動作を変更します。
継承先クラスが引数名を変更した場合、名前付き引数を使った呼び出しは失敗し、LSPに違反します。

interfaceA{publicfunctiontest($foo,$bar);}classBimplementsA{publicfunctiontest($a,$b){}}$obj=newB;// Pass params according to A::test() contract$obj->test(foo=>"foo",bar=>"bar");// ERROR!

名前付き引数が導入された場合、シグネチャの検証において引数名の変更にエラーを出さなければなりません。
通常の場合、インターフェイスと実装クラスの不一致は致命的エラーを発生させますが、名前付き引数において致命的エラーを出すとBC breakが大きくなりすぎてしまいます。
かわりに、より低いエラータイプ(WARNING / NOTICE / STRICT)を出すことを検討します。

これに関する具体的な議論のポイントをひとつ挙げておきます。
PHPは、実行時の動作を変更するini設定を導入する習慣を過去に置いてきました。
従って、この挙動をiniで制御できるようにすることは、私の選択肢にはありません。

Patch

差分がこちらにありますが、このパッチは不完全で、ダーティで、既知のバグがあります。

やるべき作業はまだまだ残っています。

・"Open questions"の結果を実装する。
・内部関数の全てのarginfosをドキュメントと一致するように更新する。現在のarginfo構造体は絶望的に時代遅れで、引数割り当てなどは自動的にできるようにしたい。
・内部関数において引数がスキップされたときに適切に動作するようにする。ほとんどの場合は自動的に動作するはずだが、手動調整が必要になる関数もかなりあるだろう。

感想

2014年とかPHP6とか出てくることからわかるように、このRFCはだいぶ昔に書かれてそのまんまです。
当時作成されたパッチはもはや使い物にならないため、新たにプルリクを作ってきたようです。
パッチにしろその他の内容にしろ、当時のPHPと今のPHPはだいぶ異なったものになっているので、なんにしろRFCのリファインは必要になるでしょう。

このプルリクについても、とりあえず提出されただけで何の展開もありませんし、メーリングリストでの議論も特に進んでいるわけではありません。
従って、このプルリクも今後どうなるかはわからず、再びこのまま忘れ去られるかもしれません。
しかしNikitaのことですから、いきなり完動品のプルリクが送られてきて第一線に躍り出る、なんてことがあっても驚きはないですね。

混沌の時代に実装された関数などでは特に、同じ内容の関数でも引数の順番が異なったりしていて大変なのですが、この機能が実装されたら、そのあたりを楽に処理できるようになります。
また却下されたデフォルト引数のRFCも、この名前付き引数があれば不要になります。
絶対にないといけないというほどでもないですが、存在すれば純粋に便利になる、そんな良い機能だと思います。

あとここからは完全に妄想ですが、このプルリクはジェネリクスへの足掛かりなのではないかと感じています。
Nikitaはどうもジェネリクス大好きっ子みたいですから、このプルリクを元に引数まわりに手を付けて、ついでにジェネリクスまでできるようにちゃったぜみたいなことを考えているのではないでしょうか。

【PHP8.0】PHPでアトリビュート/アノテーション/デコレータが書けるようになる

Attributes v2というRFCが投票中です。
投票期間は2020/05/04まで、投票者の2/3の賛成で受理されます。
2020/04/27時点では賛成48反対1で、ほぼ間違いなく可決されます。

Attributes v2

Introduction

このRFCは、クラス/プロパティ/関数/メソッド/引数/定数の宣言に、構造化されたアトリビュートをメタデータとして記述できるようにする提案です。
アトリビュートは、コードの宣言に直接設定ディレクティブを埋め込むことで定義されます。

同じような概念としてJavaのAnnotation、C#/C++/Rust/HackにおけるAttribute、Python/JavascriptにおけるDecoratorが存在します。

これまで、PHPではこのようなメタデータとしては非構造的であるdoc-commentsしか存在しませんでした。
しかしdoc-commentsはただの文字列であり、言語によって解釈されることはありません。
構造化された情報を保持するために、PHPの様々なコミュニティにおいて@ベースの疑似メタデータが考案されてきました。

ユーザランドでの使用例に加え、拡張機能などではコンパイルや構文解析、コード生成、実行時の挙動に影響を与えるようなアトリビュートの使用例も多く存在します。
下のほうに例を示します。

ユーザランドのdoc-commentsが多く使われていることから、この機能がコミュニティから強く求められていることがわかります。

Proposal

Attribute Syntax

アトリビュートは、既存の構文トークンT_SLT_SR、すなわち<<>>を使った特別なフォーマットのテキストです。

アトリビュートは言語内の多くの対象に適用されます。
・関数 (クロージャやアロー関数含む)
・クラス (無名クラス含む)、インターフェイス、トレイト
・クラス定数
・プロパティ
・メソッド
・関数/メソッド引数

以下は例です。

<<ExampleAttribute>>classFoo{<<ExampleAttribute>>publicconstFOO='foo';<<ExampleAttribute>>public$x;<<ExampleAttribute>>publicfunctionfoo(<<ExampleAttribute>>$bar){}}$object=new<<ExampleAttribute>>class(){};<<ExampleAttribute>>functionf1(){}$f2=<<ExampleAttribute>>function(){};$f3=<<ExampleAttribute>>fn()=>1;

アトリビュートはdoc-blockコメントと同様に、それが属する宣言の直前に記載します。
doc-blockコメントとどちらが先かといえば、コメントの前にも後ろにも書くことができます。

<<ExampleAttribute>>/** docblock */<<AnotherExampleAttribute>>functionfoo(){}

関数、クラス、メソッド、プロパティ、パラメータはひとつ以上のアトリビュートを持つことができます。

<<WithoutArgument>><<SingleArgument(0)>><<FewArguments('Hello','World')>>functionfoo(){}

ひとつの対象に同じアトリビュート名を複数回指定することが可能です。

アトリビュートは1行で複数回宣言できます。

<<WithoutArgument>><<SingleArgument(0)>><<FewArguments('Hello','World')>>functionfoo(){}

<<>>は式の接頭辞として使用されており、他言語で使用されているジェネリクスの一般的な構文<T>がもし今後導入されることになったとしても、それがアトリビュート構文と衝突することはありません。

構文についてはこのRFCで最も議論されてきたポイントであり、@:として表される新たな構文トークンT_ATTRIBUTEを導入する代替案も考えられました。
構文の選択はRFCの二次投票となります。

@:WithoutArgument@:SingleArgument(0)@:FewArguments('Hello','World')functionfoo(){}

この構文は、doc-blockコメントで一般的に見られる@を使用します。
欠点としては、空白を許可するとアトリビュートの終端を判別できなくなってしまうので、空白を禁止しなければならないことです。

最も要望されるであろう@[]が使えない理由については、下にあるAlternative Syntaxの議論を参照してください。

Attribute Names Resolve to Classes

アトリビュートは、コンパイル中にその時点でインポートされている全てのシンボルについて解決されます。
これはアトリビュートに名前空間を許容するためであり、別のライブラリやアプリケーションで同じアトリビュートが誤って再利用されるのを避けるためです。

useMy\Attributes\SingleArgument;useMy\Attributes\Another;<<SingleArgument("Hello")>><<Another\SingleArgument("World")>><<\My\Attributes\FewArguments("foo","bar")>>functionfoo(){}

このことはクラスにアトリビュートを宣言する際にも利点があります。
・リフレクションが解析しやすい。後述。
・静的解析ツールによる検証が容易になる。
・IDEがオートコンプリートや引数に対応することができる。

クラスアトリビュートを指定する例は以下のようになります。

namespaceMy\Attributes;usePhpAttribute;<<PhpAttribute>>classSingleArgument{public$value;publicfunction__construct(string$value){$this->value=$value;}}

Compiler and Userland Attributes

このRFCは、2種類のアトリビュートを区別しています。
・コンパイラアトリビュート:コンパイル時に検証される
・ユーザランドアトリビュート:リフレクションで検証される

コンパイラアトリビュートは、PhpCompilerAttributeに属する内部クラスです。

ユーザランドアトリビュートは、PhpAttributeに属するユーザランドのクラスです。

コンパイル時にコンパイラアトリビュートが見つかると、実行エンジンはアトリビュートに紐付けられているバリデーションのコールバックを呼び出します。
たとえばこのパッチにはPhpCompilerAttributeのバリデーションコールバックが含まれており、ユーザがPhpCompilerAttributeを使うのを防いでいます。

#include "zend_attributes.h"
voidzend_attribute_validate_phpcompilerattribute(zval*attribute,inttarget){if(target!=ZEND_ATTRIBUTE_TARGET_CLASS){zend_error(E_COMPILE_ERROR,"The PhpCompilerAttribute can only be used on class declarations and only on internal classes");}else{zend_error(E_COMPILE_ERROR,"The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead");}}INIT_CLASS_ENTRY(ce,"PhpCompilerAttribute",NULL);zend_ce_php_compiler_attribute=zend_register_internal_class(&ce);zend_compiler_attribute_register(zend_ce_php_compiler_attribute,zend_attribute_validate_phpcompilerattribute);

引数zvalは渡された全ての引数を含み、targetはアトリビュートが正しく宣言されているかを検証する為の定数です。
ユーザランドクラスはPhpCompilerAttributeを使用することができません。
使おうとするとエラーが発生します。

<?php<<PhpCompilerAttribute>>classMyAttribute{}// Fatal error: The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead

アトリビュートをクラスツールにマッピングすることで、エディタやIDEは、アトリビュートの構文やコンテキスト情報を開発者に提供することができます。
この方法の欠点は、コンパイラアトリビュートがユーザランドアトリビュートであると誤って分類されてしまうことです。

Constant Expressions in Attribute Arguments

アトリビュートは定数AST式として評価されますが、これは引数を許可することを意味します。

<<SingleArgument(1+1)>><<FewArguments(PDO::class,PHP_VERSION_ID)>>

この主な使用例は、定数/クラス定数を参照することです。
定数を参照することで、既に定数として存在する情報を再定義する重複を避けることができます。
もうひとつの利点として、ツールやIDEによる静的解析でアトリビュートを検証できるということです。

定数ASTは、リフレクションを通してアクセスする際は値に解決されます。
これはかつて提出されたアトリビュートRFCとは意図的に異なる挙動です。

またパーサは、ビットシフト演算子とアトリビュート構文を区別できます。

<<BitShiftExample(4>>1,4<<1)>>functionfoo(){}

Reflection

ReflectionクラスにgetAttributes()メソッドが追加され、ReflectionAttributeインスタンスの配列を返します。

functionReflectionFunction::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionClass::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionProperty::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionClassConstant::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];

引数$nameがあれば指定したアトリビュート、もしくはそのサブクラスを含めたものを返します。

$attributes=$reflectionFunction->getAttributes(\My\Attributes\SingleArgument::class);

引数$flagが未指定の場合、getAttributes()メソッドは名前が完全一致したアトリビュートだけを返し、この動作がデフォルトです。
ReflectionAttribute::IS_INSTANCEOFを指定すると、instanceofを通過する全てのアトリビュートを返すようになります。

$attributes=$reflectionFunction->getAttributes(\My\Attributes\MyAbstractAttribute::class,\ReflectionAttribute::IS_INSTANCEOF);

ReflectionAttributeクラスは以下のようになります。

classReflectionAttribute{publicfunctiongetName():stringpublicfunctiongetArguments():arraypublicfunctionnewInstance():object}

アトリビュートの検証はReflectionAttribute::newInstance()でのみ行われるので、実は必ずしもアトリビュート名に対応したクラスを定義する必要はありません。
アトリビュート名と引数は直接ReflectionAttributeから取って来れます。

以下は完全な例です。

namespaceMy\Attributes{<<PhpAttribute>>classSingleArgument{public$argumentValue;publicfunction__construct($argumentValue){$this->argumentValue=$argumentValue;}}}namespace{<<SingleArgument("Hello World")>>classFoo{}$reflectionClass=new\ReflectionClass(Foo::class);$attributes=$reflectionClass->getAttributes();var_dump($attributes[0]->getName());var_dump($attributes[0]->getArguments());var_dump($attributes[0]->newInstance());}/**
string(28) "My\Attributes\SingleArgument"
array(1) {
  [0]=>
  string(11) "Hello World"
}
object(My\Attributes\SingleArgument)#1 (1) {
  ["argumentValue"]=>
  string(11) "Hello World"
}
**/

この使い方では、getAttributes()は決して例外をスローしません。
これにより、異なるライブラリが同じ名前のアトリビュートを定義していた際の問題を回避することができます。

Use Cases

Use Cases for PHP Extensions

アトリビュートの主な使用先は、PHPコアと拡張モジュールになるでしょう。

HashTablesへのアトリビュートは、全てのzend_class_entry/op_array/zend_property_info/zend_class_constantで使用可能です。

PHPコアや拡張モジュールは、ある定義にアトリビュートがあるかどうかチェックしたくなることがあるでしょう。
例としてOpcache JITに対する@jitのチェックなどです。
これは、関数やメソッドを常に最適化するようJITに指示します。

アトリビュートが実装されれば、拡張モジュールでは以下のように書けるようになります。

staticintzend_needs_manual_jit(constzend_op_array*op_array)returnop_array->attributes&&zend_hash_str_exists(op_array->attributes,"opcache\\jit",sizeof("opcache\\jit")-1));

開発者は、doc-commentのかわりにアトリビュートを使うことができます。

useOpcache\Jit;<<Jit>>functionfoo(){}

Other potential core and extensions use cases/ideas

以下はアトリビュートの使用法のアイデアです。
RFCの一部ではないことに注意してください。

関数/メソッドの非推奨。
アトリビュートを持つほぼ全ての言語にこの機能が組み込まれています。
PHPにこれがあれば、クラスやプロパティ、定数を非推奨にすることができます。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\Deprecated;<<Deprecated("Use bar() instead")>>functionfoo(){}

非推奨アトリビュートは、今のところtrigger_errorを使うことができません。

classFoo{<<Deprecated()>>constBAR='BAR';}echoFoo::BAR;// PHP Deprecated:  Constant Foo::BAR is deprecated in test.php on line 7

Reclassify Engine WarningsSupport Rewinding GeneratorsのRFCのようなレガシー動作を、オプトインで変更します。
Rustが似たような機能を持っています。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\Deny;usePhp\Attributes\Allow;<<Allow("rewind_generator")>>functionbar(){yield1;}<<Deny("undeclared_variables")>>functionfoo(){echo$foo;// PHP Fatal error:  Uncaught TypeError: Access to undeclared variable $foo}<<Deny("dynamic_properties")>>classFoo{}$foo->bar;// PHP Fatal error:  Uncaught Error: Invalid access to dynamic property Foo::$bar

Rustっぽいマクロの一部は、旧バージョンのPHPでのみPolyfillを読み込んだりするときに便利かもしれません。
ライブラリがOpcacheやpreloadingなどを条件付きで宣言するときに役立つでしょう。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\ConditionalDeclare;usePhp\Attributes\IgnoreRedeclaration;<<ConditionalDeclare(PHP_VERSION_ID<70000)>>// PHP7.0以上ならASTによって削除される<<IgnoreRedeclaration>>// 重複時はエラーを出さず単に無視するfunctionintdiv(int$numerator,int$divisor){}

最終的には、あるアトリビュートの引数を返すAPIや、全アトリビュートの一覧を返すAPIが含まれる予定です。
これによって拡張機能の作者は、最小限の労力でアトリビュートを使うことができるようになります。
以下は草案です。

/* アトリビュート名から引数一覧を返す */HashTable*zend_attribute_get(HashTable*attributes,char*name,size_tname_len);/* アトリビュートを返す */zval*zend_attribute_all(HashTable*attributes,char*name,size_tname_len);

Userland Use-Case: Declaring Event Listener Hooks on Objects

ユーザランドにおいて、アトリビュートは宣言に対する追加設定を宣言のすぐ傍に置くことができるという利点があります。
以下はSymfonyのEventSubscribersをアトリビュートを使ってリファクタリングする例です。
EventSubscriberInterfaceは、イベントをどのクラスのどのメソッドで処理するかをgetSubscribedEvents()で宣言する必要があります。

// 現在のコードclassRequestSubscriberimplementsEventSubscriberInterface{publicstaticfunctiongetSubscribedEvents():array{return[RequestEvent::class=>'onKernelRequest'];}publicfunctiononKernelRequest(RequestEvent$event){}}// リファクタした<<PhpAttribute>>classListener{public$event;publicfunction__construct(string$event){$this->event=$event;}}classRequestSubscriber{<<Listener(RequestEvent::class)>>publicfunctiononKernelRequest(RequestEvent$event){}}// アトリビュートを使ったイベントディスパッチャclassEventDispatcher{private$listeners=[];publicfunctionaddSubscriber(object$subscriber){$reflection=newReflectionObject($subscriber);foreach($reflection->getMethods()as$method){// Listenerアトリビュートを取得$attributes=$method->getAttributes(Listener::class);foreach($attributesas$listenerAttribute){/** @var $listener Listener */$listener=$listenerAttribute->newInstance();// $listener->eventはcallable$this->listeners[$listener->event][]=[$subscriber,$method->getName()];}}}publicfunctiondispatch($event,$args...){foreach($this->listeners[$event]as$listener){// 呼び出し$listener(...$args);}}}$dispatcher=newEventDispatcher();$dispatcher->addSubscriber(newRequestSubscriber());$dispatcher->dispatch(RequestEvent::class,$payload);

Userland Use-Case: Migrating Doctrine Annotations from Docblocks to Attributes

アトリビュートのRFCが考慮した主要なケースのひとつが、広く普及しているDoctrine Annotationsライブラリからの移行可能性です。

PHPコアがアトリビュートをサポートすることで、ユーザがDoctrine Annotationsからアトリビュートへ移行するための基盤を確保することができます。

このためこのRFCは、名前空間を使ったアトリビュートの操作が主な要件となっています。

Doctrineおよび任意のユーザランドライブラリは、親クラスの名前フィルタを利用して、興味のあるアトリビュートだけを抽出することができます。
提案のリフレクションAPIを使用し、独自のロジックを追加することで、より厳格なアトリビュートの使用を強制することができるようになります。

以下に、Doctrineのアノテーションと、このRFCのアトリビュートで同じことを実装した複雑なオブジェクトの例を示します。

<?phpuseDoctrine\ORM\AttributesasORM;useSymfony\Component\Validator\ConstraintsasAssert;<<ORM\Entity>>/** @ORM\Entity */classUser{/** @ORM\Id @ORM\Column(type="integer"*) @ORM\GeneratedValue */<<ORM\Id>><<ORM\Column("integer")>><<ORM\GeneratedValue>>private$id;/**
     * @ORM\Column(type="string", unique=true)
     * @Assert\Email(message="The email '{{ value }}' is not a valid email.")
     */<<ORM\Column("string",ORM\Column::UNIQUE)>><<Assert\Email(array("message"=>"The email '{{ value }}' is not a valid email."))>>private$email;/**
     * @ORM\Column(type="integer")
     * @Assert\Range(
     *      min = 120,
     *      max = 180,
     *      minMessage = "You must be at least {{ limit }}cm tall to enter",
     *      maxMessage = "You cannot be taller than {{ limit }}cm to enter"
     * )
     */<<Assert\Range(["min"=>120,"max"=>180,"minMessage"=>"You must be at least {{ limit }}cm tall to enter"])>><<ORM\Column(ORM\Column::T_INTEGER)>>protected$height;/**
     * @ORM\ManyToMany(targetEntity="Phonenumber")
     * @ORM\JoinTable(name="users_phonenumbers",
     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
     *      )
     */<<ORM\ManyToMany(Phonenumber::class)>><<ORM\JoinTable("users_phonenumbers")>><<ORM\JoinColumn("user_id","id")>><<ORM\InverseJoinColumn("phonenumber_id","id",JoinColumn::UNIQUE)>>private$phonenumbers;}

アトリビュートは名前付きパラメータをサポートしていないため、少し制限があります。
これがアトリビュートが関数呼び出しのような構文を利用している理由ですが、もし名前付きパラメータがサポートされれば、自動的にこのRFCもその恩恵を受けることになります。

ユーザランドのアトリビュートへの移行には、Rectorのようなツールが役立ちます。

Criticism and Alternative Approaches

Alternative Syntax: Why not use @ or [] like other languages?

どうして@[]ではないの?

構文として<<>>を使用する理由は、コードのこの場所でまだ使用できる数少ない構文のひとつであり、その中でも自然に見えるものだからです。
接頭辞演算子としてまだ使われていない他の記号を使うことも可能ですが、実質的に使えそうなものは'%'くらいです。
他に使える記号というのは|=/などです。

@[]は、エラー抑制演算子と配列短縮構文に競合するため使用することができません。
以下のような構文は、現在既に有効なPHPコードです。

[[@SingleArgument("Hello")]]

この構文が配列宣言かアトリビュートかを決定するためには、無制限に先のトークンを調べなければならなくなる可能性があります。

Why not extending Doc Comments?

Doc Commentsをそのまま拡張するのではだめなの?

アトリビュートはDoc Commentsより優れています。

・名前空間により、同じDoc Commentsを使用している複数のライブラリによる競合を防ぎます。
・アトリビュートの存在チェックはo(1)のハッシュキーチェックであり、strstrやDoc Commentsのパースより高性能です。
・アトリビュートをクラスにマッピングすることで、アトリビュートが正しい構文であることを保証し、Doc Commentsの書き間違いによるバグの発生を減らします。
・アノテーションは非常に多くのツールやコミュニティで一般に使われているので大きな需要があります。しかし初心者がコメントと見誤ると混乱を招くことになるでしょう。また/*/**の違いもバグの原因になります。

PHPで既存のDoc Commentsを解析して構造化することは可能かもしれませんが、Doc Commentsの方言ごとに追加のパーサを呼び出す必要があります。
Doc Commentsは正しい文法になっていない可能性があるため、文法エラーの取り扱いを決めなければなりません。
最終的に、これはPHP内部に別の言語が存在するということになりそうです。
この方法は、アトリビュートを導入するより遙かに複雑となり、望ましくありません。

Why not always map attributes to simple arrays instead for simplicity?

シンプルに、アトリビュートは常に配列にマッピングするようにすればよくない?

アトリビュートのクラス名解決が何故重要なのか、その利点を前のセクションで解説しました。
アトリビュートが正しいかどうかの検証は、文法が正しいかどうかを検証できないDoc Commentsよりも大きなメリットがあります。

Why not a stricter solution like Doctrine Annotations?

Doctrine Annotationsのようにより厳密なソリューションは導入しないの?

このRFCは、PHPの基本的なアトリビュート機能のみを提案しています。
一般的なソリューションを全て解決するためには様々なユースケースを考慮しなければなりませんが、大抵の場合はDoctrineほど細かなシステムは必要ありません。

Why are nested attributes not allowed?

アトリビュートのネストが許可されないのは何故?

アトリビュートのネストとは、あるアトリビュートを他のアトリビュートの引数として定義するということを意味します。
これは引数でアトリビュートを宣言できるということなので、意図的に禁止されています。

Naming (attributes or annotations)

この機能の"アトリビュート"という名前は、既に使われているアノテーションとの混同を避けるために付けられました。
これによってDoctrine AnnotationsはPHP7ではDoc Commentsで実装され、PHP8ではアトリビュートで実装されている、といったことがおこります。

Backward Incompatible Changes

なし。

Proposed PHP Version(s)

PHP8.0

RFC Impact

To Core

トークン、ASTノード、zend_class_entry、zend_class_constant、zend_op_array、zend_property_infoの全てにアトリビュートを追加する必要があります。

To SAPIs

なし。

To Existing Extensions

なし。

JITは@JITではなくOpcache\Jitを、@nojitのかわりにOpcache\Nojitを使うことになる予定ですが、まだ未定です。

To Opcache

パッチに含まれており、100%の互換ではない可能性があります。

New Constants

なし。

php.ini Defaults

なし。

Open Issues

なし。

Future Scope

・名前付きパラメータとの統合
・下位互換性を壊すことなく、既存関数を新しい動作で拡張させる。
・関数/メソッドが呼ばれたとき、プロパティ/定数にアクセスしたときに非推奨を通知する<<Deprecated>>
・型付きプロパティとアトリビュートで、JSON/XMLからオブジェクトへの直列化がPHPコアでできるようになる。
<<«SingleArgument("foo"), MultiArgument("bar", "baz")>>を簡単に書けるような短縮構文。

Voting

2020/04/27時点で、導入には賛成48反対1で、ほぼ導入決定です。

構文は<<>>が40人、@:が10人で、<<>>に決まると思われます。

Patches and Tests

https://github.com/beberlei/php-src/pull/2<<>>
https://github.com/kooldev/php-src/pull/2@:

References

他言語でのアトリビュート/アノテーション/デコレータ。

Rust Attributes
C# Attributes
Java Annotation
TypeScript/ECMAScript Decorators
C++ Attributes
Go Tags
Attributes in Hack

かつて却下されたり放棄されたRFC。

Attributes v1
Annotations v2
Reflection Annotations using the Doc-Comment
Simple Annotations
Annotations in DocBlock RFC
Class Metadata RFC

感想

ほぼ全員賛成というのがちょっと信じがたいんだけど。
個人的にはどちらかというと賛成ですが、正直もっと意見が割れてもよさそうな提案ですよね。

ということで、PHP8からはアトリビュートが使えるようになります。
例を見るに、AttributeInterfeceをimplementsしたりとかも不要で、普通にクラスを書いたらいきなりアトリビュート名として使えるんですかねこれ。
ちょっとユーザレベルでの使い方がいまいちよくわかりませんでした。

というか正直、全体的に意味のよくわからないところが多々ありました。
私は普段ソースまで追ってないので、いきなりAST構文木とかzend_class_entryとか言われても知らんがな!ってかんじですよ。
きっと誰かが補足してくれるはず。

あと、それでは具体的にデフォルトでどんなアトリビュートが用意されてるの、ってのもRFCには書かれていません。
複雑なアトリビュートを定義するにはどうすればいいの、というのもRFCではちょっとよくわかりません。
このあたりは今後ドキュメントの追加をおねがいしたいところですね。
まあPHPのマニュアルは親切すぎて困るくらい丁寧なので、そのうち充実してくるとは思いますが。

【PHP8.0】StartsWith/EndsWithがPHP本体に実装される

先日PHP8でstr_containsが導入されることが決まったばかりですが、さらにもっと直接的な『〇〇で始まる』『〇〇で終わる』関数までも導入されることになりました。

Add str_starts_with() and str_ends_with() functionsというRFCが投票中です。
2020/05/04時点では賛成50反対4で、ほぼ導入確定です。

PHP RFC: Add str_starts_with() and str_ends_with() functions

Introduction

str_starts_withは、文字列が指定の文字列で始まるか否かをチェックし、結果をbool値で返します。
str_ends_withは、文字列が指定の文字列で終わるか否かをチェックし、結果をbool値で返します。

これらの機能は既存の文字列関数、たとえばsubstrstrpos/strrpos、そしてstrncmpsubstr_compare、あまつさえstrlenなどを駆使して実装されてきました。
これらユーザランドの実装には、様々な問題点があります。

str_starts_withとstr_ends_withの需要は高く、SymfonyLaravelYiiFuelPHP、そしてPhalconと、あらゆるフレームワークによってサポートされています。

文字列の始めと終わりをチェックすることは非常に一般的なタスクであり、簡単に行えるべきです。
多くのフレームワークがこのタスクを実装しているということは、このタスクを実行することが簡単ではないことを意味しています。
JavaScript/Java/Haskell/Matlabといった多くの高水準言語が標準でこの機能を実装している理由でもあります。
文字列の開始と終了をチェックすることは、これだけのためにフレームワークを導入したり、ユーザランドで最適ではない(どころかバグが入るかもしれない)実装を行ったりする必要のあるべき作業ではありません。

Downsides of Common Userland Approaches

この機能のアドホックな実装は、専用関数に比べると直感的ではありません。
PHPのニュービーや、他言語と同時開発する開発者にとっては特にそうです。
また、特に===を含む場合、実装を簡単に間違えます。
さらに多くのユーザランド実装はパフォーマンス上の問題があります。

注意:以下の実装には、E_WARNINGを防ぐために$needle === "" ||strlen($needle) <= strlen($haystack) &&のようなガードを入れましょう。

str_starts_with

substr($haystack,0,strlen($needle))===$needle

$haystackの無駄なコピーが発生するため、メモリ効率が良くありません。

strpos($haystack,$needle)===0

$needleが見つからなかった場合に$haystackを最後まで調べてしまうため、CPU効率が悪くなります。

strncmp($haystack,$needle,strlen($needle))===0// genericstrncmp($subject,"prefix",6)===0// ad hoc

これは効率的ですが、$needleの文字数を別に渡す必要があり冗長です。

str_ends_with

substr($haystack,-strlen($needle))===$needle

str_starts_with同様、メモリ効率がよくありません。

strpos(strrev($haystack),strrev($needle))===0

str_starts_with同様CPU効率が悪いだけでなく、文字列反転処理まで入るので、さらに非効率です。

strrpos($haystack,$needle)===strlen($haystack)-strlen($needle)

冗長であり、CPURL効率が悪くなることがあります。

substr_compare($haystack,$needle,-strlen($needle))===0// genericsubstr_compare($subject,"suffix",-6)===0// ad hoc

効率的ですが、冗長です。

Proposal

2つの関数、str_starts_with()str_ends_with()を導入します。

str_starts_with(string$haystack,string$needle):boolstr_ends_with(string$haystack,string$needle):bool

str_starts_with()は、$haystack$needleで始まるかどうかを調べます。
strlen($needle) > strlen($haystack)であれば即座にfalseを返し、そうでなければ両文字列を比較し、先頭一致すればtrueを、一致しなければfalseを返します。

str_ends_with()も同じですが、後方一致です。

以下に例を示します。

$str="beginningMiddleEnd";if(str_starts_with($str,"beg"))echo"printed\n";// trueif(str_starts_with($str,"Beg"))echo"not printed\n";// falseif(str_ends_with($str,"End"))echo"printed\n";// trueif(str_ends_with($str,"end"))echo"not printed\n";// false// 空文字if(str_starts_with("a",""))echo"printed\n";// trueif(str_starts_with("",""))echo"printed\n";// trueif(str_starts_with("","a"))echo"not printed\n";// falseif(str_ends_with("a",""))echo"printed\n";// trueif(str_ends_with("",""))echo"printed\n";// trueif(str_ends_with("","a"))echo"not printed\n";// false

空文字に関しては、受理済のstr_containsのRFCの挙動に従います。
これはJavaやPythonなどと共通の動作です。

Backward Incompatible Changes

ユーザランドに同名の関数がある場合は競合します。

Proposed PHP Version(s)

PHP8

RFC Impact

・SAPI:全てのPHP環境に関数が追加されます
・エクステンション:無し
:Opcache:無し
・New Constants:無し
・php.ini Defaults:無し

Votes

投票は2020/05/04まで。
投票者の2/3+1の賛成で受理されます。

Patches and Tests

https://github.com/php/php-src/pull/5300

References

他言語の類似機能
・JavaScript: String#startsWith() / String#endsWith()
・Python: str#startswith() / str#endswith()
・Java: String#startsWith() / String#endsWith()
・Ruby: String#start_with?() / String#end_with?()
・Go: strings.HasPrefix() / strings.HasSuffix()
・Haskell: Data.String.Utils.startswith / Data.String.Utils.endswith
・MATLAB: startsWith()) / endsWith()

bugs.php.net
bug #50434 / bug #60630 / bug #67035 / bug #74449

過去のRFC
PHP RFC: rfc:add_str_begin_and_end_functions

Rejected Features

大文字小文字を区別しない版とマルチバイト版は、以前のRFCには含まれていましたが、このRFCでは廃止されました。
理由はstr_containsを参照してください。

感想

PHPの文字列関数ってやたら大量に用意されてるわりに意外と基本的なところが抜けていたのですが、PHP8でstr_continsとこの関数が追加されたことによって、テキスト処理に必要なものは出揃うことになったのではないでしょうか。

他に必要なのって何かありますかね。デフォルト関数の命名規則とか?

【PHP8.0】なんでもあり型が書けるようになる

ジェネリクスではない…ジェネリクスではないのだよ………

ざっくり言うとvar_dump()の型引数です。
var_dumpにはプリミティブ値にオブジェクトにリソース型にと、どんな値でも渡すことができるのですが、PHP7.4時点の型システムではvar_dumpの引数の型を表すことができません。
PHP8.0で導入予定のunion型を使うとarray|bool|callable|int|float|null|object|resource|stringとなるのですが、実はresource型はPHP8.0でもまだ使えないので、mixed型を完全に再現することはできません。

ということでMixed Type v2のRFCが提出されました。
投票は2020/05/21まで、受理には2/3+1の賛成が必要です。
が2020/05/11時点では賛成35反対6で、おそらく受理されます。

Introduction

PHP7のスカラー型、7.1のnull許容型、7.2のobject型、そして最新8.0のUNION型とPHPの型システムは進化し続けており、PHPの開発者はほとんどの関数において引数と返り値、そしてプロパティについて明示的に型情報を宣言することができるようになりました。

しかし、PHPは常に型をサポートしてきたわけではありません。
そしてこれは、型情報が欠落している際にその意味が曖昧になってしまうという問題に繋がります。

・特定の型に決まっているが、プログラマが宣言を忘れてしまった
・特定の型に決まっているが、古いバージョンのPHPと互換を保つためにあえて省略している
・現在のPHPの型システムでは表現できない型である

明示的なmixed型を用意することで、引数や返り値、プロパティに型を追加して、型情報を忘れていたわけではなく、正確に指定できなかったりあえて広げているのだという主張をを示すことができます。

現在のところmixed型はPHPDocの中でのみ使用することができますが、これは適切ではありません。
PHPDocでmixed型が使用されている顕著な例としては、PHP標準ライブラリ関数の返り値などがあります。
ネイティブにmixed型があれば、これらをより正確に表現することができるでしょう。

またmixed型はPHPマニュアルにおいても広く使用されています。

var_dump(mixed$expression[,mixed$...]):void

Proposal

PHPの型システムにmixed型を追加します。
これはarray|bool|callable|int|float|null|object|resource|stringと等価です。
これはPHPの継承時の型検査の実装に適合する正しい動作です。

LSP, Covariance and Contravariance

このProposalは、リスコフの置換原則に準拠しています。

PHP7.4以降、PHPは共変戻り値と反変パラメータに対応しています。

PHPではLSP原則に従うように、パラメータの拡大を許容しています。
サブクラスにおいて、親クラスより広い、特殊でない型を使用することができます。

PHPではLSP原則に従うように、返り値の縮小を許容しています。
サブクラスにおいて、親クラスより狭い、特殊な型を使用することができます。

Parameter types are contravariant

引数は、特定の型からmixed型に拡大することができます。

// 正しい例classA{publicfunctionfoo(int$value){}}classBextendsA{// intからmixedに拡大は許可publicfunctionfoo(mixed$value){}}

引数の縮小は、LSP原則に違反するため許可されません。

// 不正な例classA{publicfunctionfoo(mixed$value){}}classBextendsA{// mixedからintに縮小は不可// Fatal errorが出るpublicfunctionfoo(int$value){}}

Return types are covariant

返り値は、mixed型から特定の型に縮小することができます。

// 正しい例classA{publicfunctionbar():mixed{}}classBextendsA{// mixedからintに縮小は許可publicfunctionbar():int{}}

返り値の拡大は、LSP原則に違反するため許可されません。

// 不正な例classC{publicfunctionbar():int{}}classDextendsC{// intからmixedに拡大は不可// Fatal errorが出るpublicfunctionbar():mixed{}}

Property types are invariant

プロパティ型指定のRFCに従い、プロパティの型は不変です。

// 不正な例classA{publicmixed$foo;publicint$bar;public$baz;}classBextendsA{// プロパティ型は縮小不可// Fatal errorが出るpublicint$foo;}classCextendsA{// プロパティ型は拡大不可// Fatal errorが出るpublicmixed$bar;}classDextendsA{// 未指定にmixed型を追加するのも駄目// Fatal errorが出るpublicmixed$baz;}classEextendsA{// 型指定の削除も駄目// Fatal errorが出るpublic$foo;}

Void return type

void型の返り値については、LSPに適合していたとしても拡張は許可されません。

classA{publicfunctionbar():void{}}classBextendsA{// Fatal error: Declaration of B::bar(): int must be compatible with A::bar(): voidpublicfunctionbar():int{}}

このRFCは、既存の振る舞いに従います。
すなわち、void型をmixed型に広げることはできません。

Signature checking of function when no parameter type present

引数に型が存在しない場合の型チェックは、mixed型が指定されたかのように動作します。

classA{// 引数の型がないのでmixedとみなすpublicfunctionfoo($value){}}classBextendsA{// mixed型を追加したが、親クラスと動きは同じpublicfunctionfoo(mixed$value){}}classCextendsB{// mixed型を削除したが、親クラスと動きは同じpublicfunctionfoo($value){}}classDextendsB{publicfunctionfoo(mixed$value=null){}}

現在のところ、これはクラスの継承に限った動作です。

PHPで型を定義できるようになれば、他のところでも動くようになるかもしれません。

Signature checking of function when no return type present

返り値に型が指定されていない場合の型チェックは、mixed|void型が指定されたかのように動作します。

サブクラスでオーバーロードする際は、返り値を未指定にするか、void型にするか、mixed型およびそのサブタイプの何れかを指定しなければなりません。
そして、一度変更したあとの型を未指定に戻すことはできません。

classA{// 返り値に型がないのでmixed|voidとみなすpublicfunctionfoo(){}}classBextendsA{// mixedを指定した。voidは禁止になるpublicfunctionfoo():mixed{}}classCextendsB{// mixed|voidはmixedより広いのでNG// Fatal errorが出るpublicfunctionfoo(){}}classDextendsB{// voidはmixedのサブタイプではないのでNG// Fatal errorが出るpublicfunctionfoo():void{}}

The mixed|void union type

このRFCは、mixed|voidのUNION型は必要ないので許可しないという立場です。
今後ユースケースが見つかれば許可される可能性はあります。

Nullability

mixed型にはnullが含まれます。
従ってmixed型のnull許容型は情報の重複となります。

このRFCは、mixed型のnull許容型を許容しないという立場です。
今後ユースケースが見つかれば許可される可能性はありますが、その際はどの冗長な型指定を許可して、どの冗長な型指定は許可しない、という議論が必要になるでしょう。

// NG、既にnull許容なのでfunctionfoo(?mixed$arg){}// NG、既にnull許容なのでfunctionbar():?mixed{}

Explicit returns

返り値にmixed型を使用する場合、明示的にreturnを記述する必要があります。
さもなければTypeErrorが発生します。

functionfoo():mixed{}foo();// Uncaught TypeError: Return value of foo() must be of the type mixed, none returned

既存のnull許容型と同じ動作です。

functionbar():?int{}bar();// Uncaught TypeError: Return value of bar() must be of the type int or null, none returned

Resource 'type'

PHPではresource型の値を変数に割り当てることができますが、ユーザランドでは引数、返り値、プロパティの型としてresource型を使用することができません。
このRFCの立場としては、resource型はresource型チェックをパスすべきである、というものです。

Mixed vs any

PHPではマニュアルやPHPStanなどの静的解析ツールで広くmixed型が使われているため、mixedになりました。
またmixedはPHP7以降弱い予約語とされていますが、anyは予約語に含まれません。

RFC Impact

Proposed PHP Version(s)

PHP8.0

Backward Incompatible Changes

クラス名としてのmixedが禁止されますが、PHP7.0以降mixedは弱い予約語です。

To SAPIs

特になし。

To Existing Extensions

特になし。

To Opcache

特になし。

Vote

2020/05/07に投票開始、2020/05/21に投票終了。
受理には2/3+1の賛成が必要です。

Patches and Tests

GitHub Pull request #5313

References

PHP RFC: Reserve Even More Types in PHP 7
phpDocumentor type reference

感想

これ、UNION型のRFCの将来の展望にある型宣言そのものですよね。
ついでだから型宣言自体もできるようにしてしまえばいいのでは。

ということでPHP8からmixed型が使えるようになります。

var_dumpのような、仕様としてあらゆる値を受け取る必要のある関数のためにこれが必要なことは確かでしょう。

しかし、ユーザランドで何も考えず適当にこれを使って大惨事、という未来が目に見えますね。
まあでも、そんな人はそもそも型引数を書かないだろうから問題ないかな?

PHP Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated

PHP の開発バージョン(master/8.0.0-dev)で、mbstringを有効にすると mbstring.internal_encoding is deprecatedと表示される。

$docker run --rm-it keinos/php8-jit /bin/sh
/ $php -vPHP Deprecated:  PHP Startup: Use of mbstring.internal_encoding is deprecated in Unknown on line 0

Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated in Unknown on line 0
PHP 8.0.0-dev (cli) (built: May 21 2020 15:58:54) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0-dev, Copyright (c), by Zend Technologies

TL; DR

mbstring.internal_encodingは PHP 8.0.0 から削除されます。予定

mbstring.internal_encodingを設定している php.iniファイルを探し、mbstring.internal_encodingの項目を削除(or ;でコメントアウト)する。

/usr/local/etc/php/conf.d/docker-php-enable-mb.ini
; Extended PHP.ini file to enable mbstring.
; =========================================
; Place this file under /usr/local/etc/php/conf.d/

zend.multibyte = On
zend.script_encoding = UTF-8
mbstring.language = Japanese
- mbstring.internal_encoding = UTF-8
+ ;mbstring.internal_encoding = UTF-8

TS; DR

もともと php.inimbstring.internal_encoding設定は PHP 5.6.0 以降から非推奨でした。

masterブランチ(PHP 8.0.0-dev)のコミット 3ca08ee(2020/03/31)で削除され Deprecatedとなり、このまま差し戻されなければ PHP 8.0.0 の正式リリースで削除されます。

おそらく、mbstring.internal_encoding以外の非推奨設定も PHP 8.0.0 を機に削除されるかもしれません。

2020/05/21 現在、非推奨とされる mbstringの設定項目。

名前変更履歴変更予定
mbstring.http_inputPHP 5.6.0 で非推奨
mbstring.http_outputPHP 5.6.0 で非推奨
mbstring.internal_encodingPHP 5.6.0 で非推奨PHP 8.0.0 で削除予定(3ca08eeで削除)
mbstring.func_overloadPHP 7.2.0 で非推奨

【PHP8.0】例外をcatchしたいけど何もしたくない

例外をcatchしたいけど何もしたくない。

try{foo();}catch(Throwable$e){// 何もしない}

何もしないのにわざわざ変数に受け取るのって無駄じゃありませんか?

というわけでnon-capturing catchesというRFCが提出されました。

PHP RFC: non-capturing catches

Introduction

今のところ、PHPは例外を明示的に変数でキャプチャする必要があります。

try{foo();}catch(SomeException$ex){die($ex->getMessage());}

しかしながら、ときにはその変数を使わない場合もあります。

try{changeImportantData();}catch(PermissionException$ex){echo"You don't have permission to do this";}

これを見た人は、プログラマが意図してこの変数を使わなかったのか、あるいはバグなのかがわかりません。

Proposal

例外を変数に受け取らずcatchできるようにします。

try{changeImportantData();}catch(PermissionException){// catchした変数を使わないという意図が明白echo"You don't have permission to do this";}

Prior art

7年前にあった似たようなRFCは、以下のように例外名も省略可能にするという理由で否定意見が多数でした。

try{foo();}catch{bar();}

いっぽう、このRFCは肯定的な反応が多く、再検討の余地があります。

Backward Incompatible Changes

後方互換性を壊す変更はありません。

Proposed PHP Version(s)

PHP8.0

RFC Impact

特になし。

Vote

投票は2020/05/24まで、投票者の2/3の賛成で受理されます。
このRFCは賛成48反対1の圧倒的多数で受理されました。

Patches and Tests

https://github.com/php/php-src/pull/5345

References

メーリングリスト

感想

このRFCが意図しているところは決して例外の握り潰しなどではなく、例外処理のロジックに例外の中身を使わないときに省略できるというものです。
変数に受け取る処理がなくなるので速度も速まることでしょう。

しかしですな、こんな機能があったら絶対にこんなコードを書く奴が出てくるわけですよ。

PHP8
try{foo();}catch(Exception){}

いやまあ、こんなの書いてしまう人は今でもやってると思いますけどね。

PHP7
try{foo();}catch(Exception$e){}

なら別にあってもいいか。

劇的に便利になるというわけではないですが、ちょっと気が利く書き方ができるようになりますね。

最後にもう一度言いますが、冒頭のコードは悪い例なので決して真似してはいけません。

【PHP8.0】PHPにオブジェクト初期化子が導入される

これまで何度も塩漬けにされたり却下されたりしていたオブジェクト初期化子ですが、ついにPHP8.0で導入されることになりました。
オブジェクト初期化子が何かというとこれです。

classHOGE{publicfunction__construct(privateint$x){// $HOGE->xが生える}}

これはオブジェクト初期化子でいいのか?

日本語で何と表すのか適切な単語が思いつかなかったのでとりあえずオブジェクト初期化子としておきます。
愚直に訳すと"コンストラクタ引数昇格"ですが、そんな単語は無いうえに型昇格と紛らわしいです。
引数プロパティ宣言パラメータプロパティ宣言もほぼ使われてないし何と表現すればいいのだろう。
きっと誰かが適切な語をプルリクしてくれるはず。

以下は該当のRFC、PHP RFC: Constructor Property Promotionの日本語訳です。

PHP RFC: Constructor Property Promotion

Introduction

PHPでは現在のところ、オブジェクトにプロパティを定義するだけでも同じことを複数回書かなければならないため、多くの無駄が必要です。
以下の単純なクラスを考えてみましょう。

classPoint{publicfloat$x;publicfloat$y;publicfloat$z;publicfunction__construct(float$x=0.0,float$y=0.0,float$z=0.0,){$this->x=$x;$this->y=$y;$this->z=$z;}}

プロパティの表記は、1:プロパティの宣言、2:コンストラクタの引数、3:プロパティの代入で3回も繰り返されます。
さらにプロパティの型も2箇所に書かなければなりません。

プロパティ宣言とコンストラクタ以外には何も含まれていないバリューオブジェクトでは特に、多くの重複によって変更が複雑になり、エラーを起こしやすいものとなります。

このRFCでは、プロパティの定義とコンストラクタを組み合わせるショートハンド構文の導入を提案します。

PHP8
classPoint{publicfunction__construct(publicfloat$x=0.0,publicfloat$y=0.0,publicfloat$z=0.0,){}}

このショートハンド構文は、前述の例と厳密に同じで、より短く書くことができます。
構文は姉妹言語Hackから採用しています。

Proposal

コンストラクタの引数にpublic/protected/private何れかが記述されている場合、その引数は"promoteされた引数"とします。
promoteされた引数には、同じ名前のプロパティが追加され、値が割り当てられます。

Constraints

promoteはabstractではないクラスのコンストラクタでのみ記述可能です。
従って、以下のような構文は使用不能です。

// エラー:コンストラクタではないfunctiontest(private$x){}abstractclassTest{// エラー:abstractなので駄目abstractpublicfunction__construct(private$x);}interfaceTest{// エラー:interfaceも駄目publicfunction__construct(private$x);}

一般的でない使い方ですが、トレイトでは使用可能です。

対応する可視性キーワードはpublic/protected/privateのみです。

classTest{// エラー:varはサポートしてないpublicfunction__construct(var$prop){}}

promoteされた引数によるプロパティは、通常のプロパティと全く同じ扱いになります。
特に注意点として、同じプロパティを二度宣言することはできません。

classTest{public$prop;// Error: Redeclaration of property.publicfunction__construct(public$prop){}}

また、プロパティにすることのできないcallable型は使用することができません。

classTest{// Error: Callable type not supported for properties.publicfunction__construct(publiccallable$callback){}}

promoteされたプロパティはプロパティ宣言と同義であるため、デフォルトがNULLの場合はNULL許容型を明示しなければなりません。

classTest{// Error: Using null default on non-nullable propertypublicfunction__construct(publicType$prop=null){}// こっちはOKpublicfunction__construct(public?Type$prop=null){}}

可変長引数をpromoteすることはできません。

classTest{// エラーpublicfunction__construct(publicstring...$strings){}}

理由としては、明示する引数の型(ここではstring)と、実際に渡される引数の型(ここではstringの配列)が異なるからです。
$stringsプロパティをstringの配列にすることも可能ですが、それではわかりづらくなります。

promoteプロパティと明示的なプロパティ宣言を組み合わせることは可能です。
またpromoteプロパティとpromoteされない引数を同時に渡すことも可能です。

// 正しいclassTest{publicstring$explicitProp;publicfunction__construct(publicint$promotedProp,int$normalArg){$this->explicitProp=(string)$normalArg;}}

Desugaring

promoteプロパティはただのシンタックスシュガーであり、全てのpromoteプロパティに対して以下の変換が適用されます。

// シンタックスシュガーclassTest{publicfunction__construct(publicType$prop=DEFAULT){}}// こう展開されるclassTest{publicType$prop;publicfunction__construct(Type$prop=DEFAULT){$this->prop=$prop;}}

自動的に宣言されるプロパティの可視性と型は、promoteプロパティの可視性および型と同じになります。
注目すべき点は、プロパティにデフォルト値は適用されず(つまり、未初期化で始まります)、コンストラクタ引数でのみ指定されるところです。

プロパティ宣言時にもデフォルト値を指定したほうがよいようにも思えますが、将来的にデフォルト値で指定することが望ましくなるであろう理由が存在します。

ひとつめは、プロパティのデフォルト値に任意の式を利用できるようにする拡張の可能性です。

// FROMclassTest{publicfunction__construct(publicDependency$prop=newDependency()){}}// TOclassTest{publicDependency$prop/* = new Dependency() */;publicfunction__construct(Dependency$prop=newDependency()){$this->prop=$prop;}}

こうなると、プロパティ宣言時とデフォルト値でオブジェクトを2回構築することとなるため望ましくありません。

また、新潟アクセス修正子のルールではプロパティでデフォルト値を宣言すると、コンストラクタで代入することもできなくなります。

promote引数が参照であった場合、プロパティも参照になります。

// FROMclassTest{publicfunction__construct(publicarray&$array){}}// TOclassTest{publicarray$array;publicfunction__construct(array&$array){$this->array=&$array;}}

promoteプロパティへの引数の割り当ては、コンストラクタの冒頭で行われます。
従って、コンストラクタ内でも引数とプロパティの両方にアクセスすることが可能です。

// 動作するclassPositivePoint{publicfunction__construct(publicfloat$x,publicfloat$y){assert($x>=0.0);assert($y>=0.0);}}// こっちも動作するclassPositivePoint{publicfunction__construct(publicfloat$x,publicfloat$y){assert($this->x>=0.0);assert($this->y>=0.0);}}

Reflection

リフレクションおよびその他の解析機構で見ると、シンタックスシュガーを解除した後の状態になります。
すなわち、promoteプロパティは明示的に宣言されたプロパティのように見え、promote引数は通常のコンストラクタ引数のように見える、ということです。

PHPは引数に関するDocコメントを公開していませんが、promoteプロパティのDocコメントも保持されます。

classTest{publicfunction__construct(/** @SomeAnnotation() */public$annotatedProperty){}}$rp=newReflectionProperty(Test::class,'annotatedProperty');echo$rp->getDocComment();// "/** @SomeAnnotation */"

この例のように、promoteプロパティではDocコメントベースのアノテーションを使用することができます。

また、2メソッドが追加されます。

ReflectionProperty::isPromoted()は、promoteプロパティであればtrueを返します。
ReflectionParameter::isPromoted()は、promote引数であればtrueを返します。

プロパティがpromoteされたかどうかを気にする場面はほとんど存在しないと思われますが、この情報によって元のコードをより簡単に再構築することができます。

Inheritance

オブジェクト初期化子は継承することができますが、特に特筆すべきようなことはありません。
abstrautを含む典型的な継承のユースケースを以下に示します。

abstractclassNode{publicfunction__construct(protectedLocation$startLoc=null,protectedLocation$endLoc=null,){}}classParamNodeextendsNode{publicfunction__construct(publicstring$name,publicExprNode$default=null,publicTypeNode$type=null,publicbool$byRef=false,publicbool$variadic=false,Location$startLoc=null,Location$endLoc=null,){parent::__construct($startLoc,$endLoc);}}

ParamNodeクラスでいくつかのpromoteプロパティを宣言し、さらに二つの普通の引数を親コンストラクタに転送しています。
これは以下のように展開されます。

abstractclassNode{protectedLocation$startLoc;protectedLocation$endLoc;publicfunction__construct(Location$startLoc=null,Location$endLoc=null,){$this->startLoc=$startLoc;$this->endLoc=$endLoc;}}classParamNodeextendsNode{publicstring$name;publicExprNode$default;publicTypeNode$type;publicbool$byRef;publicbool$variadic;publicfunction__construct(string$name,ExprNode$default=null,TypeNode$type=null,bool$byRef=false,bool$variadic=false,Location$startLoc=null,Location$endLoc=null,){$this->name=$name;$this->default=$default;$this->type=$type;$this->byRef=$byRef;$this->variadic=$variadic;parent::__construct($startLoc,$endLoc);}}

プロパティへの代入は、親コンストラクタが呼ばれる前に行われることに注意してください。
これはコーディングスタイルとして一般的ではありませんが、動作に影響が出るようなことはほぼありません。

Attributes

PHP8ではアトリビュートも導入されるため、相互作用を考慮する必要があります。
アトリビュートは、プロパティと引数の両方で使用することができます。

classTest{publicfunction__construct(<<ExampleAttribute>>publicint$prop,){}}

このコードがどのように解釈されるか決める必要があります。
1. アトリビュートは引数にのみ適用する。
2. アトリビュートはプロパティにのみ適用する。
3. アトリビュートは引数とプロパティの両方に適用する。
4. 曖昧さを避けるためエラーにする

// Option 1: アトリビュートは引数にのみ適用するclassTest{publicint$prop;publicfunction__construct(<<ExampleAttribute>>int$prop,){}}// Option 2: アトリビュートはプロパティにのみ適用するclassTest{<<ExampleAttribute>>publicint$prop;publicfunction__construct(int$prop,){}}// Option 3: アトリビュートは引数とプロパティの両方に適用するclassTest{<<ExampleAttribute>>publicint$prop;publicfunction__construct(<<ExampleAttribute>>int$prop,){}}// Option 4: 曖昧さを避けるためエラーにする

このRFCでは3番目、つまり引数とプロパティの両方に適用することを提案しています。
これが最も柔軟性の高い方法だからです。

ただし、これは実装に依ると考えています。
PHP8の実装に関わる作業で、アトリビュートをプロパティにのみ配置した方がよいと判明した場合は、そのように変更される場合があります。

Coding Style Consideration

このセクションではコーディングスタイルの推奨について解説します。
規程ではありません。

promoteプロパティを使用する場合、コンストラクタをクラス最初のメソッドとして、明示的なプロパティ宣言の直後に配置することをお勧めします。
これにより、全ての全てのプロパティが先頭にまとめられ、一目でわかるようになります。
静的メソッドを最初に配置することを要求しているコーディング規約は、コンストラクタを最初に配置するよう規約を調整する必要があります。

promoteプロパティに@paramアノテーションを使用している場合、ドキュメントツールは@varアノテーションも含まれているものとして解釈されるべきです。

// 元のコードclassPoint{/**
     * Create a 3D point.
     *
     * @param float $x The X coordinate.
     * @param float $y The Y coordinate.
     * @param float $z The Z coordinate.
     */publicfunction__construct(publicfloat$x=0.0,publicfloat$y=0.0,publicfloat$z=0.0,){}}// こう解釈するclassPoint{/**
     * @var float $x The X coordinate.
     */publicfloat$x;/**
     * @var float $y The Y coordinate.
     */publicfloat$y;/**
     * @var float $z The Z coordinate.
     */publicfloat$z;/**
     * Create a 3D point.
     *
     * @param float $x The X coordinate.
     * @param float $y The Y coordinate.
     * @param float $z The Z coordinate.
     */publicfunction__construct(float$x=0.0,float$y=0.0,float$z=0.0,){$this->x=$x;$this->y=$y;$this->z=$z;}}

最後に、promoteプロパティは、あくまで一般的なケースをカバーするための便利な省略記法であるに過ぎないことに注意してください。
promoteプロパティはいつでも明示的なプロパティに書き換えることができます。
そのため、この変更は下位互換性を壊すことはありません。

Backward Incompatible Changes

下位互換性のない変更はありません。

Future Scope

Larryが、この機能と他の機能を組み合わせることによってオブジェクト初期化を改善する方法について、より深いビジョンを提供しています

Prior Art

この機能、あるいは類似した機能は多くの言語でサポートされています。
Hack
TypeScript
Kotlin

先行するRFCが存在します。
Automatic property initialization プロパティ宣言は必要とする、より弱い形です。
Constructor Argument Promotion このRFCとほとんど同じです。
Code free constructor Kotlinの文法に基づいています。

Vote

投票期間は2020/05/29まで、2/3+1の賛成が必要です。
このRFCは賛成46反対10で受理されました。

感想

プロパティを書くのが格段に楽になりますね。
後から見るときにプロパティが宣言されているのかどうかちょっとわかりにくそうですが、この機能が使えるのはコンストラクタだけなのでそこだけ抑えていれば大丈夫でしょう。
コンストラクタだけではなく任意のメソッドで使えると便利では、と一瞬思ったものの、これを無制限に使えると完全に収拾が付かなくなってしまうので、やはりコンストラクタだけに留めておくのが賢明そうですね。

メモ: マクロ PHP_FUNCTION を展開する

結論もどき

PHP_FUNCTION(array_reverse)

void zif_array_reverse(zend_execute_data *execute_data, zval *return_value)

読むソースコード

php/php-src: The PHP Interpreter

手元のソースコードのコミットは こちら

PHP_FUNCTIONが使われているところの例

ext/standard/array.c
/* {{{ proto array array_reverse(array input [, bool preserve keys])
   Return input as a new array with the order of the entries reversed */PHP_FUNCTION(array_reverse){zval*input,/* Input array */*entry;/* An entry in the input array */zend_string*string_key;zend_ulongnum_key;zend_boolpreserve_keys=0;/* whether to preserve keys */ZEND_PARSE_PARAMETERS_START(1,2)Z_PARAM_ARRAY(input)Z_PARAM_OPTIONALZ_PARAM_BOOL(preserve_keys)ZEND_PARSE_PARAMETERS_END();/* Initialize return array */array_init_size(return_value,zend_hash_num_elements(Z_ARRVAL_P(input)));if((HT_FLAGS(Z_ARRVAL_P(input))&HASH_FLAG_PACKED)&&!preserve_keys){zend_hash_real_init_packed(Z_ARRVAL_P(return_value));ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)){ZEND_HASH_REVERSE_FOREACH_VAL(Z_ARRVAL_P(input),entry){if(UNEXPECTED(Z_ISREF_P(entry)&&Z_REFCOUNT_P(entry)==1)){entry=Z_REFVAL_P(entry);}Z_TRY_ADDREF_P(entry);ZEND_HASH_FILL_ADD(entry);}ZEND_HASH_FOREACH_END();}ZEND_HASH_FILL_END();}else{ZEND_HASH_REVERSE_FOREACH_KEY_VAL(Z_ARRVAL_P(input),num_key,string_key,entry){if(string_key){entry=zend_hash_add_new(Z_ARRVAL_P(return_value),string_key,entry);}else{if(preserve_keys){entry=zend_hash_index_add_new(Z_ARRVAL_P(return_value),num_key,entry);}else{entry=zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value),entry);}}zval_add_ref(entry);}ZEND_HASH_FOREACH_END();}}/* }}} */

PHP_FUNCTIONの定義はこちら

main/php.h
#define PHP_FUNCTION            ZEND_FUNCTION

PHP_FUNCTION(array_reverse)

ZEND_FUNCTION(array_reverse)

ZEND_FUNCTIONの定義はこちら

Zend/zend_API.h
#define ZEND_FUNCTION(name)             ZEND_NAMED_FUNCTION(zif_##name)

ZEND_FUNCTION(array_reverse)

ZEND_NAMED_FUNCTION(zif_array_reverse)

ZEND_NAMED_FUNCTIONの定義はこちら

Zend/zend_API.h
#define ZEND_NAMED_FUNCTION(name)       void ZEND_FASTCALL name(INTERNAL_FUNCTION_PARAMETERS)

ZEND_NAMED_FUNCTION(zif_array_reverse)

void ZEND_FASTCALL zif_array_reverse(INTERNAL_FUNCTION_PARAMETERS)

ZEND_FASTCALLの定義はこちら

Zend/zend_portability.h
#if defined(__GNUC__) && ZEND_GCC_VERSION >= 3004 && defined(__i386__)
# define ZEND_FASTCALL __attribute__((fastcall))
#elif defined(_MSC_VER) && defined(_M_IX86) && _MSC_VER == 1700
# define ZEND_FASTCALL __fastcall
#elif defined(_MSC_VER) && _MSC_VER >= 1800 && !defined(__clang__)
# define ZEND_FASTCALL __vectorcall
#else
# define ZEND_FASTCALL
#endif

さっぱりわからないので elseだと思い込むことにする。

void ZEND_FASTCALL zif_array_reverse(INTERNAL_FUNCTION_PARAMETERS)

void zif_array_reverse(INTERNAL_FUNCTION_PARAMETERS)

INTERNAL_FUNCTION_PARAMETERSの定義はこちら

Zend/zend.h
#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value

void zif_array_reverse(INTERNAL_FUNCTION_PARAMETERS)

void zif_array_reverse(zend_execute_data *execute_data, zval *return_value)

【PHP8.0】厳密なswitch文ことmatch式が使えるようになる

PHPがよく言われる問題点のひとつとして、switch曖昧な比較であるということが挙げられます。

switch($x){case1:'$xは1だよ';break;case"1":'$xは"1"だよ';break;}

case "1"に到達することは決してありません。

ということで厳密な比較を用いるswitchことmatch構文のRFCが提出されました。
以下はMatch expression v2の日本語訳です。

PHP RFC: Match expression v2

Proposal

このRFCは、switchに似ていますが、より安全なセマンティクスを持つmatch構文の提案です。

例として、Doctrineのクエリパーサを挙げます。

// Beforeswitch($this->lexer->lookahead['type']){caseLexer::T_SELECT:$statement=$this->SelectStatement();break;caseLexer::T_UPDATE:$statement=$this->UpdateStatement();break;caseLexer::T_DELETE:$statement=$this->DeleteStatement();break;default:$this->syntaxError('SELECT, UPDATE or DELETE');break;}// After$statement=match($this->lexer->lookahead['type']){Lexer::T_SELECT=>$this->SelectStatement(),Lexer::T_UPDATE=>$this->UpdateStatement(),Lexer::T_DELETE=>$this->DeleteStatement(),default=>$this->syntaxError('SELECT, UPDATE or DELETE'),};

Differences to switch

switch構文と異なる点です。

Return value

後で使いたい値をswitch内で生成することは非常によくあることです。

switch(1){case0:$result='Foo';break;case1:$result='Bar';break;case2:$result='Baz';break;}echo$result;//> Bar

そして$resultに代入し忘れることもよくあるミスです。
さらに深くネストされていた場合は、$resultがしっかり代入されているか確認するのもたいへんです。
それに対し、matchは実行した結果が評価される式です。
これによって多くの定型文を削除することができ、代入忘れというミスがなくなります。

echomatch(1){0=>'Foo',1=>'Bar',2=>'Baz',};//> Bar

No type coercion

switch文は緩やかな比較==を使います。
これは直感に反する結果をもたらすことがあります。

switch('foo'){case0:$result="Oh no!\n";break;case'foo':$result="This is what I expected\n";break;}echo$result;//> Oh no!

match式は厳密な比較===で比較します。
strict_typesの設定に関わらず常に厳密です。

echomatch('foo'){0=>"Oh no!\n",'foo'=>"This is what I expected\n",};//> This is what I expected

No fallthrough

switchフォールスルーは、多くの言語でバグの温床となっています。
caseは明示的にbreakしないかぎり、次のcaseへと実行が継続されます。

switch($pressedKey){caseKey::RETURN_:save();// break忘れたcaseKey::DELETE:delete();break;}

match式では、暗黙のbreakを付与することで、この問題を解決します。

match($pressedKey){Key::RETURN_=>save(),Key::DELETE=>delete(),};

複数条件で同じコードを実行したい場合は、条件をカンマで区切ります。

echomatch($x){1,2=>'Same for 1 and 2',3,4=>'Same for 3 and 4',};

Exhaustiveness

switchでよくあるもうひとつの問題は、全てのcaseに対応していない場合の処理です。

switch($operator){caseBinaryOperator::ADD:$result=$lhs+$rhs;break;}// BinaryOperator::SUBTRACTを渡しても何も起こらない

これが原因で、よくわからないところでクラッシュしたり、想定していない動作をしたり、なお悪いときにはセキュリティホールの原因になったりします。

$result=match($operator){BinaryOperator::ADD=>$lhs+$rhs,};// BinaryOperator::SUBTRACTを渡すと例外が発生する

match式はどのcaseにも当てはまらなかった場合はUnhandledMatchErrorを発するので、間違いに早期に気付くことができます。

Miscellaneous

Arbitrary expressions

matchする条件を任意の式にすることができます。
比較条件はswitch同様上から順に判定され、マッチした以後の条件は評価されません。

$result=match($x){foo()=>...,$this->bar()=>...,// foo()がマッチしたらここは呼ばれない$this->baz=>...,// etc.};

Future scope

この項目は将来の予定であり、このRFCには含まれません。

Blocks

このRFCでは、match式の本文はひとつの式でなければなりません。
ブロックを許すかについては、別のRFCで議論します。

Pattern matching

パターンマッチングについても検討しましたが、このRFCには含めないことにしました。
パターンマッチングは非常に複雑であり、多くの調査が必要です。
パターンマッチングについては別のRFCで議論します。

Allow dropping (true)

$result=match{...};// ↓と同じ$result=match(true){...};

Backward Incompatible Changes

matchがキーワードreserved_non_modifiersとして追加されます。
以下のコンテキストで使用することができなくなります。
・名前空間
・クラス名
・関数名
・グローバル定数

メソッド名およびクラス定数としては引き続き使用可能です。

Syntax comparison

他言語でのmatch構文

Vote

投票は2020/07/03まで、投票者の2/3の賛成で受理されます。
2020/06/22時点では賛成20反対1となっていて、よほどの問題でも発生しないかぎり受理されるでしょう。

感想

switchでよく問題になっていた曖昧な比較やbreakし忘れといったミスが、構文レベルで不可能となります。
そのため、match式に従っておけばswitchに起因する問題はほぼ発生しなくなるでしょう。
またmatch全体が返り値を持ってるのも便利ですね。

そのかわり、case内部には1式しか書けないため、複数の変数値を変更したり入れ子にしたりといった複雑な処理を書くことは難しくなります。
また、あえてbreakを書かずに継続したい場合も面倒な書き方になります。

// 1ならfooとbarを、2ならbarだけ実行したいswitch($x){case1:foo();case2:bar();break;}// aftermatch($x){1=>foo()&&bar(),2=>bar(),};

{}で括って複数の文を書けるようにするかどうかは、アロー関数同様今後の課題となっています。

従って本RFCは、決してあらゆるswitchを置き換える構文ではなく、アロー関数のように一部のswitch文を置き換えることができる短縮構文という立ち位置になります。
しかし、よほど変なことでもしていないかぎり、大抵のswitch文はmatch式に置き換えることができると思います。
安全性のためにも、今後はできるだけmatch式を使っていくとよいでしょう。

PHP8.0.0α1がリリースされたのでさっそくJITの威力を体感する(した)

2020/06/25、PHP8.0.0α1がリリースされました
PHP8系の初のバージョンです。
ただし名前のとおりα版であり、まだまだ実環境で使えるものではありません。
今後は2020/08/04にフィーチャーフリーズ、即ち新機能の取り込みが終了し、その後はβやRCで徐々に完成度を高めながら、2020/11/26に正式版がリリースされる予定です。

そんなわけでPHP8の目玉、JITの性能を試してみることにしましょう。

今回はXAMPPに導入してWebサーバとして動かすことができなかったので、以下のベンチマークはコマンドラインで実行した結果となります。
きっとそのうちXAMPPも対応してくれるはず。

インストール

QA ReleasesからVS16 x64 Thread Safeのzipをダウンロード。
適当なディレクトリに解凍。
php.ini-developmentphp.iniにコピー。

php.iniを変更。
memory_limit = 1024Mにする
date.timezone = "Asia.Tokyo"にする
extension_dir = "ext"のコメントアウトを外す

ベンチマークの設定

デフォルトの設定
・上の『php.iniを変更』のまま。ほぼ初期状態。

opcache有効の設定
・『デフォルトの設定』に対して、以下を追加する。
zend_extension=opcache
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=0x7FFFBFFF

JIT有効の設定
・『opcache有効の設定』に対して、以下を追加する。
opcache.jit_buffer_size = 128M

ベンチマーク結果

プログラムは最後に載せておきますが、JITのRFCで使われていたマンデルブロ集合を計算するやつです。
使用したCPUはIntel i7-9700 3.00GHz。
測定結果の単位は秒です。

デフォルトopcache有効JIT有効参考7.4.7
0.8147550.3555850.1061900.960383
0.8182600.3569070.1069280.938955
0.8227460.3537990.1060610.940920
0.8172020.3534230.1067680.951347
0.8193910.3535740.1061170.936791

本当かよ?????????

まずPHP7.4.7からPHP8にアップデートするだけで処理時間が1割削減されています。
ただでさえ新機能てんこ盛りだってのに、そのうえ速度も上がるとかどうなってるんだPHP8。

次いでopcacheを有効にすると処理時間が半分になります。

最後にJITを有効にしたら、処理時間がopcache有効状態の30%になりました。
30%縮まりました、ではありません。
なんだこれ。

ということで、JITを有効にするだけで、処理速度が初期状態の13%になりました。
どういうことかというと、元々1分かかっていた処理が8秒で終わるようになります。
足枷を外したとかいうレベルじゃねーぞ。
これ本当に計算してるのか?
計算結果が固定値だから結果だけどこかに保存してるとかじゃないよな?

しかもこれ、opcacheやJITの設定はほぼ初期値で、とりあえず有効にしただけという状態ですからね。
チューニングすればさらに早くなることでしょう。
面倒なので今回はそこまでやってませんが。

プリロード

PHP7.4.0で入ったプリロードで更なる高速化を図ってみましょう。

opcache.preload="path/to/preload.php"を設定
preload.phpの中身はopcache_compile_file('path/to/mandelbrot.php');

Error Preloading is not supported on Windows

はい。

実はWindowsではプリロードを使えません。

PHP7.4.0リリース時点ではWindowsでもプリロードを使えていたのですが、その後PHP7.4.2で提供が中止されてしまいました。
実はWindowsのプリロードはASLRのせいで本当のプリロードじゃないんだとかいうことらしいですがよくわかりません。

そんなわけで*nix勢あとよろ。

感想

あくまでCPUだけをがりがり使うプログラムに対しての検証結果です。
ファイルやデータベースへのアクセスが多くなる一般的なプログラムについては、また異なる結果になるでしょう。
またLaravelなど普通のWebアプリがどの程度高速化されるかも、今回は調べられていません。

とはいえ、そうはいってもさすがにこの結果は驚異的です。
もはや環境を作れないので試していませんが、PHP5.6からPHP7で速度が倍以上になったという過去もあります。
全部合わせるとPHP8が処理にかかる時間はPHP5の5%とかです。
PHP8はもはや、PHP5時代とは別次元の速度を手にいれました。

ベンチマークプログラム

マンデルブロ集合を計算するやつのほぼコピペです。

実行はコマンドラインからpath/to/php8/php.exe path/to/mandelbrot.phpとするだけ。

mandelbrot.php
define("BAILOUT",16);define("MAX_ITERATIONS",5000);// 1000だと早すぎたのでclassMandelbrot{publicfunction__construct(){$output='';$d1=microtime(1);for($y=-39;$y<39;$y++){for($x=-39;$x<39;$x++){if($this->iterate($x/40.0,$y/40.0)==0){$output.='*';}else{$output.=' ';}}$output.="\n";}$d2=microtime(1);$diff=$d2-$d1;echo$output;// 出力は最後にまとめたprintf("\nPHP Elapsed %0.6f\n",$diff);}publicfunctioniterate($x,$y){$cr=$y-0.5;$ci=$x;$zr=0.0;$zi=0.0;$i=0;while(true){$i++;$temp=$zr*$zi;$zr2=$zr*$zr;$zi2=$zi*$zi;$zr=$zr2-$zi2+$cr;$zi=$temp+$temp+$ci;if($zi2+$zr2>BAILOUT){return$i;}if($i>MAX_ITERATIONS){return0;}}}}$m=newMandelbrot();

百番煎じくらいの、CentOS8でPHP8環境作るコピペ

前置き

PHP8.0.0α1がリリースされたのでさっそくJITの威力を体感する(した)

これ試したかったんですが、centos8でやりたかった。

CentOS8で、PHP8のインストール。

で、これをコピペしようとしたら、ちょこちょこライブラリでつまずいた。

あれこれ調べた結果、コピペ手順ができたので、載せておこうと思いました。

OPCache/JIT周りは上の記事読んでどうぞ。

諸準備

dnf update -y

dnf install -y git wget tar sqlite* libxslt* gcc gcc-c++ make autoconf automake bison

re2c

最新をチェック

cd /usr/local/src

wget https://github.com/skvadrik/re2c/releases/download/1.3/re2c-1.3.tar.xz

tar Jxfv re2c-1.3.tar.xz

cd re2c-1.3

./configure

make

make install

re2c -v

PHP8

cd /usr/local/src

git clone https://github.com/php/php-src.git

cd php-src

./buildconf

./configure

make

make install

php -v

読解メモ: PHP RFC: Make constructors and destructors return void

この記事は @carrotRakkoPHP RFC: Make constructors and destructors return voidを読み解いて自分なりにまとめなおしたものです。

英語の解釈や PHP の仕様/実装などなどについて間違っている部分を見つけたらご指摘くださると幸いです。

この記事を書いている時点で読んでいるリビジョンは 2020/07/02 23:13 のものです。

__construct()の返り値の型指定

型指定の仕方を3パターン考えてみます↓

  1. 型指定なし: __construct()
  2. 型指定あり & void: __construct(): void
  3. 型指定あり & void以外: __construct(): bool

PHP 7.4.x では(事実)

型指定の仕方どうなる
型指定なし: __construct()問題なし
型指定あり & void: __construct(): voidFatal error
型指定あり & void以外: __construct(): boolFatal error

PHP 8.0 では(提案)

型指定の仕方どうなる
型指定なし: __construct()問題なし
型指定あり & void: __construct(): void問題なし
型指定あり & void以外: __construct(): boolFatal error

PHP 8.1/9.0 では(提案)

PHP 8.0 では(提案)と同じです。

型指定の仕方どうなる
型指定なし: __construct()問題なし
型指定あり & void: __construct(): void問題なし
型指定あり & void以外: __construct(): boolFatal error

__construct()に返り値の型指定をしなかった場合

暗黙的に返り値の型指定とみなされるパターンを2つ考えてみます↓

  1. なにも指定していないとみなされる(そのまま): 文法違反ですが強いて言うなら __construct(): void|mixed
  2. voidを指定したとみなされる: __construct(): voidとみなされる

PHP 7.4.x では(事実)

なにも指定していないとみなされる(そのまま): 文法違反ですが強いて言うなら __construct(): void|mixed

PHP 8.0 では(提案)

なにも指定していないとみなされる(そのまま): 文法違反ですが強いて言うなら __construct(): void|mixed

PHP 8.1/9.0 では(提案)

voidを指定したとみなされる: __construct(): voidとみなされる

__construct()から値を返した場合

PHP の怒り方を3パターン考えてみます↓

  1. 怒られない
  2. Deprecated
  3. Fatal error

PHP 7.4.x では(事実)

怒られない

PHP 8.0 では(提案)

Deprecated

PHP 8.1/9.0 では(提案)

Fatal error

Browsing Latest Articles All 29 Live