HackでEnumsを使いこなそう！

# PHPにも欲しいEnums
PHPにも欲しい！ということで近しい感覚で利用できる様なライブラリを使ったり、
作ったり、定数で頑張ったり・・・。
そんなEnumsがHackで利用できるのは多くのPHPerの方がご存知だと思います。
C＃、C++、Javaといった言語で実装経験のある方は
おなじみですので、Hackですぐにそのまんまで使える様になります。

PHPでEnumsといえば、
[myclabs/php-enum](https://github.com/myclabs/php-enum)　などを利用することが多いのではないでしょうか。

今回はEnumsについて早速みていきましょう。

# Hack Enums
まずはじめに！
HackのEnumsは intとstringのみ 利用できるということをしっかりと覚えておきましょう。
それさえ忘れずにいればあとは利用していくだけです。

## 書き方

書き方は簡単です。

```hack
<?hh

namespace Acme\Enums;

enum Size: int {
  SMALL = 0;
  MEDIUM = 1;
  LARGE = 2;
  X_LARGE = 3;
}
```

enumsはclassではないため、PSR-4などにしたがってディレクトリはそどうしたら、
と思う方もいるかもしれません。
[Hackでユニットテスト はじめてのHackTest#hh_autoload.json](https://qiita.com/ytake/items/e079f49535fb539d9584#hh_autoloadjson)
でも触れた様に[hhvm-autoload](https://github.com/hhvm/hhvm-autoload)　を利用するため
心配は無用です。
1ファイルに複数のEnumsやtype aliasなどを書き込んでいきましょう。

php環境でcomposerを動かすと、PHPにはない構文と機能となりますので、
忘れず hhvm でcomposerを動かすように・・。

これまでに紹介した様に、Hackではcomposer.jsonにあまり記述しませんが、
下記の通り登録されます。
PSR-4指定をcomposerやhh-autoloadに記述する必要はありません、

```hack
function map(): \Facebook\AutoloadMap\AutoloadMap {
  /* HH_IGNORE_ERROR[4110] invalid return type */
  return darray[
  'class' => 
  darray[
    // 省略
    'acme\\enums\\size' => 'src/types.php',
  ]
// 省略
```

これで型が保証される値を利用できる様になりました。
Enums自体は継承ができませんので、よく考えて利用しましょう。

## 値取得方法
Enumsの値を利用する場合は、定数利用と同じです。

```hack
<?hh

namespace Acme\Enums;

enum Language: string {
  PHP = 'PHP';
  HACK = 'Hack';
}
```

```hack
<?hh
require_once __DIR__ . '/../vendor/hh_autoload.php';

use type Acme\Enums\Language;

var_dump(Language::PHP, Language::HACK);
```

Enumsにはデフォルト値はありません。

## Enumsのキャスト
Enumsの例としてAcme\Enums\Language を利用する例です。

```hack
<?hh // strict

namespace Acme;

use type Acme\Enums\Language;

final class Display {

  public function render(string $l): string {
    return $l;
  }

  public function renderEnum(Language $l): string {
    return (string) $l;
  }
}

```

このクラスのメソッドにEnumsを使う場合は次の通りです。

```hack
$d = new \Acme\Display();
\var_dump(
  $d->render((string) Language::HACK), 
  $d->renderEnum(Language::HACK)
);
```

実はEnumsで記述した型は、通常のクラスで利用する型と互換性がないようです。
このためそれぞれキャストして値を扱う様になっています。
Enumsの型に暗黙の型変換を適用したい場合は、下記の様に記述する必要があります。

```hack
enum Language: string as string {
  PHP = 'PHP';
  HACK = 'Hack';
}

```

これで文字列型として扱うことができる様になりました。
利用するクラスも次の様に変更できます。

```hack
<?hh // strict

namespace Acme;

use type Acme\Enums\Language;

final class Display {

  public function render(string $l): string {
    return $l;
  }

  public function renderEnum(Language $l): string {
    return $l;
  }
}
```

暗黙の型変換が作用しますので、クラスに記述していたキャストがなくなりました。

## Enums Functions
Enumsは当然これだけではありません。
いくつかのメソッドが用意されていますので、少しだけ紹介します。

### assert()
文字列やintを渡してEnumsで定義されたものかどうかを確認したい場合に利用します。
Enumsで記述していない値を渡す場合は次の通りです。

```hack
  public function put(): void {
    $o = new \Acme\Display();
    \var_dump(
      Language::assert('Scala')
    );
  }
```

エラーにもしっかりと表示されて、例外が発生します。
存在する値の場合は、そのまま値を返却します。

```bash
Fatal error: Uncaught exception 'UnexpectedValueException' with message 'Scala is not a valid value for Acme\Enums\Language' in Path/To/*.php
```

### coerce()
assertと基本的には同じですが、こちらは例外ではなくnullを返却します。

```hack
  public function put(): void {
    $o = new \Acme\Display();
    \var_dump(
      Language::coerce('Scala') // null返却
    );
  }
```

### getNames()、getValues()
Enumsで記述した名前、もしくは値を取得します。
それぞれarrayで返却されますので、必要に応じてdictやMapに変換をするなどしましょう。

```hack
  public function put(): void {
    $o = new \Acme\Display();
    \var_dump(
      dict(Language::getNames()),
      dict(Language::getValues())
    );
  }
```

### isValid()
与えられた値がEnumsで利用できるかどうかを調べます。

```hack
  public function put(): void {
    $o = new \Acme\Display();
    \var_dump(
      Language::isValid('PHP'),
      Language::isValid('Scala')
    );
  }
```

それぞれbooleanで返却されます。

今回はEnumsについて簡単に紹介しました。
どれも利用方法は非常に簡単ですので、利用したい場面で使ってみましょう。