Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Associations

Tables rarely live alone: an article has an author and comments. An association describes how records of one table relate to records of another. Once it is described, related data is fetched with one call.

Preparation

Let comments be stored in a comments table with the columns id, article_id, body, created. Create it with a migration and generate the model:

bin/cake bake migration CreateComments article_id:integer body:text created
bin/cake migrations migrate
bin/cake bake model Comments

The article_id column is a foreign key: it holds the number of the article the comment belongs to.

Describing an association

Associations are written in the initialize() method of the table class:

// src/Model/Table/ArticlesTable.php
public function initialize(array $config): void
{
    parent::initialize($config);
 
    $this->addBehavior('Timestamp');
 
    $this->hasMany('Comments');
}

hasMany('Comments') means that an article has many comments. The ORM derives the article_id column name from conventions. If a model is baked with bake model after the foreign key exists, the generator adds associations itself.

Kinds of associations

AssociationMeaningWhere the foreign key is
hasMany('Comments')An article has many commentscomments.article_id
belongsTo('Articles')A comment belongs to an articlecomments.article_id
hasOne('Profiles')A user has one profileprofiles.user_id
belongsToMany('Tags')An article has many tags and a tag has many articlesThe articles_tags table

hasMany and belongsTo are two sides of the same link: the first is written in ArticlesTable, the second in CommentsTable.

// src/Controller/ArticlesController.php
public function view(int $id): void
{
    $article = $this->Articles->get($id, contain: ['Comments']);
 
    $this->set(compact('article'));
}

The contain parameter lists the associations to load together with the article.

<h1><?= h($article->title) ?></h1>
 
<?php foreach ($article->comments as $comment): ?>
    <p><?= h($comment->body) ?></p>
<?php endforeach; ?>

Comments are in the comments property, the association name in lower case. For an association with a single record the property is singular: $comment->article.

In a find query

$articles = $this->Articles->find()
    ->contain(['Comments'])
    ->all();

No matter how many articles are found, comments are loaded with one extra query.

$articles = $this->Articles->find()
    ->matching('Comments', fn ($q) => $q->where(['Comments.body LIKE' => '%thanks%']))
    ->all();

matching() keeps only the articles that have a matching comment.

$comment = $this->Articles->Comments->newEntity([
    'article_id' => $article->id,
    'body' => 'Great article!',
]);
 
$this->Articles->Comments->save($comment);

The related table is reached through the association: $this->Articles->Comments. For newEntity() to accept article_id, the field must be in $_accessible of the Comment class.

Common mistake

Reading $article->comments without contain gives null, not comments: CakePHP does not run queries on demand. This protects from hundreds of hidden queries, but associations have to be listed explicitly. If a related property is empty, check contain first.

Thanks for reading!

Donate to the site — we work hard for you.