BEAR.SundayとSQLと

この記事は[BEAR.Sunday Advent Calendar 2019](https://qiita.com/advent-calendar/2019/bear-sunday)の18日目の後出し記事です。
BEAR.SundayとDBの間にあるものと便利機能を調べました。

## BEAR.SundayとSQL
[BEAR.Sundayのチュートリアル2](https://bearsunday.github.io/manuals/1.0/ja/tutorial2.html)でも紹介されていますが、以下のように設定することで、生のSQLを記述したファイルを扱えるようになります。


```php:src/Module/AppModule.php
class AppModule extends AbstractAppModule
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $appDir = $this->appMeta->appDir;
        require_once $appDir . '/env.php';
        $this->install(
            new AuraSqlModule(
                (string) getenv('DB_DSN'),
                (string) getenv('DB_USER'),
                (string) getenv('DB_PASS')
            )
        );
        $sqlDir = $appDir . '/var/sql';
        $this->install(new SqlQueryModule($sqlDir));
        $this->install(new QueryLocatorModule($sqlDir));

        // 略
    }
```
- `AuraSqlModule`はDBへの接続情報をインジェクトしてくれる`AuraSql`をRay.DIで使えるようにしてくれています。
    - https://github.com/ray-di/Ray.AuraSqlModule
- `SqlQueryModule`はSQLを実行可能なfunction objectに変換して実行してくれます。
    - https://github.com/ray-di/Ray.QueryModule
- `QueryLocatorModule`は実行対象のSQLファイルを探してくれます。
    - https://github.com/koriym/Koriym.QueryLocator

### SELECT
SELECT文を書いてパラメータを渡してクエリを実行するのも`Query`アノテーションを使うことで簡単に実現できます。
一行取得するときは、

```php:src/Resource/App/Hoge.php
namespace MyAPP\Api\Resource\App;

use BEAR\Resource\Annotation\JsonSchema;
use BEAR\Resource\ResourceObject;
use Ray\Query\Annotation\Query;

class Hoge extends ResourceObject
{
    /**
     * @JsonSchema(schema="hoge.json")
     * @Query("hoge_by_id?hoge_id={hogelId}", templated=false, type="row")
     */
    public function onGet(string $hogelId) : ResourceObject
    {
        return $this;
    }
}

```

```sql:var/sql/hoge.sql
SELECT
    h.id,
    hd.name,
    hd.name_latin,
    hd.postal_code,
    hd.address,
    hd.email,
    hd.telephone
FROM hoge AS h
    INNER JOIN hoge_detail AS hd
    ON h.id = hd.hoge_id
WHERE h.id = :hoge_id


```
のようにかけます。
複数行取得する場合は`Named`アノテーションを使ってコンストラクタで設定もできます。

```php:src/Resource/App/HogeList.php
namespace MyAPP\Api\Resource\App;

use BEAR\Resource\Annotation\JsonSchema;
use BEAR\Resource\ResourceObject;
use Koriym\HttpConstants\StatusCode;
use Ray\Di\Di\Named;
use Ray\Query\RowListInterface;

class HogeList extends ResourceObject
{
    /**
     * @var RowListInterface
     */
    private $hogeList;

    /**
     * @Named("hogeList=hoge_list")
     */
    public function __construct(
        RowListInterface $hogeList
    ) {
        $this->hogeList = $hogeList;
    }
    // 略
}

```
```sql:var/sql/hoge_list.sql
SELECT h.id, hd.name, hd.age, hd.nationality
   FROM hoge AS h
   INNER JOIN hoge_detail AS hd
   ON h.id = hd.hoge_id
WHERE h.id = :hoge_id
```

### INSERT/UPDATE/DELETE
複数行のSELECT同様、`Named`アノテーションを使ってコンストラクタで設定します。

```php:src/Resource/App/HogeDetail.php
namespace MyApp\Api\Resource\App;

use BEAR\Resource\ResourceObject;
use Ray\Di\Di\Named;

class HogeDetail extends ResourceObject
{
    /**
     * @var callable
     */
    private $deleteHogeDetail;

    /**
     * @Named("deleteHogeDetail=delete_hoge_detail")
     */
    public function __construct(
        callable $deleteHogeDetail
    ) {
        $this->deleteHogeDetail = $deleteHogeDetail;
   }

    public function onPost() : ResourceObject
    {
        // 略
        ($this->deleteHogeDetail)(
            [
                'hoge_ids' => implode(',', $hogeIds)
            ]
        );

        $this->code = 204;

        return $this;
    }
}

```
```sql:var/sql/delete_hoge_detail.sql
DELETE FROM hoge_detail WHERE hoge_detail.hoge_id in (:hoge_ids);
```

業務要件が込み入ってきた場合に、複数のテーブルへの書き込み/削除をいっぺんにやりたくなることは、往々にしてあることです。
それぞれのテーブルに対する操作を別々のファイルにしても良いのですが、ある程度正規化されている場合には呼び忘れが発生する可能性があります。
PL/SQLとまではいきませんが、BEAR.Sundayでも複数のDML（CUD）を1ファイルで記述できます。

- https://github.com/ray-di/Ray.QueryModule/pull/9

```php:src/Resource/App/HogeDetail.php
namespace MyApp\Api\Resource\App;

use BEAR\Resource\ResourceObject;
use Ray\Di\Di\Named;

class HogeDetail extends ResourceObject
{
    /**
     * @var callable
     */
    private $deleteHogeDetailAndMemo;

    /**
     * @Named("deleteHogeDetailAndMemo=delete_hoge_detail_and_hoge_memo")
     */
    public function __construct(
        callable $deleteHogeDetail,
    ) {
        $this->deleteHogeDetailAndMemo = $deleteHogeDetailAndMemo;
   }

    public function onPost() : ResourceObject
    {
        // 略
        ($this->deleteHogeDetailAndMemo)(
            [
                'hoge_ids' => implode(',', $hogeIds)
            ],
            [
                'hoge_ids' => implode(',', $hogeIds)
            ]
        );

        $this->code = 204;

        return $this;
    }
}

```
```sql:var/sql/delete_hoge_detail_and_hoge_memo.sql
DELETE FROM hoge_detail WHERE hoge_detail.hoge_id in (:hoge_ids);
DELETE FROM hoge_memo WHERE hoge_memo.hoge_id in (:hoge_ids);
```

## BEAR.Sundayとリポジトリパターン

[BEAR.Sunday チュートリアル1](https://bearsunday.github.io/manuals/1.0/ja/tutorial.html)に紹介されている通り、クエリビルダーも使えます。

と、ここまで書いてふと、リポジトリパターンで言われている利点がリポジトリなしで実現できているのではないかと思ったのです。

リポジトリによって隠蔽されたSQLのオプティマイズや挙動を調べるのに、PHPなら`var_dump($queryBuilder->getQuery()->getSQL());`、Railsなら`record.to_sql`のような「実際にどんなSQLが発行されているか確認する」ということをしていませんか？（~~私はしょっちゅうしています~~）
はて、隠蔽されたことで果たして本当に開発が楽になっているのだろうか。何のためのリポジトリという道具なのだろうか。

そんなことを改めて考えさせられるきっかけになったBEAR.SundayとSQLについてでした。