-
Notifications
You must be signed in to change notification settings - Fork 9
/
response.php
448 lines (386 loc) · 11.6 KB
/
response.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
<?php
namespace alsvanzelf\jsonapi;
/**
* @deprecated {@see MetaDocument}
*/
class response extends base {
/**
* advised http status codes
*/
const STATUS_OK = 200;
const STATUS_CREATED = 201;
const STATUS_NO_CONTENT = 204;
const STATUS_NOT_MODIFIED = 304;
const STATUS_TEMPORARY_REDIRECT = 307;
const STATUS_PERMANENT_REDIRECT = 308;
const STATUS_BAD_REQUEST = 400;
const STATUS_UNAUTHORIZED = 401;
const STATUS_FORBIDDEN = 403;
const STATUS_NOT_FOUND = 404;
const STATUS_METHOD_NOT_ALLOWED = 405;
const STATUS_UNPROCESSABLE_ENTITY = 422;
const STATUS_INTERNAL_SERVER_ERROR = 500;
const STATUS_SERVICE_UNAVAILABLE = 503;
/**
* content type headers
*/
const CONTENT_TYPE_OFFICIAL = 'application/vnd.api+json';
const CONTENT_TYPE_DEBUG = 'application/json';
const CONTENT_TYPE_JSONP = 'application/javascript';
/**
* Jsonp callback methods
*/
const JSONP_CALLBACK_DEFAULT = "JSONP_CALLBACK";
/**
* json encode options
* default is JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE
* in debug mode (@see ::$debug) JSON_PRETTY_PRINT is added
*/
const ENCODE_DEFAULT = 320;
const ENCODE_DEBUG = 448;
/**
* whether or not ->send_response() sends out basic status headers
* if set to true, it sends the status code and the location header
*/
public static $send_status_headers = true;
/**
* internal data containers
*/
protected $links = array();
protected $meta_data = array();
protected $included_data = array();
protected $included_resources = array();
protected $http_status = self::STATUS_OK;
protected $redirect_location = null;
/**
* base constructor for all response objects (resource, collection, errors)
*
* a few things are arranged here:
* - sets the self link using $_SERVER variables
*
* @see ->set_self_link() to override this default behavior
*/
public function __construct() {
parent::__construct();
// auto-fill the self link based on the current request
$self_link = $_SERVER['REQUEST_URI'];
if (isset($_SERVER['PATH_INFO'])) {
$self_link = $_SERVER['PATH_INFO'];
}
$this->set_self_link($self_link);
}
/**
* alias for ->get_json()
*
* @see ->get_json()
*
* @return string
*/
public function __toString() {
return $this->get_json();
}
/**
* generates an array for the whole response body
*
* @see jsonapi.org/format
*
* @return array, containing:
* - links
* - meta
*/
public function get_array() {
$response = array();
// links
if ($this->links) {
$response['links'] = $this->links;
}
// meta data
if ($this->meta_data) {
$response['meta'] = $this->meta_data;
}
return $response;
}
/**
* returns the whole response body as json
* it generates the response via ->get_array()
*
* @see ->get_array() for the structure
* @see json_encode() options
*
* @param int $encode_options optional, $options for json_encode()
* defaults to ::ENCODE_DEFAULT or ::ENCODE_DEBUG, @see ::$debug
* @return json
*/
public function get_json($encode_options=null) {
if (is_int($encode_options) == false) {
$encode_options = self::ENCODE_DEFAULT;
}
if (base::$debug || strpos($_SERVER['HTTP_ACCEPT'], '/json') == false) {
$encode_options = self::ENCODE_DEBUG;
}
$response = $this->get_array();
$json = json_encode($response, $encode_options);
return $json;
}
/**
* sends out the json response to the browser
* this will fetch the response from ->get_json() if not given via $response
*
* @note this also sets the needed http headers (status, location and content-type)
*
* @param string $content_type optional, defaults to ::CONTENT_TYPE_OFFICIAL (the official IANA registered one) ..
* .. or to ::CONTENT_TYPE_DEBUG, @see ::$debug
* @param int $encode_options optional, $options for json_encode()
* defaults to ::ENCODE_DEFAULT or ::ENCODE_DEBUG, @see ::$debug
* @param json $response optional, defaults to ::get_json()
* @param string $jsonp_callback optional, response as jsonp
* @return void however, a string will be echo'd to the browser
*/
public function send_response($content_type=null, $encode_options=null, $response=null, $jsonp_callback=null) {
if (is_null($response) && $this->http_status != self::STATUS_NO_CONTENT) {
$response = $this->get_json($encode_options);
}
if (empty($content_type)) {
$content_type = self::CONTENT_TYPE_OFFICIAL;
}
if (base::$debug || strpos($_SERVER['HTTP_ACCEPT'], '/json') == false) {
$content_type = self::CONTENT_TYPE_DEBUG;
}
if (self::$send_status_headers) {
$this->send_status_headers();
}
header('Content-Type: '.$content_type.'; charset=utf-8');
if ($this->http_status == self::STATUS_NO_CONTENT) {
return;
}
// jsonp response
if ($content_type == self::CONTENT_TYPE_JSONP) {
if (empty($jsonp_callback)) {
$jsonp_callback = self::JSONP_CALLBACK_DEFAULT;
}
echo $jsonp_callback.'('.$response.')';
return;
}
echo $response;
}
/**
* sends out the http status code and optional redirect location
* defaults to ::STATUS_OK, or ::STATUS_INTERNAL_SERVER_ERROR for an errors response
*
* @return void
*/
private function send_status_headers() {
if ($this->redirect_location) {
if ($this->http_status == self::STATUS_OK) {
$this->set_http_status(self::STATUS_TEMPORARY_REDIRECT);
}
header('Location: '.$this->redirect_location, $replace=true, $this->http_status);
return;
}
http_response_code($this->http_status);
}
/**
* sets the http status code for this response
*
* @param int $http_status any will do, you can easily pass one of the predefined ones in ::STATUS_*
*/
public function set_http_status($http_status) {
$this->http_status = $http_status;
}
/**
* sets a new location the client should follow
*
* @param string $location absolute url
*/
public function set_redirect_location($location) {
if (self::$send_status_headers == false && base::$debug) {
trigger_error('location will not be send out unless response::$send_status_headers is true', E_USER_NOTICE);
}
$this->redirect_location = $location;
}
/**
* returns the included resource objects
* this is used by a collection to work with the actual objects
*
* @return array
*/
public function get_included_resources() {
return $this->included_resources;
}
/**
* adds a link
* this will end up in response.links.{$key}
*
* useful for links which can not be added as relation, @see ->add_relation()
*
* @param string $key
* @param mixed $link string with link, or raw link object array/object
* @param mixed $meta_data optional, meta data as key-value pairs
* objects are converted in arrays, @see base::convert_object_to_array()
* @return void
*/
public function add_link($key, $link, $meta_data=null) {
if ($meta_data) {
if (is_object($meta_data)) {
$meta_data = parent::convert_object_to_array($meta_data);
}
$link = array(
'href' => $link,
'meta' => $meta_data,
);
}
$this->links[$key] = $link;
}
/**
* fills the set of links
* this will end up in response.links
*
* @see ->add_link()
*
* @param array $links
* @return void
*/
public function fill_links($links) {
foreach ($links as $key => $link) {
$this->add_link($key, $link);
}
}
/**
* sets the link to the request used to give this response
* this will end up in response.links.self ..
* and in response.data.links.self for single resource objects
*
* by default this is already set using $_SERVER variables
* use this method to override this default behavior
* @see ::__construct()
*
* @param string $link
* @param mixed $meta_data optional, meta data as key-value pairs
* objects are converted in arrays, @see base::convert_object_to_array()
* @return void
*/
public function set_self_link($link, $meta_data=null) {
if ($meta_data) {
// can not combine both raw link object and extra meta data
if (is_string($link) == false) {
throw new \Exception('link "self" should be a string if meta data is provided separate');
}
if (is_object($meta_data)) {
$meta_data = parent::convert_object_to_array($meta_data);
}
$link = array(
'href' => $link,
'meta' => $meta_data,
);
}
$this->links['self'] = $link;
}
/**
* adds meta data to the default self link
* this will end up in response.links.self.meta.{$key}
*
* @note you can also use ->set_self_link() with the whole meta object at once
*
* @param string $key
* @param mixed $meta_data objects are converted in arrays, @see base::convert_object_to_array()
* @return void
*/
public function add_self_link_meta($key, $meta_data) {
if (is_object($meta_data)) {
$meta_data = self::convert_to_array($meta_data);
}
// converts string-type link
if (is_string($this->links['self'])) {
$this->links['self'] = array(
'href' => $this->links['self'],
'meta' => array(),
);
}
$this->links['self']['meta'][$key] = $meta_data;
}
/**
* adds an included resource
* this will end up in response.included[]
*
* prefer using ->add_relation() instead
*
* a $resource should have its 'id' set
*
* @note this can only be used by resource and collection, not by errors
*
* @param \alsvanzelf\jsonapi\resource $resource
*/
public function add_included_resource(\alsvanzelf\jsonapi\resource $resource) {
if (property_exists($this, 'included_resources') == false) {
throw new \Exception(get_class($this).' can not contain included resources');
}
$resource_array = $resource->get_array();
if (empty($resource_array['data']['id'])) {
return;
}
// root-level meta-data
if (!empty($resource_array['meta'])) {
$this->fill_meta($resource_array['meta']);
}
$resource_array = $resource_array['data'];
$key = $resource->get_type().'/'.$resource->get_id();
$this->included_data[$key] = $resource_array;
// make a backup of the actual resource, to pass on to a collection
$this->included_resources[$key] = $resource;
// allow nesting relationshios
foreach ($resource->get_included_resources() as $included_resource) {
if (empty($included_resource->primary_id)) {
continue;
}
$included_key = $included_resource->get_type().'/'.$included_resource->get_id();
$this->included_resources[$included_key] = $included_resource;
$included_array = $included_resource->get_array();
$included_array = $included_array['data'];
$this->included_data[$included_key] = $included_array;
}
}
/**
* fills the included resources
* this will end up in response.included[]
*
* prefer using ->fill_relations() instead
*
* @param mixed $resources array of \alsvanzelf\jsonapi\resource objects
* or \alsvanzelf\jsonapi\collection object
* @return void
*/
public function fill_included_resources($resources) {
if ($resources instanceof \alsvanzelf\jsonapi\collection) {
$resources = $resources->get_resources();
}
foreach ($resources as $resource) {
$this->add_included_resource($resource);
}
}
/**
* adds some meta data
* this will end up in response.meta.{$key}
*
* @param string $key
* @param mixed $meta_data objects are converted in arrays, @see base::convert_object_to_array()
* @return void
*/
public function add_meta($key, $meta_data) {
if (is_object($meta_data)) {
$meta_data = parent::convert_object_to_array($meta_data);
}
$this->meta_data[$key] = $meta_data;
}
/**
* fills the meta data
* this will end up in response.meta
*
* @param array $meta_data
* @return void
*/
public function fill_meta($meta_data) {
foreach ($meta_data as $key => $single_meta_data) {
$this->add_meta($key, $single_meta_data);
}
}
}