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":12,"date":"2015-04-28T00:21:49","date_gmt":"2015-04-28T06:21:49","guid":{"rendered":"http:\/\/www.munderwood.ca\/?p=12"},"modified":"2015-04-28T00:22:33","modified_gmt":"2015-04-28T06:22:33","slug":"listing-associated-ids-during-a-controller-query-in-cakephp-3","status":"publish","type":"post","link":"https:\/\/www.munderwood.ca\/index.php\/2015\/04\/28\/listing-associated-ids-during-a-controller-query-in-cakephp-3\/","title":{"rendered":"Listing associated IDs during a Controller query in CakePHP 3"},"content":{"rendered":"

Yesterday<\/a>\u00a0I looked into adding a key-value pair\u00a0such as\u00a0“tag_ids”: [1, 2, 3]<\/span>\u00a0to a JSON\u00a0object returned by a serialized CakePHP view, for Bookmarks\u00a0belonging to many Tags. My solution produces the desired result, but involves the execution of a new database query for every bookmark, on top of the one that retrieved the record in the first place. This is not exactly desirable behaviour, so today I looked into other options. I still don’t have what I consider to be an optimal solution, but did come up with an alternative that manages to pull all the associated Tag ids in the same query that retrieves the current batch of Bookmarks.<\/p>\n

There is a new disadvantage introduced by this solution, because the resulting KVP contains a string instead of an array, looking like\u00a0“tag_ids”: “[1, 2, 3]”<\/span>\u00a0. Additionally, the\u00a0code is nowhere near ready to be generalized for easy addition to multiple controllers, nor even particularly elegant in terms of making use of Cake’s routines for inflection etc. Nevertheless, I want to record it while it’s still fresh in my mind.<\/p>\n

The idea is to use the CakePHP query builder to LEFT JOIN the Bookmarks model’s table to the join table that contains its many-to-many relationships with the Tags model. The query is then grouped by all of the Bookmarks fields, and the\u00a0tag_id<\/span>\u00a0field from the join table is aggregated into a single comma-delimited string. I’m using PostgreSQL, so accomplish this with the\u00a0string_agg command<\/a>. Here’s a working example:<\/p>\n

\/\/ In src\/Controller\/Bookmarks.php\r\npublic function index () {\r\n  \/\/ Array of Bookmarks fields to include\r\n  $bookmark_fields = ['id', 'title' \/* add other fields here *\/ ];\r\n  \/\/ PostgreSQL function that will concatenate over the GROUP\r\n  $concat = \"string_agg(BookmarksTags.tag_id::text, ',')\";\r\n  \/\/ Aggregated field from the join table\r\n  $agg_fields = [\r\n    'tag_ids' => \"'[' || $concat || ']'\"\r\n  ];\r\n  \/\/ For the group-by, we need Bookmarks.id, Bookmarks.title, etc.\r\n  $group_by_fields = array_map(\r\n    function ($f) { return 'Bookmarks.' . $f; },\r\n    $bookmark_fields\r\n  );\r\n  $bookmarks = $this->Bookmarks->find('all')\r\n      ->select(array_merge($group_by_fields, $agg_fields))\r\n      ->autofields(false)\r\n      ->join([\r\n        'table' => 'bookmarks_tags',\r\n        'alias' => 'BookmarksTags',\r\n        'type' => 'LEFT',\r\n        'conditions' => 'BookmarksTags.bookmark_id = Bookmarks.id'\r\n      ])\r\n      ->group($group_by_fields);\r\n  \/\/ Store the query and set it to be serializable\r\n  $this->set('bookmarks', $bookmarks);\r\n  $this->set('_serialize', ['bookmarks']);  \r\n}<\/pre>\n

I found that I had to set autofields(false)<\/span>\u00a0 to avoid having CakePHP automatically include every field in the join table, and therefore needing to add them to the group-by clause or aggregating them in some fashion.<\/p>\n

There are a few improvements that can be made right away. Additional joins can be added, but will introduce multiples in the concatenated string unless the string aggregation is made distinct,\u00a0string_agg(distinct Book…)<\/span>. Bookmarks with no tags yield \u00a0“tag_ids”: null<\/span>, but can be made to give “tag_ids”: “[]”<\/span>\u00a0 by wrapping the aggregation in coalesce(…, ”)<\/span>.<\/p>\n

Larger-scale improvements could involve outputting\u00a0an array instead of a string in the JSON, perhaps by returning a postgres array instead of a CSV string, and teaching Cake how to deal with that properly. Beyond that, not hard-coding the table names, generating the list of joins automatically, and generally wrapping this all up into a behaviour or trait would be nice steps to take.<\/p>\n","protected":false},"excerpt":{"rendered":"

Yesterday\u00a0I looked into adding a key-value pair\u00a0such as\u00a0“tag_ids”: [1, 2, 3]\u00a0to a JSON\u00a0object returned by a serialized CakePHP view, for Bookmarks\u00a0belonging to many Tags. My solution produces the desired result, but involves the execution of a new database query for every bookmark, on top of the one that retrieved the record in the first place. … [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\/12"}],"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=12"}],"version-history":[{"count":16,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/posts\/12\/revisions"}],"predecessor-version":[{"id":28,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/posts\/12\/revisions\/28"}],"wp:attachment":[{"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/media?parent=12"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/categories?post=12"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.munderwood.ca\/index.php\/wp-json\/wp\/v2\/tags?post=12"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}