-
Notifications
You must be signed in to change notification settings - Fork 0
Query Builder Integration
A Model is a thin wrapper around a DatabaseInterface. Every call you make to the model first checks for a method on the model itself; anything else flows downward through __call to the Database and then to the underlying query builder.
The net effect: every method on DatabaseInterface and on QueryBuilderInterface is callable directly on a model, and fluent chains stay rooted in the model.
$posts = new Posts();
$rows = $posts
->select('id', 'title') // QueryBuilder
->where('status', '=', 'published') // QueryBuilder
->andWhere('author_id', '=', 7) // QueryBuilder
->orderBy('id', 'DESC') // QueryBuilder
->limit(10) // QueryBuilder
->offset(0) // QueryBuilder
->read() // Model — terminates the chain
->rows(); // DataMapperEvery builder call is forwarded through Database::__call and re-wrapped to return the model, so the chain reads naturally from top to bottom.
The query builder ships ~100 fluent methods. Below is a cheat-sheet of the most useful families — every signature is the same as on InitORM\Database\Facade\DB, which mirrors the full builder interface.
$model->select('id', 'name'); // SELECT id, name
$model->clearSelect(); // wipe a prior select chain
$model->selectAs('user.name', 'author_name'); // user.name AS author_name
$model->selectDistinct('email'); // SELECT DISTINCT email
$model->selectCount('id', 'total'); // COUNT(id) AS total
$model->selectCountDistinct('email'); // COUNT(DISTINCT email)
$model->selectMax('score'); $model->selectMin('score');
$model->selectAvg('score'); $model->selectSum('score');
$model->selectUpper('name'); $model->selectLower('name');
$model->selectLength('body');
$model->selectMid('body', 1, 100); $model->selectLeft('body', 50); $model->selectRight('body', 50);
$model->selectCoalesce('nickname', 'unknown', 'display');
$model->selectConcat(['first', 'last'], 'fullname');$model->from('users'); // FROM users
$model->from('users', 'u'); // FROM users AS u
$model->addFrom('posts', 'p'); // , posts AS p
$model->table('users'); // alias of from()
$model->join('posts', 'posts.user_id = users.id', 'INNER');
$model->innerJoin('posts', 'posts.user_id = users.id');
$model->leftJoin('posts', 'posts.user_id = users.id');
$model->rightJoin('posts', 'posts.user_id = users.id');
$model->leftOuterJoin('posts', 'posts.user_id = users.id');
$model->rightOuterJoin('posts', 'posts.user_id = users.id');
$model->selfJoin('posts', 'posts.user_id = users.id');
$model->naturalJoin('posts');$model->where('status', '=', 'published');
$model->andWhere('author_id', '=', 7);
$model->orWhere('featured', '=', 1);
$model->whereIn('id', [1, 2, 3]);
$model->whereNotIn('id', [4, 5]);
$model->orWhereIn(...); $model->andWhereIn(...);
$model->orWhereNotIn(...); $model->andWhereNotIn(...);
$model->whereIsNull('deleted_at');
$model->whereIsNotNull('published_at');
$model->orWhereIsNull(...); $model->andWhereIsNull(...);
$model->orWhereIsNotNull(...); $model->andWhereIsNotNull(...);
$model->between('score', 0, 100);
$model->notBetween('score', 0, 100);
$model->orBetween(...); $model->andBetween(...);
$model->orNotBetween(...); $model->andNotBetween(...);
$model->like('title', 'hello'); // LIKE %hello%
$model->startLike('title', 'hello'); // LIKE hello%
$model->endLike('title', 'hello'); // LIKE %hello
$model->notLike(...);
$model->orLike(...); $model->andLike(...);
$model->orNotLike(...); $model->andNotLike(...);
$model->regexp('username', '^[a-z]+$');
$model->soundex('name', 'Smith');
$model->findInSet('tags', 'php');
$model->notFindInSet('tags', 'spam');$model->groupBy('status');
$model->groupBy('status', 'author_id');
$model->having('count', '>', 10);
$model->orderBy('id', 'DESC');
$model->orderBy('created_at', 'ASC');
$model->limit(10);
$model->offset(20);// Sub-query as a value:
$model->whereIn('author_id', $model->subQuery(function ($sub) {
$sub->select('id')->from('users')->where('active', '=', 1);
}));
// Grouped WHERE:
$model->where('status', '=', 'published')
->group(function ($g) {
$g->where('author_id', '=', 7)
->orWhere('featured', '=', 1);
});
// WHERE status = :status AND (author_id = :author_id OR featured = :featured)use InitORM\QueryBuilder\RawQuery;
$model->select($model->raw("CONCAT(name, ' ', surname) AS fullname"))
->where($model->raw('status = 1 OR status = 0'))
->read()
->rows();Never embed unsanitised user input into a raw fragment. raw() opts out of every safety net the builder provides.
Builder methods accumulate state on the underlying query builder. CRUD calls (read, update, delete, etc.) consume that state and then reset it — so the next CRUD call starts clean:
$posts->where('id', '=', 5)->update(['title' => 'X']); // uses the WHERE
$posts->update(['title' => 'Y'], ['id' => 6]); // clean slate; explicit conditionsIf you need two independent queries against the same connection (e.g. a parent query and a sub-query that are not chained), spin off a fresh builder via the Database:
$reports = $posts->getDatabase()->withFreshBuilder();
$reports->read('events')->rows();
// $posts is unaffected.A "latest 10 published posts from active authors, with the author's name and post count" query:
$rows = $posts
->select('p.id', 'p.title', 'p.created_at')
->selectAs('u.name', 'author_name')
->from('posts', 'p')
->innerJoin('users AS u', 'p.author_id = u.id')
->where('p.status', '=', 'published')
->andWhere('u.active', '=', 1)
->orderBy('p.created_at', 'DESC')
->limit(10)
->read()
->rows();Notice that read() did not need a $selectors or $conditions argument — the builder chain already configured everything. read() just compiles, executes, and resets.
The model hydrates read() into $entity instances. To opt out of that and get raw data:
$rawRows = $posts->read()->asAssoc()->rows(); // array<string, mixed>[]
$rawObjs = $posts->read()->asObject()->rows(); // stdClass[]
$rawIter = $posts->read()->asLazy(); // PDOStatement-backed iteratorasAssoc() / asObject() / asLazy() are methods on the DataMapper and override the fetch mode set by the model.
Calling a method that doesn't exist anywhere in the chain raises BadMethodCallException:
try {
$posts->thisIsNotARealMethod();
} catch (\BadMethodCallException $e) {
echo $e->getMessage(); // 'Method "App\Model\Posts::thisIsNotARealMethod" does not exist.'
echo $e->getPrevious()::class; // 'InitORM\Database\Exceptions\DatabaseException'
}The previous exception is the DatabaseException the underlying Database layer raised — useful for debugging when you mistype a builder method.
-
Architecture — how
__callwalks the layers. - CRUD Operations — how builder state interacts with CRUD calls.
- [Database wiki — Query Builder] — the exhaustive method-by-method reference.
- [QueryBuilder repo] — the canonical builder source.
InitORM ORM · MIT · maintained by Muhammet ŞAFAK · part of the InitORM stack
Getting Started
Models
Entities
Cross-Cutting
Reference
Upgrading
Project