データベース#
DATABASE_DSN を設定すると Db レイヤーが自動で DI コンテナに登録され、ページ/コンポーネントが Database を型で受け取れるようになります。
DATABASE_DSN=mysql:host=127.0.0.1;dbname=app;charset=utf8mb4
DATABASE_USER=app
DATABASE_PASSWORD=secret
DATABASE_TIMEOUT=5
DATABASE_READ_TIMEOUT=10DSN はそのまま PDO に渡されます(%placeholder% 展開なし)。 SQLite は 絶対パスが必要です(相対パスはプロセスの cwd 基準で解決される)。
DATABASE_DSN=sqlite:/srv/app/var/app.db使い方#
<?php
use Polidog\Relayer\Db\Database;
use Polidog\Relayer\Router\Component\PageComponent;
use Polidog\UsePhp\Runtime\Element;
final class UserPage extends PageComponent
{
public function __construct(private readonly Database $db) {}
public function render(): Element
{
$user = $this->db->fetchOne(
'SELECT id, name FROM users WHERE id = :id',
['id' => 42],
);
return <h1>{$user['name']}</h1>;
}
}メソッド: fetchAll() / fetchOne() / fetchValue() / perform() / insert() / update() / delete() / lastInsertId() / transactional()。
Database エイリアスは常に CachingDatabase(リクエストスコープの読み取りメモ化)に解決され、dev ではプロファイラ用に TraceableDatabase でラップされます。
リクエストスコープキャッシュ#
1 ページは複数のコンポーネントから組み立てられ、それぞれが同じ行を引きにくる(ログイン中のユーザー、設定の 1 レコード、…)ことがよくあります。素朴に書けば 1 リクエストで同一 SQL の往復が N 回発生するため、 Database のデフォルト実装は そのリクエストの間だけ 読み取り結果をメモ化します。
挙動の要点:
- メモ化されるのは 読み取り 3 種だけ(
fetchAll/fetchOne/
fetchValue)。それぞれ独立した連想配列で、互いに混ざりません。
- キャッシュキーは
SQL 文字列 + シリアライズしたパラメータ。同じ SQL
でもパラメータが違えば別エントリです。
- 寿命は 1 リクエスト。プロセス内配列でしかなく、永続化もリクエスト間
共有もしません。TTL もありません。次のリクエストではゼロから。
- 書き込み系(
perform/insert/update/delete/
transactional)は 発行前にキャッシュを丸ごとフラッシュします。「読む → 書く → また読む」で自分の書き込みが反映された値を返すための単純で安全な選択です(テーブル単位の精緻な無効化はしません)。
dev では CachingDatabase が最外殻のデコレータなので、ヒットしたクエリは TraceableDatabase まで届きません。 プロファイラ のタイムラインでは実際の往復が db.query スパンとして並び、節約された方は db.cache_hit イベントのピンとして見えます。
例: 同じページの 2 つのコンポーネントが現在ユーザーを引く。SQL もパラメータも同一なので、2 回目は往復ゼロ(db.cache_hit)。
// HeaderComponent.php
$me = $this->db->fetchOne('SELECT id, name FROM users WHERE id = :id', ['id' => $uid]);
// SidebarComponent.php — 同一リクエスト内なら DB に行かない
$me = $this->db->fetchOne('SELECT id, name FROM users WHERE id = :id', ['id' => $uid]);パラメータが違えば別エントリなので往復は走ります。
$this->db->fetchOne('SELECT * FROM posts WHERE id = :id', ['id' => 1]); // 往復
$this->db->fetchOne('SELECT * FROM posts WHERE id = :id', ['id' => 2]); // 往復(別キー)
$this->db->fetchOne('SELECT * FROM posts WHERE id = :id', ['id' => 1]); // ヒット書き込みは発行前に 全エントリをフラッシュします。「読む → 書く → また読む」で必ず自分の書き込みが見えます。
$user = $this->db->fetchOne('SELECT * FROM users WHERE id = :id', ['id' => 42]);
$this->db->update('users', ['name' => 'Bob'], ['id' => 42]); // ここで cache flush
// 同じ SQL でも再度往復が走り、'Bob' を返す
$user = $this->db->fetchOne('SELECT * FROM users WHERE id = :id', ['id' => 42]);transactional() も入口でフラッシュ。トランザクション中の SELECT は新しいキャッシュに積まれ、コミット後の読み取りからも使えます(ロールバックされてもそのキャッシュは残るので、ロールバック直後に古い値を信じるコードを書かないこと)。
cross-request の HTTP キャッシュとは別物です。ブラウザ/CDN に向けたページ単位の Cache-Control / ETag は HTTP キャッシュと ETag を参照。
単一テーブル DML(insert / update / delete)#
自前で SQL を書くまでもない、単純な 1 行 INSERT と等価条件の UPDATE / DELETE のための薄いシュガーです。内部でパラメータ化した文を組み立てて perform() に流すので、キャッシュ・トレースなどのデコレータ挙動はそのまま効きます。
| メソッド | シグネチャ | 戻り値 |
|---|---|---|
insert | insert(string $table, array $data) | lastInsertId()(無ければ空文字) |
update | update(string $table, array $set, array $where) | 影響行数 |
delete | delete(string $table, array $where) | 影響行数 |
$id = $this->db->insert('users', ['name' => 'Alice', 'email' => '[email protected]']);
$this->db->update('users', ['name' => 'Bob'], ['id' => $id]);
$this->db->delete('users', ['id' => $id]);$where は等価条件のみで AND 結合。値が null のキーは IS NULL に変換されます(col = ? に null を束縛しても SQL では決して一致しない静かな取りこぼしを防ぐため)。update() / delete() の $where は空不可——全行を対象にする更新・削除は perform() で明示的に書きます。insert() の $data と update() の $set も空不可です。
識別子(テーブル名・カラム名)は素の名前のみ(^[A-Za-z_][A-Za-z0-9_]*$、$table は schema.table 可)。値は常にバインドされます。これを超えるもの——JOIN・OR・IN・式・ RETURNING・upsert・要クォートの予約語識別子——は perform() で生 SQL を書きます。違反時(ドライバ失敗・不正な識別子・空配列)は DatabaseException。
このサイトのストレージ#
ドキュメント本体は DATABASE_DSN の Db レイヤーではなく、独自の DocStore を使っています。理由は Turso(libSQL)に HTTP API 経由で接続し、ローカルでは pdo_sqlite にフォールバックするため。 SQLite の FTS5(trigram トークナイザ)で日本語・英語の部分一致検索を実現しています。記事は Turso を唯一の正として保持し、bin/docs CLI ($EDITOR)で直接編集します。詳細は デプロイ。
変更履歴 (5)
- 本文を更新 (+26 −0)
- 「リクエストスコープキャッシュ」を追加 (+25 −0)
- 「自動配線」を「自動で DI コンテナに登録」に。直訳の配線(wiring)が不明瞭なため明確化
- バージョン差分表記(vX.Y.Z 追加/破壊的変更/依存バージョン注記)を削除し現在形に整理
- 新規作成