A model represents one database table. It extends FrostMVC's Model class, which gives it a query builder:
you chain methods to build the SQL, and every value is bound as a parameter, so user input never ends up inside the SQL text.
app/settings.php.
See Configuration.
app/models/Posts.php
<?php
namespace MyApp\models;
use FrostMVC\Model;
class Posts extends Model {
public function published() {
return $this->get()->where('status', 'publish')->order('created', 'DESC')->read();
}
}
The table name comes from the class name: TABLE_PREFIX + the lowercase class name + TABLE_SUFFIX.
With the default TABLE_SUFFIX of _tbl, Posts uses the table posts_tbl.
Call $this->setTableName('other_table') in the constructor to use a different table.
use MyApp\models\Posts;
$posts = (new Posts)->get()->read(); // all rows
$post = (new Posts)->get()->where('id', $id)->readRow(); // one row, [] when none
$title = (new Posts)->get('title')->where('id', $id)->readScalar(); // one value
$recent = (new Posts)->get(['id', 'title'])
->where('status', 'publish')
->and_('views', '>=', 100)
->order('created', 'DESC')
->limit(0, 10) // offset, count
->read();
get($cols) | Start a SELECT. Omit $cols for all columns. |
where('col', $value)where(['a' => 1, 'b' => 2]) | Equality conditions (an array is AND-joined). |
and_('col', '>=', $value)or_('col', $value) | Add more conditions, optionally with an operator. |
in([...]), like($pattern), between($a, $b), not(...) | More comparisons, chained after a column. |
order('col', 'DESC'), group('col'), limit($count) | Sorting, grouping and paging. |
join('Authors', Model::escape('authors_tbl.id = posts_tbl.author')) | Join another model's table, with the full ON condition. Pass 'LEFT' as the third argument for a LEFT JOIN. |
read() / readRow() / readScalar() | Run it: all rows, the first row, or the first column of the first row. |
$id = (new Posts)->insert(['title' => $title, 'status' => 'draft'])->execute(); // new row ID
(new Posts)->update(['status' => 'publish'])->where('id', $id)->execute(); // true
(new Posts)->update('views', Model::escape('views + 1'))->where('id', $id)->execute();
(new Posts)->delete()->where('id', $id)->execute();
execute() returns the new ID for an INSERT, true for UPDATE and DELETE, and false when the query fails.
Always chain where() on updates and deletes.
Model::escape() marks a value as raw SQL, so it is not bound as a parameter. Only use it for SQL you wrote yourself, never for user input.
Chain cache() before a read to keep the result for TABLE_CACHE_EXPIRATION seconds (600 by default).
When the cached copy is stale it is still returned immediately, and the query is refreshed in the background after the page is sent:
$menu = (new Categories)->get()->order('name')->cache()->read();
For SQL the builder does not cover, pass it to execute(). Use named placeholders and pass their values by name, so they are bound as parameters:
$rows = (new Posts)->execute(
'SELECT author, COUNT(*) AS total FROM posts_tbl WHERE created >= :since GROUP BY author',
['since' => $since],
true // return rows
);
use MyApp\models\Posts;
public function index() {
View::get('blog/index', ['posts' => (new Posts)->published()]);
}
Next: Views & Twig · Reference: Model, DB