Index
# mmo.pdo — overview for AI code generation `mmo.pdo` is Marc Mouret's SQL storage package for MMOFramework (PHP package `mmo.pdo`, PHP namespace `MMO\pdo`). It is a drop-in alternative to `mmo.core`'s `JsonRepository` (and to a Mongo-backed repository, if one exists in this application): `PDORepository` implements the same `iRepository` contract — collections, `Query`, `\MMO\commit()` — but backed by SQLite or MySQL through PDO instead of a JSON file. Anything that already knows how to work with entities, collections and queries in `mmo.core` needs nothing new to work against a `PDORepository`; `MMO\core\controllers\RepositoryController` serves one over HTTP exactly like any other. Under the hood the package is its own small SQL layer: `SqliteHandler`/`MysqlHandler` (one connection plus a dialect), a `helpers\Table` (a table's columns, and a generated `PDOEntity` subclass for its rows), a `statements\*` query builder (SELECT/INSERT/UPDATE/DELETE/CREATE/DROP), and `PDOPrinter`/`PDOExporter` that turn a statement and an entity's values into real SQL with bound parameters. Most application code only ever touches the repository/collection/query layer; the SQL layer underneath is there when you need to drop down to it. ## The five things that matter most 1. **It is an `iRepository`, not a new API to learn.** Build a `Table` (or let `PDORepository::setTables()` build one from a column array), register it, then use `getCollection()`/`query()`/`createEntity()`/ `\MMO\commit()` exactly as with any other repository. See [Repository](../Repository.md). 2. **`AUTO_INCREMENT` ids are negative placeholders until the row is inserted.** A newly created entity on such a column gets `-1`, `-2`, … so it has *some* id from the moment it is created (the framework requires one) — `commitEntity()` swaps it for the database's real id right after the `INSERT`. `PDORepository::IsPendingId()` tells the two apart. See [Repository → Ids](../Repository.md#ids). 3. **A `Query`'s field list is schema-driven, and that cuts both ways.** Requesting a property no table column has (`status`, `uid`, `metadata`, …) is silently dropped from the `SELECT`. Filtering on one, or on any other column the table does not have, is an **error** (`eNotFound`) — never silently ignored, because dropping a filter would return more rows than were asked for. Give the table the column (e.g. add a `status` column before using `Query::listOnly()`), or filter on columns it has. See [Repository → Fields versus filters](../Repository.md#fields-versus-filters) and [Troubleshooting](../Troubleshooting.md). 4. **A nested-path filter (`['author/name' => 'marc']`) is NOT turned into a join.** Nothing in the package knows which columns relate two tables (that is what `helpers\Liaison` was meant to describe, and it was never finished — it is dead code today). Build the join yourself with `SelectStatement::join()`/`on()` and filter on the joined alias's column instead. See [Joins](../Joins.md). 5. **Never quote a `{placeholder}` in a raw statement template.** `name = "{name}"` renders the literal text `"?"` while the value is still bound, so the bind count and the `?` count drift apart and PDO fails with `column index out of range`. Write `name = {name}`, unquoted; the printer decides on its own whether the value becomes a `?` or literal SQL. See [Statements](../Statements.md) and [Troubleshooting](../Troubleshooting.md). ## Recommended reading order The 7 files below are the detailed reference — this page is just the map. Read in this order the first time; after that, jump straight to whichever one covers the task at hand. 1. **[Repository](../Repository.md)** — start here. Setting up a `PDORepository` (inline or from configuration), reading (`query()`, pagination, `$total`), writing (`createEntity`, `\MMO\commit()`), and how ids are handled for each of the three primary-key shapes (auto-increment, single, composite). This is the layer almost all application code touches. 2. **[Tables](../Tables.md)** — declaring a `Table` (inline, as a subclass, or via configuration), the column mini-language (`PRIMARY`, `NOT NULL`, `DEFAULT x`, …), and — importantly — how columns become entity properties on a *generated* `PDOEntity` subclass, versus when to write your own entity class by hand (as soon as it needs behaviour). Read this before declaring any real table. 3. **[Handlers](../Handlers.md)** — `SqliteHandler`/`MysqlHandler`: connecting, transactions (`do()` is the one to use), and schema introspection (`table()` on an undeclared name reads the live database). Needed whenever you are wiring up a connection or reasoning about transaction boundaries. 4. **[Filters](../Filters.md)** — reference for how a core `iFilter` (the same filter structure used everywhere in the framework) becomes a SQL `WHERE`: the operator table, `null` handling (`IS`/`IS NOT`, never `=`), and what is *not* supported (`$regex`, `$has`, `$text`, nested paths). 5. **[Statements](../Statements.md)** — reference for the query builder itself: the `{path:type}` placeholder syntax, which types bind versus render as text, and the SELECT/INSERT/UPDATE/DELETE/CREATE statement classes. Needed only when you drop below the repository layer to write a raw statement or a custom query. 6. **[Joins](../Joins.md)** — reference for joining tables explicitly (`join()`/`on()`), since the repository layer does not do this for you (see point 4 above). Skim once, come back to it when a query actually needs a join. 7. **[Troubleshooting](../Troubleshooting.md)** — reference, not reading-order material. A symptom → cause → fix list (both drivers, then SQLite-specific, then MySQL-specific). Search it by the error message. ## Two drivers, uneven coverage `SqliteHandler` and `MysqlHandler` render through the same `PDOPrinter`/`Table`/`Statement` pipeline and are meant to behave identically from the caller's side, but the test suite only actually *executes* SQLite (a temporary file needs no setup); MySQL is exercised by rendering tests only, so a MySQL-only bug is the kind most likely to still be there. Prefer `SqliteHandler::MEMORY` for anything that does not specifically need MySQL — tests, demos, quick scripts.