PHP: Builderパターンの実装手順 #2【Fluent Interface実装】

前回、[PHPでのBuilderパターンを実装する手順を紹介した](https://qiita.com/suin/items/d8d4dc019d3beb428249)が、今回はその続きとしてfluent interfaceを実装したBuilderパターンの実装方法を説明する。

なお、本稿のサンプルコードの完全版は[GitHub](https://github.com/suin/php-playground/tree/master/BuilderPattern/No2FluentInterface)にて公開している。

## fluent interface とは

fluent interfaceとは「流れるようなインターフェイス」という意味で、method chaining(メソッドの連鎖)という小技を使って、可読性の高いコードを実現するメソッドの作り方のことだ。よくドメイン固有言語(DSL)を提供するようなクラスを作るときに使われる。

fluent interfaceを提供しているので有名なのはjQueryかと思う。

```js:jQuery
$('table')
    .find('tr:even').addClass('even').end()
    .find('tr:odd').addClass('odd');
```

## fluentじゃないBuilder

前回のEmailBuilderの実装は普通のインターフェイスだったので、セッターを呼ぶたびに`$emailBuilder->`と書かなければならなかった。

```php
$emailBuilder = new EmailBuilder();
$emailBuilder->setFrom('alice@example.com');
$emailBuilder->setTo('bob@example.com');
$emailBuilder->setSubject('Hello');
$emailBuilder->setBody('Hello, there.');
$email = $emailBuilder->build();
```

今回は、次のコードのように、メソッドを連鎖できるようにしてfluent interfaceを実現したいと思う。上のコードと見比べると、繰り返し書かれていたことがなくなったぶん、可読性が良くなったと感じないだろうか？

```php
$email = (new EmailBuilder())
    ->setFrom('alice@example.com')
    ->setTo('bob@example.com')
    ->setCc('carol@example.com')
    ->setSubject('Hello')
    ->setBody('Hello, Bob.')
    ->build();
```

## fluent interfaceの実装法

PHPのfluent interfaceの実装は各メソッドに`return $this`を追加するだけだ。戻り値の型宣言は`self`にする。

```php

final class EmailBuilder
{
    // ...略...

    public function setFrom(string ...$from): self
    {
        $this->from = $from;
        return $this;
    }

    public function setTo(string ...$to): self
    {
        $this->to = $to;
        return $this;
    }

    public function setCc(string ...$cc): self
    {
        $this->cc = $cc;
        return $this;
    }

    public function setSubject(string $subject): self
    {
        $this->subject = $subject;
        return $this;
    }

    public function setBody(string $body): self
    {
        $this->body = $body;
        return $this;
    }

    // ...略...
}
```

この実装をUMLクラス図に起こすとこうなる:

![image.png](https://qiita-image-store.s3.amazonaws.com/0/889/a4eaf4e4-9579-26c4-3f03-52e90d86b8f4.png)


## テストコード

テストコードでもfluent interfaceを使っておけば、`return $this`がちゃんと動いていることが確認できる。

```php
use PHPUnit\Framework\TestCase;

final class EmailTest extends TestCase
{
    /**
     * @test
     */
    public function email_builder_usage(): void
    {
        $email = (new EmailBuilder())
            ->setFrom('alice@example.com')
            ->setTo('bob@example.com')
            ->setCc('carol@example.com')
            ->setSubject('Hello')
            ->setBody('Hello, Bob.')
            ->build();

        // The Email object will be like the following:
        self::assertSame(['alice@example.com'], $email->getFrom());
        self::assertSame(['bob@example.com'], $email->getTo());
        self::assertSame(['carol@example.com'], $email->getCc());
        self::assertSame('Hello', $email->getSubject());
        self::assertSame('Hello, Bob.', $email->getBody());
    }

    // ...略...

    public function test_missing_sender_email_address(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage(
            'At least one from-address must be provided'
        );
        (new EmailBuilder())
            ->setCc('dummy@address')
            ->setSubject('dummy_subject')
            ->setBody('dummy_body')
            ->build();
    }
}
```

## まとめ

* Builderパターンにfluent interfaceを取り入れると、Builderを使う側のコードの可読性が高まる。

## 関連

* [PHP: Builderパターンの実装手順 #1【基礎実装】](https://qiita.com/suin/items/d8d4dc019d3beb428249)
* [PHP: Builderパターンの実装手順 #2【Fluent Interface実装】](https://qiita.com/suin/items/888f59851f940ee97c3a) `この投稿`
* [PHP: Builderパターンの実装手順 #3【Immutable Builder実装】](https://qiita.com/suin/items/7594e8984efc96594cee)
* [PHP: Builderパターンの実装手順 #4【Builder経由での生成を強制する】](https://qiita.com/suin/items/feafae06c644f871f7b8)