Db — TableRepository + Query
TableRepository doet CRUD op gewone tabellen (members, orders, …); Query is de fluent builder erachter. Onveilige veldnamen worden geweigerd, massa-delete/update zonder filter throwt — gebruik ->deleteAll() als je dat echt wil. Alle voorbeelden hier draaien tegen een SQLite-fixture die we per snippet opbouwen.
CRUD-basics: insert, find, update, delete
find($id) is een shortcut rond query()->where(pk, $id)->first().
$members = new TableRepository($pdo, 'members');
$id = $members->insert(['email' => 'jan@x', 'name' => 'Jan', 'age' => 28, 'country' => 'nl', 'active' => 1]);
$row = $members->find($id);
$members->update($id, ['name' => 'Jan Jansen']);
$members->delete($id);
return [
'inserted_id' => $id,
'after_delete' => $members->find($id), // null — net gewist
];Array
(
[inserted_id] => 6
[after_delete] =>
)
Query builder — where / whereIn / orderBy
where(col, value) is shortcut voor =. Met operator als 2e arg krijg je <, >=, !=, …
$members = new TableRepository($pdo, 'members');
return $members->query()
->where('active', 1)
->where('age', '>=', 18)
->whereIn('country', ['nl', 'be'])
->orderBy('name')
->limit(10)
->get();Array
(
[0] => Array
(
[id] => 1
[email] => anna@x
[name] => Anna
[age] => 25
[country] => nl
[active] => 1
)
[1] => Array
(
[id] => 5
[email] => eva@x
[name] => Eva
[age] => 35
[country] => nl
[active] => 1
)
)
Groeperen — nest()/unnest() of whereGroup(closure)
Twee stijlen, dezelfde uitkomst. nest() opent een sub-tree, unnest() sluit hem.
$members = new TableRepository($pdo, 'members');
// Stijl 1: nest/unnest
$a = $members->query()
->where('active', 1)
->nest()
->where('age', '>=', 30)
->orWhere('country', 'be')
->unnest()
->toSql();
// Stijl 2: closure
$b = $members->query()
->where('active', 1)
->whereGroup(fn($q) => $q->where('age', '>=', 30)->orWhere('country', 'be'))
->toSql();
return ['nest' => $a[0], 'closure' => $b[0]];Array
(
[nest] => SELECT * FROM `members` WHERE `active` = ? AND (`age` >= ? OR `country` = ?)
[closure] => SELECT * FROM `members` WHERE `active` = ? AND (`age` >= ? OR `country` = ?)
)
paginate(page, perPage)
Returnt items + meta (total, totalPages, hasMore).
$members = new TableRepository($pdo, 'members');
return $members->query()->orderBy('id')->paginate(page: 1, perPage: 2);
// ['items', 'page', 'perPage', 'total', 'totalPages', 'hasMore']Array
(
[items] => Array
(
[0] => Array
(
[id] => 1
[email] => anna@x
[name] => Anna
[age] => 25
[country] => nl
[active] => 1
)
[1] => Array
(
[id] => 2
[email] => bert@x
[name] => Bert
[age] => 17
[country] => be
[active] => 1
)
)
[page] => 1
[perPage] => 2
[total] => 5
[totalPages] => 3
[hasMore] => 1
)
Safety: massa-delete zonder filter throwt
Tegen het per ongeluk wissen van een hele tabel. Gebruik deleteAll() als je dat echt wil.
$members = new TableRepository($pdo, 'members');
try {
$members->query()->delete(); // geen where → exception
return 'unexpected';
} catch (\LogicException $e) {
return ['safety' => $e->getMessage()];
}Array
(
[safety] => delete() zonder filter is niet toegestaan — gebruik deleteAll() als je dit echt bedoelt.
)
columns() whitelist
Ongedefinieerde kolommen worden gericht geweigerd — nuttig wanneer je vanuit een form direct schrijft.
$members = (new TableRepository($pdo, 'members'))->columns(['email', 'name']);
try {
$members->insert(['email' => 'x', 'name' => 'X', 'rogue' => 1]);
return 'unexpected';
} catch (\InvalidArgumentException $e) {
return ['blocked' => $e->getMessage()];
}Array
(
[blocked] => Kolom 'rogue' staat niet in de whitelist van tabel 'members'.
)
Events — BeforeInsert kan data muteren
Listeners op de PSR-14 EventDispatcher kunnen $data herschrijven (audit-velden, hashing, etc.).
$dispatcher = new class implements \Psr\EventDispatcher\EventDispatcherInterface {
public function dispatch(object $event): object {
if ($event instanceof \Framework\Events\Db\BeforeInsertEvent) {
$d = $event->getData();
$d['name'] = strtoupper($d['name']);
$event->setData($d);
}
return $event;
}
};
$members = new TableRepository($pdo, 'members', events: $dispatcher);
$id = $members->insert(['email' => 'a@b', 'name' => 'jan', 'age' => 20, 'country' => 'nl', 'active' => 1]);
return ['name_after_event' => $members->find($id)['name']]; // 'JAN'Array
(
[name_after_event] => JAN
)
DynamicWriter — schrijven naar DynamicItems
Voor DynamicItems is er een aparte writer. create/update/setActive/delete + auto-cache-warm.
$writer = new \Framework\Dynamic\DynamicWriter($pdo, $structure, $cacheWarmer);
$id = $writer->create('contact', [
'name' => 'Jan',
'email' => 'jan@x.nl',
], language: 'nl');
$writer->update($id, ['phone' => '06-1234']);
$writer->setActive($id, false);
$writer->delete($id);tests/Framework/Dynamic/DynamicWriterTest.php al. Bekijk daar de roundtrip.