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

# 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](https://hhvm.com/blog/2018/09/12/end-of-php-support-future-of-hack.html)
上記のブログで公開されているようにHHVM 3.30でPHPのサポートは最後となりますので、　
それ以降はPHPのコードをHHVM上で実行することはできなくなります。

PHPコードの動作自体も完全に保証しているわけではないので、
現時点でもPHPのコードをHHVM上で動かすメリットはありません。  

つまりHack専用の環境となるわけですが、Hackのメリットは何でしょうか？
これについて厳格な型システムがメリットとあげられるかと思いますが、

**現時点ではPHPに、GoやScalaのような静的型付け機能がついてTypeScriptみたいな感じになって、
JavaやC#みたいな機能がついた**、と思うと良いかもしれません。

3.28のバージョンを踏まえていくつか紹介します。

### 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) も用意されており、  
個人的にオススメです。 

#### コード補完どうするの？
Hackのコードであれば補完はできますが、
型チェックをどこまで厳格にするかで多少変わります。
[Typechecker: Modes](https://docs.hhvm.com/hack/typechecker/modes)

##### strict

Hackのコードのみで実装する場合はこのモードが一番オススメです。  
Typechecker(Hackの型検査システム) がIDEと結びついて静的言語のコンパイラなみに教えてくれます。
PHPでいうと常にPHPStanなどがチェック機能として動いていると思うと良いかもしれません。
ただし、strictモードにするとグローバル変数などは利用できず、
requireなども記述できません。

PHPで同じみの配列を例にすると下記のような違いがあります。  

```hack
<?hh // strict

class StrictClass {
  // このプロパティは型エラーとなります。
  protected array $collection = ['Hack', 'PHP'];

  // このメソッドは型エラーとなります
  public function CollectableArray(): array {
    return [
      'message' => 'hello'
    ];
  }
}
```

Hackで記述するにはどういう配列なのかを記述し、Typecheckerが理解できるように実装する必要がありますので 
下記のように記述します。

```hack
<?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](https://github.com/hhvm/hsl) を組み合わせて、
様々な関数をreactiveで実行することができます。  

これらの配列とHackのCollectionをどういう処理を実装するかで選択することができます。
[Collection](https://docs.hhvm.com/hack/collections/introduction) 

細かい解説は別でするとして、
Hackで実装する場合は、このstrictを指定することで型のレビューや、
細かい実装のほとんどはTypecheckerが担当することになりますので、
開発の効率化などに繋げることができます。
いわゆるゆるふわと言われていたPHPと同様ではなく、
JavaやScalaといった静的型付け言語と同等である、という意識を持つことが重要です。

#### PHPのライブラリの補完について
今後PHPのコードは実行できなくなりますが、3.30を使っているうちは利用することができます。
ただしPHPのコードはそのままでは補完することができません。
型が宣言されていないため、Typecheckerが理解できずIDEで認識がされません。
これを解決するためには、hhiというファイルを作成することで、Typecheckerが認識してくれるようになります。
TypeScriptなどにもあるように型宣言ファイルを作れば良いわけです。  

hhiファイルは下記のように記述しましす。  

```hack:MiddlewareInterface.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](https://github.com/nikic/FastRoute/blob/master/FastRoute.hhi)

## IDEをさらに快適にするために

上記までの内容だけではまだHackの環境を快適にすることはできません。
快適にするためには次にのべるものを導入しましょう。

### Composer
PHP同様にcomposerを利用するわけですが、PHPと異なるのはコマンドです。
Hackなので、hhvmコマンドを利用する必要があります。

```bash
$ hhvm $(which composer) update
```

などとして利用しましょう。
hhvmを指定しないとPHPに存在しないEnumsや、Genericsがあるクラスを読み込んだ場合に
処理できないためエラーとなります。

### hhvm-autoload
hhiやstrictモード以外に、下記のライブラリを使うことで
Hackのこうしたファイルを的確に認識することができるようになります。
[hhvm/hhvm-autoload](https://github.com/hhvm/hhvm-autoload)

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

それに加えて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のライブラリで、
この辺りに対応しているライブラリはほぼ保守されているものだと思っていいでしょう。
[packagistのHackライブラリ](https://packagist.org/search/?tags=hack)
ライブラリ作成時は必須です。

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

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

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

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

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

### hsl
文中にすこし紹介したこれは Hack Standard Libraryというもので、
結構重宝することがおおい処理や、Hackならではの実装が多くあり、
Hackで実装するときには最初の学習に利用するのもいいでしょう。

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

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

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

Vec\filter_nullsは、vec配列の要素でnullではないものを対象として新たにvec配列を作成します。
Vec\mapは第一引数に渡された配列をMapとし、
第二引数は、第一引数に渡されたMapの要素がそれぞれcallbackで渡されます。  
PHPで書くと以下のようになります。

```php
Vec\map($$, function($it) {
  return (string) $it
}
```

Hackの場合は、ご存知 **Lambda** で記述するため、 `$it ==>` となります。

当然 **Pipe Operator** や **Lambda** は必須ではなく、あくまで記法の一つなので
慣れるまではPHPライクに記述しても構いません。

上記のサンプル関数を利用する場合は、次の通りです。

```hack
<?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を触った方はとりあえず作れば良いだけのファイル、として認識しているかもしれません。

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

```text
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](https://docs.hhvm.com/hack/typechecker/setup#common-configuration-options__assume_php)
arrayについての指定は下記記事を参照ください。
[array, array<T>, and array<Tk, Tv>](https://hhvm.com/blog/2017/09/26/hhvm-3-22.html)

unsafe_rxは、Hackで `<<__Rx>>` とAttribute(Annotation)で指定すると
reactiveで処理を実行させることができますが(全てではなく、HackのCollectionか、hslを利用して使うのが無難)、　　
これを指定することでreactiveにするための条件などをTypecheckerが教えてくれますので、
実践する場合に重宝します。

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

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

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

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

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

```yaml
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のコンテナに含めてしまう方が簡単かもしれません。

```docker:/docker/hhvm/Dockerfile
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として動作します。

```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.port = 9000
hhvm.server.type = fastcgi
hhvm.log.use_log_file = true
```

nginxのコンテナでは、`fastcgi_pass hhvm:9000;` というようにconfに記述すれば
nginxと組み合わせて利用できます。
環境構築ができたらIDE環境と合わせて整えてみましょう。

Hackならではの記法(PHPとの比較)と、追加続けている様々な機能についてはまた別で。