Query builder
The query builder writes SQL for you. You chain methods that describe the query, and every value you pass is sent as a bound parameter, so it can't break out of the query.
Starting a query
In a model, $this->db is the connection. Anywhere else, use App::db():
use FloCMS\Core\App;
$posts = App::db()->table('posts')
->where('status', '=', 'published')
->orderBy('created_at', 'DESC')
->limit(10)
->get();
App::db() returns null when the database isn't configured in .env.
table() starts a new, independent query each time it's called, so you can build two queries at once:
$posts = $db->table('posts')->where('author_id', '=', $id);
$count = $db->table('comments')->where('author_id', '=', $id)->count();
$rows = $posts->get();
Getting results
| Method | Returns |
|---|---|
get() |
A list of rows, each an object: $post->title |
first() |
The first row as an object, or null |
count(), count('column') |
The number of matching rows |
paginate($page, $perPage) |
One page of rows and the page numbers. See models. |
toSql() |
[$sql, $bindings] without running anything, handy for debugging |
Choosing columns
$db->table('users')->select('id, full_name AS name, email')->get();
$db->table('users')->select(['id', 'email'])->distinct()->get();
Column names may contain letters, digits, _ and a . for table.column, with an optional alias. Anything else throws an InvalidArgumentException, so a column name can never smuggle in SQL.
For an expression, use selectRaw() with ? placeholders:
$db->table('listings')
->select('id, title')
->selectRaw('ST_Distance_Sphere(location, POINT(?, ?)) AS meters', [$lng, $lat])
->get();
Conditions
where() takes a column, an operator and a value. Conditions are joined with AND; orWhere() joins with OR:
$db->table('posts')
->where('status', '=', 'published')
->where('views', '>=', 100)
->orWhere('featured', '=', true)
->get();
The operators are =, !=, <>, <, >, <=, >=, LIKE, NOT LIKE, IN, NOT IN, IS and IS NOT. Values are converted for the database: true and false become 1 and 0, dates become Y-m-d H:i:s.
There are shortcuts for the common cases, each with an or version (orWhereIn(), orWhereNull() and so on):
| Method | SQL |
|---|---|
whereIn('id', [1, 2, 3]), whereNotIn(...) |
id IN (?, ?, ?); the array must not be empty |
whereNull('deleted_at'), whereNotNull(...) |
deleted_at IS NULL |
whereBetween('price', [100, 500]), whereNotBetween(...) |
price BETWEEN ? AND ? |
Grouping conditions
Pass a function to where() or orWhere() to put conditions in parentheses:
// status = ? AND (title LIKE ? OR city LIKE ?)
$db->table('listings')
->where('status', '=', 'active')
->where(fn ($q) => $q->where('title', 'LIKE', "%{$term}%")
->orWhere('city', 'LIKE', "%{$term}%"))
->get();
Raw conditions
whereRaw() and orWhereRaw() accept SQL the builder can't write, such as full-text search:
$db->table('posts')
->whereRaw('MATCH(title, body) AGAINST(? IN BOOLEAN MODE)', [$words])
->get();
Raw SQL must use ? placeholders with exactly one binding each. Named placeholders and a wrong number of bindings are rejected with an InvalidArgumentException.
Warning
Never put request values into raw SQL by joining strings. A literal ? inside a quoted string also counts as a placeholder, so bind such values too.
Joins
$db->table('posts p')
->select('p.id, p.title, u.full_name AS author')
->join('users u', 'u.id', '=', 'p.author_id')
->join('categories c', 'c.id', '=', 'p.category_id', 'LEFT')
->get();
join($table, $left, $operator, $right, $type) compares two columns. The type is INNER (the default), LEFT, RIGHT or CROSS.
Grouping
$db->table('orders')
->select('customer_id')
->selectRaw('SUM(total) AS spent')
->groupBy('customer_id')
->havingRaw('SUM(total) > ?', [1000])
->get();
having(), orHaving(), havingIn(), havingNotIn(), havingNull() and havingNotNull() work like their where versions.
Ordering and limits
$db->table('posts')
->orderBy('featured', 'DESC')
->orderBy('created_at') // ASC by default
->limit(20)
->offset(40)
->get();
offset() only applies together with limit(). orderByRaw() takes an expression with optional bindings, for example orderByRaw('meters ASC').
Inserting, updating and deleting
$id = $db->table('posts')->insert([
'title' => 'Hello',
'created_at' => new DateTimeImmutable(),
]);
$db->table('posts')->where('id', '=', $id)->update(['title' => 'Hello again']);
$db->table('posts')->where('id', '=', $id)->delete();
insert()returns the new row's ID.update()anddelete()refuse to run without awhere(), so a forgotten condition can't change or empty a whole table.- Array values are stored as JSON.
Raw queries and transactions
query($sql, $params) runs any prepared statement and returns a PDOStatement, and transaction() runs a function in a transaction. Both are described on the models page.