HHVM/Hack
HHVMについて、下記の印象をお持ちの方が多いのではないでしょうか
- HHVMってPHPのちょっと早いやつだよね
- PHP7で早くなったし、HHVMってもう使わないよね
HHVM上で動作するHackについてはおそらく下記の印象をお持ちではないでしょうか
- EnumsとかGenericsとか使いたいけどIDEが。。。
- PHP7も早くなってるし、何が嬉しいの?
HHVMは 2018/10/13現在のバージョンで3.28.3で、大きく進化していますので、
せっかくなのでHHVM/Hackで開発するための知識のアップデートをしていきましょう。
HHVMの今
Ending PHP Support, and The Future Of Hack
上記のブログで公開されているようにHHVM 3.30でPHPのサポートは最後となりますので、
それ以降はPHPのコードをHHVM上で実行することはできなくなります。
PHPコードの動作自体も完全に保証しているわけではないので、
現時点でもPHPのコードをHHVM上で動かすメリットはありません。
つまりHack専用の環境となるわけですが、Hackのメリットは何でしょうか?
これについて厳格な型システムがメリットとあげられるかと思いますが、
現時点ではPHPに、GoやScalaのような静的型付け機能がついてTypeScriptみたいな感じになって、
JavaやC#みたいな機能がついた、と思うと良いかもしれません。
3.28のバージョンを踏まえていくつか紹介します。
IDE事情
開発する上で一番大事なものでしょう。
これは Nuclide のイメージが強いと思いますが、
現在は Visual Studio Code に
Hack for Visual Studio Code も用意されており、
個人的にオススメです。
コード補完どうするの?
Hackのコードであれば補完はできますが、
型チェックをどこまで厳格にするかで多少変わります。
Typechecker: Modes
strict
Hackのコードのみで実装する場合はこのモードが一番オススメです。
Typechecker(Hackの型検査システム) がIDEと結びついて静的言語のコンパイラなみに教えてくれます。
PHPでいうと常にPHPStanなどがチェック機能として動いていると思うと良いかもしれません。
ただし、strictモードにするとグローバル変数などは利用できず、
requireなども記述できません。
PHPで同じみの配列を例にすると下記のような違いがあります。
<?hh // strict
class StrictClass {
// このプロパティは型エラーとなります。
protected array $collection = ['Hack', 'PHP'];
// このメソッドは型エラーとなります
public function CollectableArray(): array {
return [
'message' => 'hello'
];
}
}
Hackで記述するにはどういう配列なのかを記述し、Typecheckerが理解できるように実装する必要がありますので
下記のように記述します。
<?hh // strict
class StrictClass {
protected varray<string> $collection = ['Hack', 'PHP'];
public function CollectableArray(): darray<string, string> {
return [
'message' => 'hello'
];
}
}
Hackの配列には PHPライクな配列だと array<int, string>
や darray<string, string>
、
varray<string>
、
Hack独特の vec
、dict
、keyset
があります。
後者のvec
、dict
、keyset
は hhvm/hsl を組み合わせて、
様々な関数をreactiveで実行することができます。
これらの配列とHackのCollectionをどういう処理を実装するかで選択することができます。
Collection
細かい解説は別でするとして、
Hackで実装する場合は、このstrictを指定することで型のレビューや、
細かい実装のほとんどはTypecheckerが担当することになりますので、
開発の効率化などに繋げることができます。
いわゆるゆるふわと言われていたPHPと同様ではなく、
JavaやScalaといった静的型付け言語と同等である、という意識を持つことが重要です。
PHPのライブラリの補完について
今後PHPのコードは実行できなくなりますが、3.30を使っているうちは利用することができます。
ただしPHPのコードはそのままでは補完することができません。
型が宣言されていないため、Typecheckerが理解できずIDEで認識がされません。
これを解決するためには、hhiというファイルを作成することで、Typecheckerが認識してくれるようになります。
TypeScriptなどにもあるように型宣言ファイルを作れば良いわけです。
hhiファイルは下記のように記述しましす。
<?hh // strict
namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
interface MiddlewareInterface {
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface;
}
ここでは、PSR-15のインターフェースをHackの型宣言ファイルとして扱うように、
<?hh
に変更します。
そしてファイル名をhhiとし、コードとして実行できないようにします。
これで型宣言ファイルとして機能させることができます。
インターフェース以外にも通常のクラスファイルにも用意することで、補完ができるようになるわけです。
Typecheckerの性質を知ることでフル活用できるようになるわけです。
下記のPHPのライブラリにも実は含まれていたりします。
nikic/FastRoute
IDEをさらに快適にするために
上記までの内容だけではまだHackの環境を快適にすることはできません。
快適にするためには次にのべるものを導入しましょう。
Composer
PHP同様にcomposerを利用するわけですが、PHPと異なるのはコマンドです。
Hackなので、hhvmコマンドを利用する必要があります。
$ hhvm $(which composer) update
などとして利用しましょう。
hhvmを指定しないとPHPに存在しないEnumsや、Genericsがあるクラスを読み込んだ場合に
処理できないためエラーとなります。
hhvm-autoload
hhiやstrictモード以外に、下記のライブラリを使うことで
Hackのこうしたファイルを的確に認識することができるようになります。
hhvm/hhvm-autoload
現在Hackで実装する場合は ほとんど必須 と言ってもいいくらいのライブラリです。
利用方法は簡単で、composerのvendor/autoload.phpを記述している部分を
vendor/hh_autoload.php へファイルに変更します。
それに加えてhh_autoload.jsonをプロジェクトルートに作成し、
autoloaderにHackのクラスファイルや、
hhiファイル、enumsやtype aliasなどを記載したファイルがどのディレクトリにあるかなどを記述します。
記述方法はいくつかありますが、もっとも標準的な指定は以下のようなものになるかと思います。
{
"roots": [
"src/"
],
"devRoots": [
"tests/"
]
}
これを記述しておくことで、composer dump-autoload時にHack用のautoloaderに取り込まれ、
IDEへ反映させたり、様々なファイルのオートロードが可能になります。
名前空間などはそのままcomposer.jsonを利用しましょう。
公開されているHackのライブラリで、
この辺りに対応しているライブラリはほぼ保守されているものだと思っていいでしょう。
packagistのHackライブラリ
ライブラリ作成時は必須です。
hhast
HackのASTライブラリで、Linterや、HHVMのバージョンによる記述方法の差分などを解決してくれるライブラリです。
Hack for Visual Studio Codeなどと組み合わせて利用することで、
実装中に、型チェック以外にcheck style的な動作をしてくれるわけです。
Hack専用ですので、Hackで実装するときは特別に何かない限りは入れておくと良いでしょう。
これを利用するときは、プロジェクトルートに hhast-lint.json
を作成してどのディレクトリを対象にするか、
などを記述します。
{
"roots":[
"src/",
"tests/"
]
}
これも指定方法はいくつかありますが、ほとんど上記の指定で良いでしょう。
アプリケーションに合わせて記述してください。
hsl
文中にすこし紹介したこれは Hack Standard Libraryというもので、
結構重宝することがおおい処理や、Hackならではの実装が多くあり、
Hackで実装するときには最初の学習に利用するのもいいでしょう。
サンプルにあるコードを例にしてみましょう。
<?hh // strict
use namespace HH\Lib\Vec;
function sample(vec<?int> $foo): vec<string> {
return $foo
|> Vec\filter_nulls($$)
|> Vec\map($$, $it ==> (string) $it);
}
上記の関数は、intのみを要素とするvec配列で、vecの中身にnullがあっても構わない となります。
渡されたvecはPipe Operatorで
順番に処理されてreturnされます。
Vec\filter_nullsは、vec配列の要素でnullではないものを対象として新たにvec配列を作成します。
Vec\mapは第一引数に渡された配列をMapとし、
第二引数は、第一引数に渡されたMapの要素がそれぞれcallbackで渡されます。
PHPで書くと以下のようになります。
Vec\map($$, function($it) {
return (string) $it
}
Hackの場合は、ご存知 Lambda で記述するため、 $it ==>
となります。
当然 Pipe Operator や Lambda は必須ではなく、あくまで記法の一つなので
慣れるまではPHPライクに記述しても構いません。
上記のサンプル関数を利用する場合は、次の通りです。
<?hh
require __DIR__ . '/vendor/hh_autoload.php';
var_dump(sample(vec[]));
var_dump(sample(vec[1,2,3]));
var_dump(sample(vec[1, null, 400]));
簡単ですね。
ここで、strictいうたのに違うのか?と思うかもしれませんが、
Toplevel statements besides requires are not allowed in strict files
となりますので、トップレベルではstrict指定できません。
.hhconfig
Hackとして動作させるには、.hhconfig
ファイルをプロジェクトルートに設置しなければいけません。
実はこの.hhconfigファイルには様々な指定を記述することができます。
ちょっと前のHackを触った方はとりあえず作れば良いだけのファイル、として認識しているかもしれません。
主に実装する場合は、次の記述をするといいでしょう
assume_php=true
ignored_paths = [ "vendor/.+/tests/.+", "vendor/hhvm/hhast/.+" ]
safe_array = true
safe_vector_array = true
unsafe_rx = false
PHPのコードが混入する場合はtrue(PHPのライブラリを使うなど)にする必要がありますが、
特に必要がなければfalseで良いでしょう。
options__assume_php
arrayについての指定は下記記事を参照ください。
array, array, and array
unsafe_rxは、Hackで <<__Rx>>
とAttribute(Annotation)で指定すると
reactiveで処理を実行させることができますが(全てではなく、HackのCollectionか、hslを利用して使うのが無難)、
これを指定することでreactiveにするための条件などをTypecheckerが教えてくれますので、
実践する場合に重宝します。
動作環境について
開発するための事前知識がついたら実際に開発環境を、となりますが、
下記にある通りの環境で動作させることが望ましいです。
Installation
MacOSの場合は、brewでインストールできます
$ brew tap hhvm/hhvm
$ brew install hhvm
Docker
Dockerを利用する場合は公式から提供されていますので、それを利用しましょう。
Docker Hub
docker-composeを利用する場合は、下記のようにすると簡単に利用できます。
version: '3'
services:
nginx:
image: nginx:1.15.3-alpine
ports:
- "80:80"
volumes:
- .:/var/www/html
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/conf.d:/etc/nginx/conf.d
restart: always
depends_on:
- hhvm
links:
- hhvm
hhvm:
build:
context: ./docker/hhvm
volumes:
- .:/var/www/html
- ./docker/hhvm/hh.conf:/etc/hh.conf
- ./docker/hhvm/php.ini:/etc/hhvm/php.ini
- ./docker/hhvm/server.ini:/etc/hhvm/server.ini
command: hhvm --mode server -vServer.AllowRunAsRoot=1
restart: always
composerを利用するには、hhvmコマンドを利用する必要がありますので、
hhvmのコンテナに含めてしまう方が簡単かもしれません。
FROM hhvm/hhvm:3.28.3
RUN apt-get update
RUN cd '/' \
&& php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
&& php composer-setup.php \
&& php -r "unlink('composer-setup.php');" \
&& mv composer.phar /usr/local/bin/composer
hhvmの開発向けphp.iniなどは環境に合わせて記述してください。
下記の記述でfastcgiとして動作します。
xdebug.enable = 1
date.timezone = Asia/Tokyo
hhvm.log.header = true
hhvm.debug.server_error_message = true
display_errors = On
html_errors = On
error_reporting = 22527
hhvm.server.fix_path_info = true
hhvm.server.port = 9000
hhvm.server.type = fastcgi
hhvm.log.use_log_file = true
nginxのコンテナでは、fastcgi_pass hhvm:9000;
というようにconfに記述すれば
nginxと組み合わせて利用できます。
環境構築ができたらIDE環境と合わせて整えてみましょう。
Hackならではの記法(PHPとの比較)と、追加続けている様々な機能についてはまた別で。