[2020] HHVM/Hackの始め方 導入編

# HHVM/Hack

今回はHHVM/Hackについて、
2018年のアドベントカレンダーからアップデートした内容をお届けします。
まずはHHVMについて、以前は下記の印象をお持ちの方が多いと思います。

 - HHVMってPHPのちょっと早いやつだよね
 - PHP7で早くなったし、HHVMってもう使わないよね

これはあくまでHHVM3系までの話であり、 
2019年1月にリリースされた v4.0.0以降でPHPを動かすことはできません。

ネット上にある日本語の記事のほとんどが古いバージョンのインストール記事が多く、
最近のものでもHHVM3系、または2系をインストールしている物はほぼ動かないものと思ってください。

## HHVMの今

先日PHP8がリリースされ、Hackに実装されていたJITコンパイラや、
nullsafe operator、Attributes、Constructor property promotionなどが追加されました。  

そんな中でHackを使う利点ってあるの。。？ と思う方も多いのかもしれません。

現時点のHackは、PHPとは全く別の方向を歩んでおり、
元はPHP5系ではありましたが、それからLL言語っぽくない進化を遂げています。
そんな中でもHackの大きなアドバンテージは厳格な型チェックがあります。

PHPではPhan/PHPStan/Psalmといったライブラリを導入しなければ静的解析ができませんが、
HackではTyepCheckerが、IDEと結びつき、
LL言語でありながらも静的型付け言語に近い解析、コンパイラライクなツールが用意されています。
PHPでは開発を行うまでにいくつか用意するものがあり、
安全な開発までにステップが必要です。
またより良いライブラリが登場すると、それへの移行コストが付き纏います。

これが言語レベルでサポートされていればどんなに幸せでしょうか？
HHVM/Hackではだれでも簡単に(何もしなくても)コンパイラ並の静的解析がリアルタイムで実行されます。
これはPHPと大きく異なる点ともいえるでしょう！
もちろんPHPでは実装されていないEnumsとかGenerics、PHPよりも進化しているAttributeや、
シングルバイナリ実行など、
実際に利用すると静的片付け言語の様な機能が多く実装されているのがわかります。

そんなHHVM/Hackを始めるためにどうすればいいのか解説していきましょう。

## IDE

IDEは開発する上で一番大事なもの。

現在でも[Nuclide](https://nuclide.io/docs/languages/hack/) のイメージが強い方が多いと思いますが、  
現在は [Visual Studio Code](https://code.visualstudio.com/) に
[Hack for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=pranayagarwal.vscode-hack) も用意されているので、
Visual Studio CodeにHack for Visual Studio Codeを組み合わせるのが鉄板です。

## コードの書き方

以前はhhタグにstrictを指定するのが一般的でしたが、
現在はhhタグはあまり使わない方がいいでしょう。
hhタグを使わない代わり、ファイル拡張子に.hackを使うことで自動でstrictモードになるように変わっています。

```hack:sample.hack
final class StrictClass {

  public function __construct(
    private vec<string> $v = vec['Hack', 'PHP'],
    private (function(): string) $func = () ==> "HHVM"
  ) {}

  public function CollectableArray(): vec<string> {
    return $this->v;
  }
}
```

hhタグを書かずにいきなりコードを書くように変更されていますので、
PHPと併用する方は書き方に気をつけましょう！

> IDEを使っても補完とかあまりしてくれないんでしょう？

確かにPHPStormの様に自動で色々やってくれるわけではありませんが、
普通の補完については不自由なく対応してくれます。

![スクリーンショット 2020-11-30 22.05.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32814/78d677ee-19e9-7dc2-252d-8c2b8a553d96.png)

開発時には上記のようにクラスの内容なども教えてくれますので、
とくに不自由はないでしょう。
当然PHPと共通している関数や、Hack独自の関数なども教えてくれます。

## 開発を快適にするために

開発を快適にするためには次にのべるものを導入しましょう。

### Composer

以前HHVM上での動作がドロップされましたが、
通常通りHackのライブラリ導入にはそのまま使える様になっています。
(2.0で全く問題なく動作します)

```bash
$ composer update
```

これまでは `$ hhvm $(which composer) update` などとしていましたが、
利用できなくなりました。
その代わりにPHPと同じ様にcomposerコマンドを実行すると、
HHVM環境に合わせてHackのライブラリなどを持ってきてくれます。

yarnを拡張してFacebook関連の言語やライブラリを提供するパッケージマネージャ開発話がありましたが、
現在はなくなっています。

### hhvm-autoload

Hackではcomposerのautoloaderを使わずに、Hack専用のものを使う必要があります。
[hhvm/hhvm-autoload](https://github.com/hhvm/hhvm-autoload)

現在Hackで実装する場合は **ほとんど必須** のライブラリです。
利用方法は簡単で、composerのvendor/autoload.phpを記述している部分を
**vendor/hh_autoload.php**、または**vendor/autoload.hack**へ変更します。

それに加えてhh_autoload.jsonをプロジェクトルートに作成し、
autoloaderにHackのクラスファイルや、
hhiファイル、enumsやtype aliasなどを記載したファイルがどのディレクトリにあるかなどを記述します。
記述方法はいくつかありますが、もっとも標準的な指定は以下のようなものになるかと思います。

```json:hh_autoload.json
{
  "roots": [
    "src/"
  ],
  "devRoots": [
    "tests/"
  ]
}
```

これを記述しておくことで、composer dump-autoload時にHack用のautoloaderに取り込まれ、
IDEへ反映させたり、様々なファイルのオートロードが可能になります。
名前空間などはそのままcomposer.jsonを利用しましょう。

公開されているHackのライブラリで、
この辺りに対応しているライブラリはほぼ保守されているものだと思っていいです。
逆にこのライブラリを導入していないものは保守されていないものとなりますので、
判断しやすいでしょう！

### hhast
HackのASTライブラリで、Linterや、HHVMのバージョンによる記述方法の差分などを解決してくれるライブラリです。
Hack for Visual Studio Codeなどと組み合わせて利用することで、
実装中に、型チェック以外にcheck style的な動作をしてくれるわけです。

[hhvm/hhast](https://github.com/hhvm/hhast)

Hack専用ですので、特別に理由がない限りは入れておくと良いでしょう。
これを利用するときは、プロジェクトルートに `hhast-lint.json` を作成してどのディレクトリを対象にするかを記述します。

```json:hhast-lint.json
{
  "roots":[
    "src/",
    "tests/"
  ]
}
```

これも指定方法はいくつかありますが、ほとんど上記の指定で良いでしょう。
アプリケーションに合わせて記述してください。

特別な指定をしたい場合は、overridesを使って以下のように記述できます。

```json
  "overrides": [
    {
      "patterns": [ "codegen/*", "tests/examples/*" ],
      "disabledLinters": [
        "Facebook\\HHAST\\UnusedUseClauseLinter",
        "Facebook\\HHAST\\LicenseHeaderLinter"
      ],
      "disableAllAutoFixes": true
    },
    {
      "patterns": [ "src/__Private/Wrap/*" ],
      "disabledLinters": [
        "Facebook\\HHAST\\CamelCasedMethodsUnderscoredFunctionsLinter"
      ]
    },
    {
      "patterns": ["tests/*"],
      "extraLinters": [
        "Facebook\\HHAST\\DataProviderTypesLinter"
      ]
    }
  ]
```

当然ですがPHP-CS-Fixerなどは利用せずに、Hack専用のものを使いましょう！

### hsl

hslは Hack Standard Libraryというもので、
重宝することが多い処理や、Hackならではの実装が多く用意されているライブラリで、
Hackで実装するときには最初の学習に利用するといいでしょう。

[hhvm/hsl](https://github.com/hhvm/hsl)

サンプルにあるコードを例にしてみましょう。

```hack
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](https://docs.hhvm.com/hack/operators/pipe-operator)で
順番に処理されてreturnされます。  

Vec\filter_nullsは、vec配列の要素でnullではないものを対象として新たにvec配列を作成します。
Vec\mapは第一引数に渡された配列をMapとし、
第二引数は、第一引数に渡されたMapの要素がそれぞれcallbackで渡されます。
上記のサンプル関数を利用する場合は、次の通りです。

```hack
require_once __DIR__ . '/vendor/autoload.hack';

<<__EntryPoint>>
function main(): void {
  var_dump(sample(vec[]));
  var_dump(sample(vec[1,2,3]));
  var_dump(sample(vec[1, null, 400]));
}
```

簡単です。

## .hhconfig

Hackとして動作させるには、`.hhconfig`ファイルをプロジェクトルートに設置しなければいけません。
この.hhconfigファイルには様々な指定を記述することができます。
ちょっと前のHackを触った方はとりあえず作れば良いだけのファイル、として認識しているかもしれません。
が、現在はそうではありません。
エラーの内容の精査やさまざまな挙動の変更を指示することができます。

実装する場合は、次の記述をするといいでしょう

```text
assume_php=false
ignored_paths = [ "vendor/.+/tests/.+" ]
disallow_elvis_space=true
disallow_non_arraykey_keys=true
disallow_unsafe_comparisons=true
decl_override_require_hint=true
enable_experimental_tc_features=shape_field_check,sealed_classes
disable_primitive_refinement=true
disable_static_local_variables = true
disallow_array_literal = true
allowed_decl_fixme_codes=1002,2053,4045,4047
allowed_fixme_codes_strict=1002,2011,2049,2050,2053,4007,4027,4045,4047,4053,4104,4106,4107,4108,4110,4128,4135,4188,4240,4323
```

いくつかのライブラリではcomposer install時にtestsが含まれる場合もありますので、
念のためにignored_pathsでtype checkerの対象から外します。
disallow_*はHackらしく、通常のものからより厳格に書くためのものです。
PHP系の書き方を排除することができます。
disable_系も同じく、古き良きPHPライクな書き方を排除できます。
現在も動くには動く、古き良きPHPの記述法は将来のバージョンアップで削除される可能性が非常に高いため、
HHVMのバージョンアップ情報をブログでキャッチして、対応するといいでしょう。

[HHVM blog](https://hhvm.com/blog/)

余談ですが、Hack/HHVMのドキュメントサイトは当然Hackでできています。

# 動作環境について

開発するための事前知識がついたら実際に開発環境を、となりますが、
下記にある通りの環境で動作させることが望ましいです。
[Installation](https://docs.hhvm.com/hhvm/installation/linux) 

MacOSの場合は、brewでインストールできます

```bash
$ brew tap hhvm/hhvm
$ brew install hhvm
```

Linux環境ではUbuntuのみサポートされています。
インターネット上にはCentOSにインストールする、というようなエントリがありますが、
100%古い記事なので、注意しておきましょう。
現在はDockerが普及していますので、特に都合が悪いということはないでしょう。

## Docker

Dockerを利用する場合は公式から提供されていますので、それを利用しましょう。
[Docker Hub/hhvm-proxygen](https://hub.docker.com/r/hhvm/hhvm-proxygen/)
[Docker Hub/hhvm](https://hub.docker.com/r/hhvm/hhvm/)

コンテナを使って運用をする場合は、
アプリケーションをコンパイルして、シングルバイナリで動かすことができるhhvm-proxygenを利用することを強くお勧めします。

docker-composeを利用する場合は、下記のようにすると簡単に利用できます。

```yaml
version: '3'
services:
  nginx:
    image: nginx:1.19-alpine
    ports:
      - "80:80"
    volumes:
      - .:/var/www/html
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./docker/nginx/conf.d:/etc/nginx/conf.d
    container_name: web-server
    tty: true
  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
    tty: true
    container_name: hhvm
    ports:
      - 19001:19001
```

composerを利用するには、hhvmコマンドを利用する必要がありますので、
hhvmのコンテナに含めてしまう方が簡単かもしれません。

```docker:/docker/hhvm/Dockerfile
FROM hhvm/hhvm-proxygen:latest AS dev

RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive
RUN apt install -y dnsutils iputils-ping net-tools

RUN hhvm --version && php --version
RUN cd $(mktemp -d) \ 
 && curl https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

```

hhvmの開発向けphp.iniなどは環境に合わせて記述してください。
proxygenで動かす場合は下記の記述にしておくといいでしょう。

```ini:php.ini
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.type = proxygen
hhvm.server.port = 18080
hhvm.log.use_log_file = true
hhvm.server.source_root = /var/www/public

hhvm.php_file.extensions[hack]=1 
hhvm.jit=1

hhvm.server.default_document = "index.hack"
hhvm.server.error_document404 = "index.hack"
hhvm.server.utf8ize_replace = true
hhvm.log.file=stderr

hhvm.admin_server.port=19001
hhvm.admin_server.password=SomePassword
```

環境構築ができたらIDE環境と合わせて整えてみましょう。

Hackを運用するためのコンテナ環境、コンパイルについては次回以降に紹介します。
乞うご期待！