本セッションの登壇者
セッション動画
よろしくお願いします。PHPについて話させていただきます。
ピクシブという会社でPHPを書いています。
あとは、Emacs PHP Modeという、テキストエディターのEmacsでPHPを開発する開発環境みたいなものを作ったりしています。
また、PHPカンファレンスやイベント等でPHPの話をしています。
今回の私の話はユーザー寄りで、言語開発者のみに向けたものではないので、気楽に聞いていただければと思います。
PHPは見る人の心を映す汎用プログラミング言語
さて、皆さんの想像するPHPとは、どのようなものでしょうか。
パブリックイメージとして様々な考えがあるのではないかと思いますが、できることはたくさんあるので、皆さんの認識はおよそ間違いかもしれません。
ソースコードで見てみるとどんな感じのイメージかというと、
パターンA:ファイル冒頭からいきなりHTMLが始まって、その途中にif
文や制御構造、関数呼び出しが差し込まれる
パターンB:いきなりファイルをロードしたり、おもむろにデータベースに接続してそこで取得した結果をイテレーションしながら、HTMLをプリントする
パターンC:冒頭の<?php
の後にnamespaceの定義やclassの定義が書かれている
世の中にはこういうPHPがあります。
PHPを最初に作ったラスマスという人は、2007年のMySQL Conferenceでこういうことを言いました。そんな人が作った言語です。
イメージとしては、
・変数に$
があって、なんか雰囲気がPerlっぽい
・C言語みたいな関数があったり、->
(矢印) を使うとか
・なんかJavaっぽいオブジェクト指向、クラス制御があるんでしょ?
というように、PHPは、見る人によってどういうものに見えるかが変わる、その人の心を映す言語だと思っています。
では、整理しましょう。
PHPとは汎用のプログラミング言語ではあるのですが、基本はWebサービスです。シンプルな掲示板とかメールフォーム、業務システム、ゲームのAPIサーバーなども含めたHTTPで動くものが基本的に簡単に作れる言語です。
また、他の言語だとCGIライブラリとしてサポートしているような機能を標準で備えています。これらのウェブ機能は、内部実装的にはサーバーAPI(SAPI)と呼ばれる仕様で定義されています。
特徴としては、どのサーバー上でもCGI的な動きをします。2000年代はPerlのCGIやRubyのCGIのようなものもあって、それに対応しているレンタルサーバーがあれば、ソースコードをFTPでコピーして簡単に動かせる時代がありましたが、現代ではRubyのCGIはほとんど使われておらず、Ruby on Railsなどアプリケーションサーバーを動かす方式のものに置き換えられていると思っています。
PHPでもそういう動作方式のものはあるのですが、レンタルサーバーなどでは未だにCGIであったり、Apacheモジュールみたいなもので動作しており、そこにファイルを転送すれば、アプリケーションサーバーの再起動などを必要とせず、その場ですぐに動かせる言語であるということを保っています。
先ほど、Perlと似ているという話をしたのですが、Perlで先ほどのコードを書き写すとこういう感じですね。
こちらがPHPの構造ですね。表面的にはそっくりなところもあるんですが、プログラミング的にはちょっと違う特徴を持っています。
改めて、現代でも動くCGIということで、現代的なアプリケーションというのは、PerlであればもうすでにCGIを使っていないものが多いんですけれども、PHPであれば先ほどみたいなスタイルなので、20年前に書かれたPHPでも、いくつか手直しをすれば、大きく構造を変えなくても動かせる言語です。
PHPの構文として特筆すべきこととして、悪名高いgotoは最初はありませんでしたが、PHP 5系の途中で新機能として追加されました。ただ、任意の場所にジャンプできるC言語のgotoとは違って、ループの外に出たり、関数の上の方に遡っていけるぐらいで、変なところにジャンプしたり、ループの中に突入したりが起きない仕様になっています。
ただ、PythonとかRubyでは普通にできるオープンクラスやモンキーパッチと呼ばれるものが、PHPの場合は完全に許容されません。もし再定義しようとしたら、Fatal errorが出る仕様になっています。
仮想マシンは心臓部、コンパイルされたら即実行
PHPは、Rubyをはじめ現代的なスクリプト言語と同じく仮想マシン(VM)型のインタプリタになっています。実行フローとしては、インタプリタでチェック解析と構文解析をして、オペコードにコンパイルして、その後、バーチャルマシンでオペコードを実行するというフローです。
最近だとオペコードにコンパイルした後にJITコンパイルするというのもPHP 8から正式対応されています。
PHPのインタプリタはZend Engineと呼ばれてPHP本体とは疎結合に作られたものですが、結果的にPHP専用の実行エンジンになっています。
では、仮想マシンというのはどういうことでしょうか。
PHPの心臓部みたいな説明をされることがありますが、ざっくり言ってしまうと、自分のことをCPUだと思い込んでいるソフトウェアです。
Zend Engineでオペコード(opcode)にコンパイルされたら実行されます。たとえば、echo 1;
とだけ書かれたソースコードがあったとすると、引数1でECHOするという命令列にコンパイルされます。
echo 'foo'
(fooは文字列)は、ECHO に文字列のfooというリテラルを渡すとしてこのようにコンパイルされます。
print
も同様です。人間がソースコードに書くように1対1対応しているわけではなくて、似たような命令は1個にまとめられています。
1+2
のような定数の計算は、コンパイル実行時に定数だけで解決できるので、加算としてコンパイルされるのではなく、足し算された後の3
という定数にコンパイルされます。
しかし、変数1
を代入して+2
をエコーするというコードは、ASSIGNで変数に対して1という数字を代入した後、それに対して2を加算する、その変数をさらにECHOするというコードにコンパイルされます。
ということで、左側から人間が分かりやすいコードで書いたものは、オペコードのECHO 'foo'
という命令にコンパイルされたあと、実行されます。PHPはこういう実行フローになっています。
なぜこうなっているかというと、PHPは毎回実行時に構文解析をして実行すると実行効率が良くないので、一旦シンプルな命令列にコピーしてから実行するようになっているからです。分けて開発することで最適化しやすくなるということです。
型の話をしよう
PHPはいわゆる動的言語ではありますが、実は関数とかプロパティに型宣言ができて、構文上に宣言したものは実行時に必ずその型であることが保証されます。他の動的言語だと、型宣言のための機能などが入っていても実行時にはそれを無視して実行されることが多いですが、PHPに関しては実行時に必ず処理系側がランタイムチェックをして、エラーにするかキャストするかは処理系がいい感じにしてくれます。ただし現状は、変数そのものには型がない状態になっています。
それでは困るということで、PHPでは古くからDoc Comment、関数定義などの上にコメントブロックを書いて、その中に型注釈を書くという拡張がかなり発達しています。これは実行中に何か解釈されるものではありませんが、IDEや解析ツールが参照して、その配列に意図してないものが入らないかチェックしたり、取り出した時に必ずその型であるというサジェストをしたりして間違ったコードにならないように補助してくれる機能があります。
また、PHPの言語仕様自体に静的解析しやすい特徴があります。先ほど、モンキーパッチできないという話をしましたが、奥の手としてデバッグ時やテスト実行時にC言語の拡張を使って無理やり再定義することもできなくはありません。しかし、基本的にはクラスや関数の再定義はできないと考えてください。また、RubyやPythonなどでは関数文の行末にセミコロンを書かなくていい、Rubyの場合は括弧も書かなくていいといった構文ですが、PHPに関しては常に必ずつけるという言語仕様になっています。
PHPの進化というのは型宣言の進化です。今や静的型付きと言っても過言ではないPHPですが、
昔はたとえば足し算を行うadd関数なら、$a
と$b
というところに直接型を書くことはできませんでした。
PHP7になってint
という型が書けるようになりました。先ほど言ったように、これは実行時に必ず保証されます。
このint+int
ですが、PHPにはいわゆる多倍長整数 (big integer) のようなものはなく、int
で扱える上限としてPHP_INT_MAX
という定数が宣言されています。それを超えてオーバーフローした値はfloat
に自動キャストされるので、実行時エラーでクラッシュする恐れがあります。
ということで、幅広い値を取るためにfloat
で受け取ろうとすると、int
からfloat
にキャストされて意図しない変化があるかもしれません。
PHP7では、PHPDocの型宣言にint|float
として、処理系ではなくIDEの方でサポートする書き方をしていました。
PHP8になって、int
を使ってfloat
と書けるようになりました。ここまでが、現代のPHPの型宣言の持っている機能です。
静的解析ニーズを満たすPHPStan
2000年代はIDE不要で簡単に書ける言語ということで、PHPやPerl、Rubyがフィーチャーされていたと思います。現代では大規模なWebアプリケーションもそれらの言語で開発されるようになって、開発効率化のためにきちんと静的解析をしていきましょうという流れになっています。PHPでも10年ほど前にPhpStormというIDEが普及して、タグジャンプのようなシンプルなものしかなかったところから、静的解析やインテリジェントな編集機能、型に従ったメソッドジャンプなどを提供するようになりました。
PHPの静的解析ツールと呼ばれているものは2000年代から一応ありました。2000年代後半、特にPhpStorm以降は、型システムや変数のスコープを正確に扱えるツールが普及してきています。
今の代表格はPHP Static Analysis Tool(PHPStan)と呼ばれるものです。これは2016年から開発されていて、現代では静的解析、ソースコードをPHPスクリプトで読み込むのがデフォルトの実行モードです。競合の静的解析ツールのPhanは、先ほどPHPの作成者として言及したラスマスが最初に作り始めたものです。
PHPStanは、修飾型としてたとえばループしたら必ず1回以上は回るような配列をチェックできます。特筆するものとしては定数型ですね。123といった数値や”foo”
のようなリテラル、クラスに宣言された定数も型として使用できます。
PHP処理系と新リリース8.3概況
最後にPHP処理系の話もしておきます。PHPはオープンソースのプロダクトなので、PHP自体のソースコードはGitHub上にあってC言語で書かれています。基本的な知識に関してはWeb上にある『Rubyソースコード完全解説』という本が役立つと思います。
PHPの開発自体はメーリングリストで議論するのがメインで、GitHubのissueでも受け付けてくれたり、Pull Requestも送れば見てくれたりします。
新機能の追加や大規模な改善にはPHPのRFCというシステムがあって、提供する機能を言語化してメーリングリストで議論して、PHPの開発者たちで投票します。リリースは年1回で、PHP8.3がこの後11月頃にリリースされる予定です。
Emacs PHPはphp-ts-modeモードを開発中
最後に私の開発しているEmacs PHPですが、Emacs自体は標準的なPHP編集機能を持っていないので、サードパーティーパッケージとして開発しています。CCモードというC言語っぽい制御構文if
やwhile
を持っていたり、文末にセミコロンが来るような構文だったり、きちんと色分けやインデントできたりするフレームワークがあるので、基本的にはそれに基づいています。最近、追加された言語構造ではこれでサポートしきれないようなものがあるので、Lispで頑張ってインデントルールを書いてといった対応をしています。
C言語風のモード用の機能なので、HTMLテンプレートが含まれるものは基本的にはサポートしていません。最初のAパターンのようにHTMLテンプレートを含むコードに関しては、別のHTMLテンプレートに対応したモードにフォールバックする実装になっています。
最近のEmacs 29ではTree-sitterというものが統合されました。これは言語横断で使える高速なIncremental parsing libraryです。入力中でソースコードとして完璧ではないものに関してもきちんとパースしてくれるもので、徐々に普及してきています。
そこで、最近はphp-ts-modeを開発中です。先ほど言ったCCモードではものすごく頑張って正規表現でマッチさせたりしているので、長大なソースコードではかなり重くなるのですが、このTree-sitterを使うとパフォーマンス的にもかなり有利なので、軽量な開発環境が実現できます。また、構文木を直接扱えるので、今までヒューリスティックに行っていたインデントなども改善できそうです。
発表は以上です。ありがとうございました。