Skip to content

Commit b1f10af

Browse files
authored
Merge pull request #21 from bluecadet/feature/add-in-cachable-json-response
Add in cacheable json response
2 parents 10ce674 + aec5040 commit b1f10af

File tree

16 files changed

+460
-19
lines changed

16 files changed

+460
-19
lines changed
-6 KB
Binary file not shown.
-6 KB
Binary file not shown.

modules/bc_api_example/bc_api_example.info.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ core_version_requirement: ^10 || ^11
66
package: Bluecadet
77

88
dependencies:
9+
- drupal:taxonomy
910
- bc_api_base:bc_api_base
1011

1112
configure: null

modules/bc_api_example/bc_api_example.routing.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,26 @@ bc_api_example.pirates_detail:
2020
parameters:
2121
nid:
2222
type: entity:node
23+
24+
bc_api_example.cacheable.pirates:
25+
path: '/api/cacheable/pirates'
26+
methods: [GET, HEAD]
27+
defaults:
28+
_controller: '\Drupal\bc_api_example\Controller\ApiControllerCacheableResponsePirateExample:getResourceList'
29+
requirements:
30+
_permission: 'use api'
31+
options:
32+
_auth: [ 'key_auth' ]
33+
34+
bc_api_example.cacheable.pirates_detail:
35+
path: '/api/cacheable/pirates/{nid}'
36+
methods: [GET, HEAD]
37+
defaults:
38+
_controller: '\Drupal\bc_api_example\Controller\ApiControllerCacheableResponsePirateExample:getResource'
39+
requirements:
40+
_permission: 'use api'
41+
options:
42+
_auth: [ 'key_auth' ]
43+
parameters:
44+
nid:
45+
type: entity:node
-6 KB
Binary file not shown.
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
3+
namespace Drupal\bc_api_example\Controller;
4+
5+
use Drupal\bc_api_base\Controller\ApiControllerBase;
6+
use Drupal\bc_api_base\CacheableJsonResponseTrait;
7+
8+
/**
9+
* Example API Controller Class.
10+
*
11+
* @ApiDoc(
12+
* params = {
13+
* @ApiParam(
14+
* name = "status",
15+
* type = "bool",
16+
* description = "Filter on node status.",
17+
* default = "TRUE",
18+
* ),
19+
* @ApiParam(
20+
* name = "nat",
21+
* type = "int",
22+
* description = "Filter on nationality. This should use the taxonomy id of the nationality.",
23+
* ),
24+
* }
25+
* )
26+
*/
27+
class ApiControllerCacheableResponsePirateExample extends ApiControllerBase {
28+
29+
use CacheableJsonResponseTrait;
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function getApiCacheTime($id = NULL) {
35+
// Parent method looks at settings form and takes into account list or
36+
// detail.
37+
return 0;
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function getCacheId() {
44+
// This should be a unique string, characters only.
45+
$cid = "yo-ho-ho";
46+
47+
if (!empty($this->params)) {
48+
$cid .= ":" . implode(":", $this->params);
49+
}
50+
51+
$cid .= ":page-" . $this->page;
52+
$cid .= ":limit-" . $this->limit;
53+
54+
return $cid;
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
public function initCacheTags() {
61+
$this->cacheTags = [
62+
'myAwesomeCoolCacheTag',
63+
];
64+
}
65+
66+
/**
67+
* {@inheritdoc}
68+
*/
69+
public function setParams() {
70+
// It would be good to always run this.
71+
parent::setParams();
72+
73+
$this->privateParams['status'] = 1;
74+
75+
// Here we also want to handle any bad param errors...
76+
}
77+
78+
/**
79+
* {@inheritdoc}
80+
*/
81+
public function getDefaultPlatform() {
82+
return "cinder";
83+
}
84+
85+
/**
86+
* {@inheritdoc}
87+
*/
88+
public function getResourceQueryResult() {
89+
// Just having this here as an example.
90+
// Most times no need to override this method.
91+
parent::getResourceQueryResult();
92+
}
93+
94+
/**
95+
* {@inheritdoc}
96+
*/
97+
public function getResourceListQueryResult() {
98+
// This method should be overridden for any endpoint.
99+
$query = $this->entityTypeManager->getStorage('node')->getQuery();
100+
$query->accessCheck(TRUE);
101+
$query->condition('status', $this->privateParams['status']);
102+
$query->condition('type', 'pirate');
103+
104+
$count_query = clone $query;
105+
106+
$query->range(($this->page * $this->limit), $this->limit);
107+
$entity_ids = $query->execute();
108+
109+
// Must set total result count so we can properly page.
110+
$this->resultTotal = (int) $count_query->count()->execute();
111+
112+
// Process Items.
113+
$nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($entity_ids);
114+
115+
$this->rawData = $nodes;
116+
}
117+
118+
/**
119+
* {@inheritdoc}
120+
*/
121+
public function buildAllResourceData() {
122+
$data = [];
123+
124+
foreach ($this->rawData as $node) {
125+
$this->cacheTags = array_merge($this->cacheTags, $node->getCacheTags());
126+
$this->addCacheableDependency($node);
127+
$created_changed = $this->transformer->createdChangedFieldVals($node);
128+
129+
$item = [
130+
'nid' => (int) $node->id(),
131+
'cms_title' => $node->label(),
132+
'created' => $created_changed[0],
133+
'updated' => $created_changed[1],
134+
];
135+
$data[] = $item;
136+
}
137+
138+
$this->data = $data;
139+
}
140+
141+
/**
142+
* {@inheritdoc}
143+
*/
144+
public function buildLinks() {
145+
// This method should be overridden for any endpoint.
146+
$base_url = $this->request->getSchemeAndHttpHost() . $this->request->getPathInfo();
147+
$tmp_query_params = $this->params;
148+
$tmp_query_params['platform'] = $this->platform;
149+
$tmp_query_params['limit'] = $this->limit;
150+
151+
if ($this->page == 0) {
152+
$this->prev = "";
153+
}
154+
else {
155+
$tmp_query_params['page'] = $this->page - 1;
156+
$this->prev = $base_url . "?" . http_build_query($tmp_query_params);
157+
}
158+
159+
if ($this->resultTotal > (($this->page + 1) * $this->limit)) {
160+
$tmp_query_params['page'] = $this->page + 1;
161+
$this->next = $base_url . "?" . http_build_query($tmp_query_params);
162+
}
163+
else {
164+
165+
$this->next = "";
166+
}
167+
168+
}
169+
170+
}

modules/bc_api_example/src/Controller/ApiControllerPirateExample.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public function getApiCacheTime($id = NULL) {
3838
* {@inheritdoc}
3939
*/
4040
public function getCacheId() {
41-
$cid = "SOMETHING_UNIQUE";
41+
// This should be a unique string, characters only.
42+
$cid = "pirates";
4243

4344
if (!empty($this->params)) {
4445
$cid .= ":" . implode(":", $this->params);
@@ -93,6 +94,7 @@ public function getResourceQueryResult() {
9394
public function getResourceListQueryResult() {
9495
// This method should be overridden for any endpoint.
9596
$query = $this->entityTypeManager->getStorage('node')->getQuery();
97+
$query->accessCheck(TRUE);
9698
$query->condition('status', $this->privateParams['status']);
9799
$query->condition('type', 'pirate');
98100

-6 KB
Binary file not shown.

src/CacheableJsonResponseTrait.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
namespace Drupal\bc_api_base;
4+
5+
use Drupal\Core\Cache\CacheableJsonResponse;
6+
use Drupal\Core\Cache\CacheableMetadata;
7+
use Symfony\Component\HttpFoundation\Response;
8+
9+
/**
10+
* Trait CacheableJsonResponseTrait.
11+
*
12+
* This trait provides a way to create cacheable JSON responses. It allows for
13+
* the addition of cacheable metadata and dependencies to the response, ensuring
14+
* that the response can be cached appropriately.
15+
*/
16+
trait CacheableJsonResponseTrait {
17+
18+
/**
19+
* Cacheable metadata for the response.
20+
*
21+
* @var \Drupal\Core\Cache\CacheableMetadata
22+
*/
23+
protected $cacheMetadata = NULL;
24+
25+
/**
26+
* Returns the cacheable metadata for the response.
27+
*
28+
* When building any data, cacheable metadata should be set from there. And it
29+
* will be applied when the request is altered.
30+
*
31+
* @return \Drupal\Core\Cache\CacheableMetadata
32+
*/
33+
protected function getCacheableMetadata(): CacheableMetadata {
34+
if (!isset($this->cacheMetadata)) {
35+
$this->cacheMetadata = new CacheableMetadata();
36+
}
37+
return $this->cacheMetadata;
38+
}
39+
40+
/**
41+
* Adds a dependency on an object: merges its cacheability metadata.
42+
*
43+
* For instance, when a response depends on some configuration, an entity, or
44+
* an access result, we must make sure their cacheability metadata is present
45+
* on the response. This method makes doing that simple.
46+
*
47+
* @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $dependency
48+
* The dependency. If the object implements CacheableDependencyInterface,
49+
* then its cacheability metadata will be used. Otherwise, the passed in
50+
* object must be assumed to be uncacheable, so max-age 0 is set.
51+
*
52+
* @return $this
53+
*/
54+
public function addCacheableDependency($dependency) {
55+
56+
$this->cacheMetadata = $this->getCacheableMetadata()->merge(CacheableMetadata::createFromObject($dependency));
57+
58+
return $this;
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function getResourceQueryResult() {
65+
parent::getResourceQueryResult();
66+
67+
// If the resource is set, add it as a cacheable dependency.
68+
if (isset($this->resource) && !empty($this->resource)) {
69+
$this->addCacheableDependency($this->resource);
70+
}
71+
}
72+
73+
/**
74+
* {@inheritdoc}
75+
*
76+
* Override the base classes response, to create a cacheable response.
77+
*/
78+
public function createResponse(): Response {
79+
$response = new CacheableJsonResponse($this->return_data);
80+
81+
// Attach cache metadata if available.
82+
if ($cache_metadata = $this->getCacheableMetadata()) {
83+
$response->addCacheableDependency($cache_metadata);
84+
}
85+
86+
// Alter it.
87+
$this->responseAlter($response);
88+
89+
return $response;
90+
}
91+
92+
}

src/Controller/ApiControllerBase.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,14 @@ class ApiControllerBase extends ControllerBase implements ApiControllerInterface
174174
/**
175175
* Limit the number of results.
176176
*
177-
* @var string
177+
* @var int
178178
*/
179179
protected $prev = "";
180180

181181
/**
182182
* Limit the number of results.
183183
*
184-
* @var string
184+
* @var int
185185
*/
186186
protected $next = "";
187187

@@ -464,9 +464,8 @@ final public function getResource(Request $request) {
464464
return [];
465465
}
466466

467-
$response = new JsonResponse($this->return_data);
468-
// Alter it.
469-
$this->responseAlter($response);
467+
// $response = new JsonResponse($this->return_data);
468+
$response = $this->createResponse();
470469

471470
return $response;
472471
}
@@ -533,9 +532,7 @@ final public function getResourceList(Request $request) {
533532
return [];
534533
}
535534

536-
$response = new JsonResponse($this->return_data);
537-
// Alter it.
538-
$this->responseAlter($response);
535+
$response = $this->createResponse();
539536

540537
return $response;
541538
}
@@ -589,6 +586,19 @@ public function buildLinks() {
589586
$this->next = "";
590587
}
591588

589+
/**
590+
* {@inheritdoc}
591+
*/
592+
public function createResponse(): Response {
593+
$response = new JsonResponse($this->return_data);
594+
$response->setStatusCode($this->return_data['status']);
595+
596+
// Allow subclasses to alter it.
597+
$this->responseAlter($response);
598+
599+
return $response;
600+
}
601+
592602
/**
593603
* {@inheritdoc}
594604
*/

0 commit comments

Comments
 (0)