-
Notifications
You must be signed in to change notification settings - Fork 0
Models
Every model extends Model and declares its table name:
<?php
class ArticleModel extends Model
{
protected static string $table = 'articles';
protected static bool $timestamps = true; // auto created_at / updated_at
}Naming convention: model classes and files use the
XxxModelsuffix (e.g.TodoModel.php→class TodoModel) to avoid ambiguity with the table name or with unrelated classes. The autoloader matches the file name to the class name exactly, so renaming one always requires renaming the other.
// ── Read ─────────────────────────────────────────────────────────────
ArticleModel::all() // All records
ArticleModel::all('title', 'DESC') // With custom order
ArticleModel::find(5) // By ID → array | null
ArticleModel::where('active', 1) // Simple filter
ArticleModel::where('views', '>', 100) // With operator (=, !=, <, >, <=, >=, LIKE)
ArticleModel::first() // First record
ArticleModel::count() // Total records
// Pagination
$result = ArticleModel::paginate(page: 1, perPage: 10);
// $result['data'] → records for this page
// $result['total'] → total records in the table
// $result['pages'] → total number of pages
// $result['current'] → current page
// ── Write ────────────────────────────────────────────────────────────
$id = ArticleModel::create(['title' => 'Hello']); // → int (inserted ID)
ArticleModel::update(5, ['title' => 'Updated']); // → bool
ArticleModel::delete(5); // → boolIf
$timestamps = true,create()fillscreated_at/updated_atautomatically, andupdate()refreshesupdated_at.
Column and table identifiers are validated (rejected if not a simple [a-zA-Z_][a-zA-Z0-9_]* pattern) and quoted automatically according to the active driver — backticks for MySQL, double quotes for PostgreSQL and SQLite. You never need to escape them yourself, and dynamic column names (e.g. from user input) are rejected rather than interpolated unsafely.
Set DB_DRIVER in .env to mysql, pgsql, or sqlite. For SQLite, also set DB_PATH to the .sqlite file's location (created automatically on first write if it doesn't exist).
DB_DRIVER=mysql # mysql | pgsql | sqlite
DB_PATH= # only used when DB_DRIVER=sqliteThe connection is lazy — if no route ever calls a model, no connection is ever opened.
For anything the generic API doesn't cover, drop into Database::connect() directly inside your model:
class TodoModel extends Model
{
protected static string $table = 'todo';
public static function allOrdered(): array
{
$table = static::quoteIdent(static::$table);
$pdo = Database::connect();
$stmt = $pdo->query("SELECT * FROM $table ORDER BY completed ASC, created_at DESC");
return $stmt->fetchAll();
}
}Use static::quoteIdent() (inherited from Model) for any identifier you interpolate, so your custom queries stay driver-agnostic and safe.
See Optional Connections.