この記事はBEAR.Sunday Advent Calendar 2019の18日目の後出し記事です。
BEAR.SundayとDBの間にあるものと便利機能を調べました。
BEAR.SundayとSQL
BEAR.Sundayのチュートリアル2でも紹介されていますが、以下のように設定することで、生のSQLを記述したファイルを扱えるようになります。
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で使えるようにしてくれています。 -
SqlQueryModule
はSQLを実行可能なfunction objectに変換して実行してくれます。 -
QueryLocatorModule
は実行対象のSQLファイルを探してくれます。
SELECT
SELECT文を書いてパラメータを渡してクエリを実行するのもQuery
アノテーションを使うことで簡単に実現できます。
一行取得するときは、
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;
}
}
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
アノテーションを使ってコンストラクタで設定もできます。
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;
}
// 略
}
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
アノテーションを使ってコンストラクタで設定します。
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;
}
}
DELETE FROM hoge_detail WHERE hoge_detail.hoge_id in (:hoge_ids);
業務要件が込み入ってきた場合に、複数のテーブルへの書き込み/削除をいっぺんにやりたくなることは、往々にしてあることです。
それぞれのテーブルに対する操作を別々のファイルにしても良いのですが、ある程度正規化されている場合には呼び忘れが発生する可能性があります。
PL/SQLとまではいきませんが、BEAR.Sundayでも複数のDML(CUD)を1ファイルで記述できます。
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;
}
}
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に紹介されている通り、クエリビルダーも使えます。
と、ここまで書いてふと、リポジトリパターンで言われている利点がリポジトリなしで実現できているのではないかと思ったのです。
リポジトリによって隠蔽されたSQLのオプティマイズや挙動を調べるのに、PHPならvar_dump($queryBuilder->getQuery()->getSQL());
、Railsならrecord.to_sql
のような「実際にどんなSQLが発行されているか確認する」ということをしていませんか?(私はしょっちゅうしています)
はて、隠蔽されたことで果たして本当に開発が楽になっているのだろうか。何のためのリポジトリという道具なのだろうか。
そんなことを改めて考えさせられるきっかけになったBEAR.SundayとSQLについてでした。