Release:
0.1.0-beta3Β |ΒCHANGELOG.md
The data & persistence layer for the Waffle Framework (RFC-022). Built for FrankenPHP resident-worker mode: a warm connection pool, a backend-agnostic query AST, parameterized SQL / Firestore compilers, a property-hook hydrator, and a stateless SQL migration runner. No stateful ORM, no identity map, no change tracking β a row becomes an immutable value object and nothing more.
composer require waffle-commons/dataRequires PHP 8.5+ and ext-pdo. Depends only on waffle-commons/contracts (plus PSR + PHP core).
| Class | Role |
|---|---|
Waffle\Commons\Data\Connection\PDOConnectionPool |
final pool of reusable PDO connections (ConnectionPoolInterface + ResettableInterface). Ping-before-dispense (SELECT 1), transparent reconnect, per-connection statement cache, reset() rolls back dangling transactions between requests. |
Waffle\Commons\Data\Query\Criteria |
Static factory for predicates: eq/neq/gt/gte/lt/lte/like/in/notIn. |
Waffle\Commons\Data\Query\Query |
Immutable, copy-on-write query AST (select / from / where / orderBy / limit / offset). Pure representation β knows nothing about any backend. |
Waffle\Commons\Data\Query\Operator / Direction |
enum operators (=, <>, >, β¦, IN, LIKE) and sort directions (ASC / DESC). |
Waffle\Commons\Data\Compiler\SQLCompiler / SQLWriteCompiler |
Compile a Query (reads) or a mapped entity row (INSERT/UPDATE/DELETE) into parameterized statements (? placeholders, injection-safe) for a chosen SQLDialect. |
Waffle\Commons\Data\Compiler\SQLDialect |
enum MySQL / MariaDB / SQLite / MSSQL / PostgreSQL / Oracle β identifier quoting + pagination grammar. |
Waffle\Commons\Data\Compiler\FirestoreCompiler |
Compiles a Query into a CompiledFirestoreQuery with path isolation; only equality is pushed server-side, ranges/ordering flag requiresInMemoryFilter. |
Waffle\Commons\Data\Compiler\FirestoreScope |
public(appId, collection) / private(appId, userId, collection) path scoping. |
Waffle\Commons\Data\Compiler\{Mongo,KeyValue,Cassandra,GraphQL}Compiler |
Per-backend SQR compilers: MongoDB filter documents, key-value GET/MGET plans, parameterised CQL, GraphQL query/mutation documents. |
Waffle\Commons\Data\Repository\β¦ |
Seven stateless WritableRepositoryInterface repositories β SQLRepository, FirestoreRepository (three auth guardrails), MongoRepository, CassandraRepository, KeyValueRepository, GraphQLRepository, JsonFileRepository β full CRUD through pure DataMapperInterface mappers. |
Waffle\Commons\Data\Driver\β¦ |
Live drivers: FirestoreRestClient, MongoDriverSession, RedisKeyValueClient, GraphQLExecutor (+ the injectable CQL port). Every backend failure is rethrown as a sanitized DatabaseException. |
Waffle\Commons\Data\Evaluation\InMemoryEvaluator |
Stateless fetch-then-filter evaluation (range/set/sort/offset) for backends with restricted server-side querying. |
Waffle\Commons\Data\Storage\JsonFileStore |
Atomic flat-file JSON store (read-modify-write under LOCK_EX). |
Waffle\Commons\Data\Hydrator\PropertyHookHydrator |
Maps a raw row onto an immutable DTO via its constructor; corrupt data is rejected by the DTO's PHP 8.5 set hooks as a ValidationExceptionInterface. |
Waffle\Commons\Data\Migration\MigrationRunner |
MigrationRunnerInterface β applies versioned *.sql files in order, tracked in waffle_migrations; each migration runs in its own transaction. |
Waffle\Commons\Data\Warmup\QueryWarmer |
DataWarmerInterface β pre-compiles named SQR trees into an atomic <?php return [β¦] artifact primed into OPcache (bin/waffle data:warmup, Beta-3). |
Waffle\Commons\Data\Exception\DatabaseException |
DatabaseExceptionInterface β wraps any backend failure, lifts the ANSI SQLSTATE from a PDOException. |
Waffle\Commons\Data\Exception\ValidationException |
ValidationExceptionInterface β a field-aware hydration/validation failure (surfaces as RFC 7807 422). |
use Waffle\Commons\Data\Connection\PDOConnectionPool;
$pool = new PDOConnectionPool(
factory: static fn (): \PDO => new \PDO($dsn, $user, $pass, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
]),
maxConnections: 8, // hard ceiling on simultaneously borrowed handles
pingQuery: 'SELECT 1', // liveness probe before dispensing
);
$connection = $pool->acquire(); // a healthy PDO, reconnected transparently if the socket died
// ... use $connection ...
$pool->release($connection); // return it to the idle set
$pool->reset(); // end-of-request: roll back stragglers, clear statement cacheuse Waffle\Commons\Data\Query\Query;
use Waffle\Commons\Data\Query\Criteria;
use Waffle\Commons\Data\Query\Direction;
use Waffle\Commons\Data\Compiler\SQLCompiler;
use Waffle\Commons\Data\Compiler\SQLDialect;
$query = Query::select('id', 'email')
->from('users')
->where(Criteria::eq('status', 'active'), Criteria::in('role', ['admin', 'editor']))
->orderBy('email', Direction::Ascending)
->limit(20);
$compiled = new SQLCompiler(SQLDialect::MySQL)->compile($query);
$compiled->sql; // 'SELECT `id`, `email` FROM `users` WHERE `status` = ? AND `role` IN (?, ?) ORDER BY `email` ASC LIMIT 20'
$compiled->parameters; // ['active', 'admin', 'editor']The same Query compiles to a Firestore payload via FirestoreCompiler with a FirestoreScope (public vs per-user path isolation).
use Waffle\Commons\Data\Hydrator\PropertyHookHydrator;
$hydrator = new PropertyHookHydrator(UserDto::class);
$user = $hydrator->hydrate(['id' => '42', 'email' => 'ada@example.com']); // UserDto
// A malformed value is rejected by UserDto's `set` hook as a ValidationExceptionInterface.MigrationRunner is wired into the console as bin/waffle db:migrate (see the console component). Programmatically:
use Waffle\Commons\Data\Migration\MigrationRunner;
$runner = new MigrationRunner(pool: $pool, config: $config);
$applied = $runner->run(static fn (string $version) => printf("applied %s\n", $version));
// $applied: list of versions applied this run ([] when already up to date)final readonly classfor every value object (CompiledQuery,CompiledFirestoreQuery,Comparison,Order,FirestoreScope).- Property Hooks β DTOs validate inside their own
sethooks; the hydrator never sees invalid state. (A hooked DTO isfinal class+public private(set), since hooked properties cannot bereadonly.) - Asymmetric visibility (
public private(set)) on the immutableQuerybuilder. - First-class callable syntax (
$this->method(...)) in the compilers;enumwith methods (Operator::isSetOperator());never-returning rejection helpers; typed class constants.
Production code under Waffle\Commons\Data may depend only on Waffle\Commons\Data\**, Waffle\Commons\Contracts\**, Psr\**, and @global + Psl\**. A forbidden use fails the build, not a reviewer β enforced by vendor/bin/mago guard (bundled into composer mago, zero baselines). Interfaces must be named *Interface, Exception\** classes must end in *Exception, and any Enum\** namespace may hold only enum declarations.
Contract-first, component-agnostic by construction: the data layer composes with the rest of the framework through waffle-commons/contracts, never directly through another component.
docker exec -w /waffle-commons/data waffle-dev composer testsThe full gate (composer mago && composer tests) runs format, lint, analyze, guard, and PHPUnit (β₯95% line coverage) with zero baselines.
MIT β see LICENSE.md.