Models & the ORM
In the Controllers page you saw
$this->Articles->find()->all() "just work" without any setup. That's CakePHP's
ORM (Object-Relational Mapper) at work — its own fluent, safe query builder for
talking to your database, built specifically for CakePHP rather than a third-party library like
Doctrine or Eloquent. Understanding it means understanding two related but distinct kinds of classes.
Table Classes vs. Entity Classes
| Class | Represents | Typical file |
|---|---|---|
ArticlesTable | The whole articles table — query logic, business rules, validation | src/Model/Table/ArticlesTable.php |
Article | One row/record from that table, with its own properties (title, body, ...) | src/Model/Entity/Article.php |
A Table object is the thing you query — it's how you ask "give me all articles" or
"save this article." Each individual result you get back is an Entity object, one per
database row, carrying that row's own data as plain properties like $article->title.
src/Model/Table/ArticlesTable.php or src/Model/Entity/Article.php
doesn't exist, CakePHP auto-generates a basic, working version of it behind the scenes — you only
create the file yourself once you need custom behavior.Worked Example: A Custom Table Class
Once you do need custom logic — say, a reusable query for "only published articles" — you create
the Table class yourself and add a finder method. Finder methods are named
findSomething() and are called by dropping the find prefix:
find('published').
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function findPublished(Query $query, array $options)
{
return $query->where(['Articles.published' => true]);
}
}
With that in place, any call to $this->Articles->find('published') anywhere in
the app automatically applies that WHERE condition — the custom logic lives in one
place instead of being copy-pasted across every controller that needs it.
Accessing a Table From a Controller
This is the same "just works" magic from the Controllers page, explained fully now: because your
controller is named ArticlesController, CakePHP infers you'll want the
Articles model and loads it automatically as $this->Articles — no
constructor code, no manual instantiation.
$articles = $this->Articles->find('published')->all();
The ORM Is CakePHP's Own Query Builder
It's worth being explicit: CakePHP's ORM is not Doctrine, not Eloquent, and not a wrapper around
either — it's a query builder written specifically for CakePHP. Every method call you chain, like
where(), order(), or limit(), builds up a SQL query behind
the scenes while automatically escaping values to protect against SQL injection.
$query = $this->Articles->find()
->where(['Articles.published' => true])
->order(['Articles.created' => 'DESC'])
->limit(10);
Because every value passed in is bound safely rather than concatenated into raw SQL, using the ORM this way already sidesteps the SQL injection risks covered on Core PHP's Security page — as long as you build conditions through the query builder rather than string-concatenating raw SQL yourself.