PHP 7でFizzBuzz

みんな、あたらしい言語を覚えたらFizzBuzzを書きたがりますよね。

私も[最終鬼畜FizzBuzz大全](http://qiita.com/tadsan/items/51d39ec7b49a8e8ee7fd)とかイキがってた時代がありました。現在はまさに約束された[誰だって婆だってFizzBuzzが書ける2016年](http://d.hatena.ne.jp/matarillo/20120515/p1)ですが、私は未だに逆FizzBuzzの解法に自信が持てません。

さて、新時代の言語と言へば**PHP 7**です。未だにオブジェクト指向がしっくりこないstaticおにいさんなのですが、新しい言語で初めてFizzBuzzを書くともなれば張りきってもしまひます。仕事ではがんばってPHP 7導入の準備を進めてるものの、まだ5系に留まってるので、うづうづしてる気持ちもあります。

そんなこともあって、どうせなら[エンタープライズに耐へうる品質](https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition)で書きたい！

でもデザインパターンがすごく苦手でおぼえられないので、[デザインパターン | TECHSCORE(テックスコア)](http://www.techscore.com/tech/DesignPattern/index.html/)にお世話になってにらめっこしながら、それっぽいクラス名とかメソッド名をつけていくことにします。英語とデザインパターンは本とか読んでも全然覚えられないんです。「英語と恋はうまくならない」ってﾓﾓｰｲも言ってた。

最初に呼び出し側のコードから。`take()`は自作の補助函数です。これは配列とか`foreach`でループ可能な何かの先頭`$n`要素を取り出してくるだけのやつです。

```php
<?php

use FizzBuzz;

$map = [
    3 => '\FizzBuzz\FizzValue',
    5 => '\FizzBuzz\BuzzValue',
];
$factory = new FizzBuzz\FizzBuzzFactory(new FizzBuzz\Mapper($map));

foreach (take($factory->generate(), 100) as $v) {
    echo $v, PHP_EOL;
}
exit;

/**
 * @param  array|\Traversable $iter
 * @param  int                $n
 * @return \Generator
 */
function take($iter, $n)
{
    $i = 0;
    foreach ($iter as $x) {
        if ($n <= $i++) { return; }
        yield $x;
    }
}
```

`FizzBuzz\FizzBuzzFactory`は`FizzBuzzStrategy`に従ってFizzBuzzしてくれるやつです。[Strategyパターン](http://www.techscore.com/tech/DesignPattern/Strategy.html/)と[AbstractFactoryパターン](http://www.techscore.com/tech/DesignPattern/AbstractFactory.html/)の合せ技(ｷﾘｯ のつもりですが、まちがってるかもしれません。`FizzBuzz\Mapper`は`FizzBuzz\FizzBuzzStrategy`を実装したクラスです。

`FizzBuzz\Mapper`には連想配列を渡して、「3の倍数なら`\FizzBuzz\FizzValue`」「5の倍数なら`\FizzBuzz\BuzzValue`」と、動的に**対応するクラス名の文字列**を指定してやることができる仕組みです。

そうそう、エンタープライズの世界なので、ただの文字列じゃなくて**値を表すオブジェクト**を対象にすることにします。ここでは`abstract FizzBuzz\Value`って抽象クラスがそれです。それを継承したクラスとして`FizzValue`、`BuzzValue`、`NumberValue`を用意しました。これらはそれぞれ`__toString()`メソッドを持ち、`"Fizz"`, `"Buzz"`、そして数字を十進数表現の文字列(たとへば`"11"`)に文字列化することができます。

おやおやFizzとBuzzはわかったよ、じゃあ`"FizzBuzz"`はどうやって出すんだね。みなさまご存じの通り、「3の倍数」であることと「5の倍数」であることは**同時に成り立ちます**。そして今回は縛りとして、`abstract`なクラス以外は継承できないように`final class`にしてあります。そもそもPHPに多重継承はないし… そんなときのための`ComposableValue`クラスです。こいつがあれば、`FuzzBuzz\Value`を実装したクラスがいくつあろうと、状態を組み合せられます。やったぞ！

そろそろ説明おにいさんも飽きてきたのでコード見せます。

```php
<?php

namespace FizzBuzz
{
    // Strategy

    interface FizzBuzzStrategy
    {
        public function fizzbuzzize(int $n) :Value;
    }

    final class Mapper implements FizzBuzzStrategy
    {
        /** @var array */
        private $fizzbuzz_map;

        /**
         * @param array $fizzbuzz_map
         */
        public function __construct(array $fizzbuzz_map)
        {
            $this->fizzbuzz_map = $fizzbuzz_map;
        }

        /**
         * @param  int $n
         * @return Value
         */
        public function fizzbuzzize(int $n) :Value
        {
            $vs = [];

            foreach ($this->fizzbuzz_map as $m => $v) {
                if ($n % $m == 0) {
                    $vs[] = ($v instanceof \Closure) ? $v($n) : new $v($n);
                }
            }

            return (count($vs) > 0) ? new ComposableValue($n, ...$vs) : new NumberValue($n);
        }
    }

    // Value

    abstract class Value
    {
        /** @var int */
        protected $n;

        public function __construct(int $n)
        {
            $this->n = $n;
        }

        abstract function __toString();
    }

    final class NumberValue extends Value
    {
        public function __toString()
        {
            return sprintf("%d", $this->n);
        }
    }

    trait SymbolToString
    {
        public function __toString()
        {
            return static::SYMBOL;
        }
    }

    final class FizzValue extends Value
    {
        use SymbolToString;
        const SYMBOL = 'Fizz';
    }

    final class BuzzValue extends Value
    {
        use SymbolToString;
        const SYMBOL = 'Buzz';
    }

    final class ComposableValue extends Value
    {
        /** @var Value[] */
        private $values = [];

        public function __construct(int $n, Value ...$values)
        {
            parent::__construct($n);

            $this->values = array_values($values);
        }

        public function add(Value $value)
        {
            $this->values[] = $value;
        }

        public function __toString()
        {
            $s = '';

            foreach ($this->values as $v) {
                $s .= $v;
            }

            return $s;
        }
    }

    // Factory

    final class FizzBuzzFactory
    {
        /** @var FizzBuzzStrategy */
        private $strategy;

        public function __construct(FizzBuzzStrategy $strategy)
        {
            $this->strategy = $strategy;
        }

        /**
         * @param  int $from
         * @return \Generator
         */
        public function generate(int $from = 1)
        {
            $n = $from;
            while (true) {
                yield $this->strategy->fizzbuzzize($n++);
            }
        }
    }
}
```

~~*(TODO: あとでリポジトリつくってGitHubに置く)*~~

GitHubに置きました https://github.com/BaguettePHP/FizzBuzz

```
composer g require baguette/fizzbuzz
```

Composerでインストールしよう。

## まとめ

だるいので、ここで使ってるPHPの言語機能を列挙するだけにしておきます ヾ(〃＞＜)ﾉﾞ

 - [PHP: クラスとオブジェクト - Manual](http://php.net/manual/ja/language.oop5.php)
    - [PHP: コンストラクタとデストラクタ - Manual](http://php.net/manual/ja/language.oop5.decon.php)
    - [PHP: プロパティ - Manual](http://php.net/manual/ja/language.oop5.properties.php)
    - [PHP: オブジェクト定数 - Manual](http://php.net/manual/ja/language.oop5.constants.php)
    - [PHP: オブジェクト インターフェイス - Manual](http://php.net/manual/ja/language.oop5.interfaces.php)
    - [PHP: トレイト - Manual](http://php.net/manual/ja/language.oop5.traits.php)
    - [PHP: クラスの抽象化 - Manual](http://php.net/manual/ja/language.oop5.abstract.php)
    - [PHP: オブジェクトの継承 - Manual](http://php.net/manual/ja/language.oop5.inheritance.php)
    - [PHP: 無名クラス - Manual](http://php.net/manual/ja/language.oop5.anonymous.php)
    - [PHP: 遅延静的束縛 (Late Static Bindings) - Manual](http://php.net/manual/ja/language.oop5.late-static-bindings.php) *(これは不必要)*
    - [PHP: マジックメソッド - Manual](http://php.net/manual/ja/language.oop5.magic.php#object.tostring) #__toString()
 - [PHP: 関数 - Manual](http://php.net/manual/ja/language.functions.php)
    - [PHP: 関数の引数 - Manual](http://php.net/manual/ja/functions.arguments.php#functions.arguments.type-declaration) #型宣言
    - [PHP: 無名関数 - Manual](http://php.net/manual/ja/functions.anonymous.php)
    - [PHP: 可変関数 - Manual](http://php.net/manual/ja/functions.variable-functions.php)
 - [PHP: ジェネレータ - Manual](http://php.net/manual/ja/language.generators.php)
    - [PHP: ジェネレータとは - Manual](http://php.net/manual/ja/language.generators.overview.php)
    - [PHP: ジェネレータの構文 - Manual](http://php.net/manual/ja/language.generators.syntax.php)
 - [PHP: 制御構造 - Manual](http://php.net/manual/ja/language.control-structures.php)
    - [PHP: while - Manual](http://php.net/manual/ja/control-structures.while.php)
    - [PHP: foreach - Manual](http://php.net/manual/ja/control-structures.foreach.php)

　

　
　

　

　

-----

## おまけ

なんでわざわざ`FizzBuzz\Mapper`なんてクラスを分けたのかって? それに、上のコードには無名クラスなんてどこにもないじゃないか。うんうん。

それはね、[頭のとんがった上司](http://practical-scheme.net/trans/icad-j.html#pointy)に「チミィ、このプログラムに7の倍数だったときは`Nass`って出力する機能を追加してくれたまへよ」って言はれたときのためだよ。

```php
$map = [
    3 => '\FizzBuzz\FizzValue',
    5 => '\FizzBuzz\BuzzValue',
    7 => function (int $n) {
        return new class($n) extends FizzBuzz\Value { use FizzBuzz\SymbolToString; const SYMBOL = 'Nass'; };
    }
];
```