-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathActiveResourceBase.php
More file actions
executable file
·567 lines (520 loc) · 17.4 KB
/
Copy pathActiveResourceBase.php
File metadata and controls
executable file
·567 lines (520 loc) · 17.4 KB
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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
<?php
/** \mainpage
* Flexible implementation of the Ruby on Rails ActiveResource REST client.
* Intended to work with RoR-based REST servers, which all share similar
* API patterns.
*
* The implementation is decoupled with many concerns:
* - ActiveResource itself
* - Transport
* - Serialization
* - Inflection
*
* The provided serializations are XML and JSON.
*
* An AuthenticatedActiveResourceBase class is also provided that may be used in conjonction with authlogic_api
* (see http://github/phurni/authlogic_api)
*
* @copyright Copyright 2010 Pascal Hurni
* @license http://www.opensource.org/licenses/mit-license.php Licensed under the MIT License
* @author Pascal Hurni <phi@ruby-reactive.org> http://github.com/phurni
*
* Usage:
* @code
*
* ActiveResource.php
* <?php
* require_once('ActiveResourceBase.php');
* require_once('CurlTransporter.php');
* require_once('XmlSerializer.php');
* require_once('JsonSerializer.php');
* require_once('TypeMarshaller.php');
*
* // Setup a base class with all your chosen concerns
* class ActiveResource extends ActiveResourceBase {
* static $marshaller;
*
* function __construct ($data = array ()) {
* // These may be set by subclasses constructor, so check them before re-setting
* if (! $this->transporter) $this->transporter = new CurlTransporter();
* if (! $this->serializer) $this->serializer = new XmlSerializer(self::$marshaller, array('element_name' => $this->element_name)));
*
* parent::__construct($data);
* }
* }
* ?>
*
* Person.php
* <?php
* require_once ('ActiveResource.php');
*
* class Person extends ActiveResource {
* var $site = 'http://localhost:3000/';
* var $element_name = 'person';
* var $collection_name = 'people';
* }
* ?>
*
* UseCases.php
* <?php
* require_once ('Person.php');
*
* // create a new person
* $person = new Person( array('firstname => 'John', 'lastname => 'Doe') );
* if (!$person->save()) {
* echo $person->error_message;
* }
*
* // for all other operations you must have a blank object as a handle.
* $Person = new Person();
*
* // find people
* $people = $Person->find('all');
* if ($people === false) { // don't use "if (!$people)" because an empty array will match!
* echo $people->error_message;
* }
* else {
* foreach ($people as $person) {
* var_dump($person->to_object());
* }
* }
*
* // find people with some options (passed as query string parameters)
* $people = $Person->find('all', 'lastname' => 'Doe');
*
* // find a known person
* $person = $Person->find(123);
* if ($person === false)
* echo $Person->error_message; // read carefully $Person with a uppercase P
* }
* else {
* var_dump($person->to_object());
* }
*
* // update an attribute
* // have a Person object in $person (find() it)
* $person->phone_number = '555-98-76';
* if (!$person->save()) {
* echo $person->error_message;
* }
*
* // delete a person
* // have a Person object in $person (find() it)
* if (!$person->destoy()) {
* echo $person->error_message;
* }
*
* // custom method
* $object = $Person->get('banned', array('level' => 3));
* if ($object === false) {
* echo $person->error_message;
* }
*
* ?>
*
* @endcode
*/
/**
** You'll find here examples of concrete ActiveResource classes that may be used as a base class for your real model classes
**
** @example ActiveResource_withInflection.php
** @example ActiveResource_withMarshaller.php
*/
/**
** ActiveResourceBase is the abstract base class for a REST resource.
**
** To become a concrete class, subclasses have to set the $serializer and $transporter variables
** with objects having interfaces (duck typing not PHP Interface) explained below.
**
** They also have to set
** - $site to point to the base URL for the remote site
** - $element_name The singular underscored name of the resource
** - $collection_name The plural underscored name of the resource
**
*
* @code
* Serializer
* / **
* * Constructor
* *
* * @param object $model_object The related ActiveResource object.
* * @param assoc $options Future options may be passed here.
* *
* * /
* function __construct($model_object, $options = array())
*
* / **
* * Encodes an object to the target format (specified by the identity of the class)
* * Will return `false` if process failed. Check $error_code to know what went wrong.
* *
* * @param object|assoc $data The object to encode, will be iterated with foreach.
* * @param assoc $options Options map. Currently no options available.
* * @return string The object encoded in the right format
* *
* * /
* function encode($data, $options = array())
*
* / **
* * Decodes a text stream to any of three forms.
* * Will return `false` if process failed. Check $error_code to know what went wrong.
* * The first form is an array consisting of objects of the $model_object class.
* * The second form is a single object of the $model_object class.
* * The third form is an array of string each of them being an error. (The caller already knows the stream is an error stream)
* *
* * It is also possible to get simple objects instead of $model_object class objects. Pass `raw` => true in the options.
* *
* * @param string $text The stream to decode.
* * @param assoc $options Options map. Valid options: `raw`
* * @return array|object
* *
* * /
* function decode($text, $options = array())
*
* / **
* * contains the extension used when generating URL. Must be a string.
* * /
* var $extension
*
* / **
* * contains the MIME type of the format this class handles. Must be a string.
* * /
* var $mime_type
*
*
* Transporter
* / **
* * Sends a GET request using the passed URL
* * Will return FALSE if an error occured. Check $error_code to know what went wrong.
* *
* * @param string $url The complete URL to request.
* * @param assoc $options Options map. Currently no options.
* * @return string The response body.
* *
* * /
* function get($url, $options = array())
*
* / **
* * Sends a POST request.
* * You may set the `Content-Type` header by passing a string to options `format`.
* * Will return FALSE if an error occured. Check $error_code to know what went wrong.
* *
* * @param string $url The complete URL for the request.
* * @param string $data The data to pass as the request body.
* * @param assoc $options Options map. Valid options: `format`
* * @return string The response body.
* *
* * /
* function post($url, $data, $options = array())
*
* / **
* * Sends a PUT request.
* * You may set the `Content-Type` header by passing a string to options `format`.
* * Will return FALSE if an error occured. Check $error_code to know what went wrong.
* *
* * @param string $url The complete URL for the request.
* * @param string $data The data to pass as the request body.
* * @param assoc $options Options map. Valid options: `format`
* * @return string The response body.
* *
* * /
* function put($url, $data, $options = array())
*
* / **
* * Sends a DELETE request.
* * Will return FALSE if an error occured. Check $error_code to know what went wrong.
* *
* * @param string $url The complete URL for the request.
* * @param assoc $options Options map. Currently no options.
* * @return string The response body (has no meaning).
* *
* * /
* function delete($url, $options = array())
*
* @endcode
*/
abstract class ActiveResourceBase {
/// The serializer object (see above)
var $serializer = null;
/// The transporter object (see above)
var $transporter = null;
/// The REST site address, e.g., http://www.example.com/
var $site = null;
/// The element name used for encoding and url generation
var $element_name = false;
/// The collection name used for encoding and url generation
var $collection_name = false;
/// An error message if an error occurred.
var $error_message = false;
/// The error number if an error occurred.
var $error_code = false;
/// The attributes list to scan for the string representation of this record. See __toString()
var $to_string_attributes = array('name', 'label', 'caption', 'title');
// The data of the current object, accessed via the anonymous get/set methods.
protected $_data = array();
/**
** @param assoc $data attributes map to initialize the instance with.
*/
function __construct($data = array()) {
$this->_set_data($data);
}
/**
** Saves a new record or updates an existing one via:
**
** @code
** POST /collection.format
** PUT /collection/id.format
** @endcode
*/
function save() {
if ($this->is_new_record()) {
return $this->_post(array(), $this->_encode($this->_get_data()));
}
return $this->_put(array('id' => $this->_get_id()), $this->_encode($this->_get_data()));
}
/**
** Deletes a record via:
**
** DELETE /collection/id.xml
*/
function destroy() {
return $this->_delete(array('id' => $this->_get_id()));
}
/**
** Finds a record or a collection of records via:
**
** @code
** GET /collection/id.xml
** GET /collection.xml
** @endcode
**
** @param mixed $id
** @param array $options key/value pairs sent as query string parameters
** @return mixed|Object
*/
function find($id, $options = array()) {
if ($id == 'all') {
return $this->_decode($this->_get($options));
}
return $this->_decode($this->_get(array_merge($options, array('id' => $id))));
}
/**
** Calls a specified custom method with the GET verb on the current object via:
**
** @code
** GET /collection/id/method.xml
** GET /collection/id/method.xml?attr=value
** @endcode
**
** @param string $method The custom method name
** @param assoc $options Key/value pairs for query string parameters
** @return Object A simple object (StdClass) representing the result body (passed to the serializer)
*/
function get($method, $options = array()) {
return $this->_decode_object($this->_get(array_merge($options, array('action' => $method, 'id' => $this->_get_id()))));
}
/**
** Calls a specified custom method with the POST verb on the current object via:
**
** @code
** POST /collection/id/method.xml
** POST /collection/id/method.xml?attr=value
** @endcode
**
** @param string $method The custom method name
** @param assoc $options Key/value pairs for query string parameters
** @param string $body The body to send for the request
** @return Object A simple object (StdClass) representing the result body (passed to the serializer)
*/
function post ($method, $options = array(), $body = '') {
return $this->_decode_object($this->_post(array_merge($options, array('action' => $method, 'id' => $this->_get_id())), $this->_encode($body)));
}
/**
** Calls a specified custom method with the PUT verb on the current object via:
**
** @code
** PUT /collection/id/method.xml
** PUT /collection/id/method.xml?attr=value
** @endcode
**
** @param string $method The custom method name
** @param assoc $options Key/value pairs for query string parameters
** @param string $body The body to send for the request
** @return Object A simple object (StdClass) representing the result body (passed to the serializer)
*/
function put($method, $options = array(), $body = '') {
return $this->_decode_object($this->_put(array_merge($options, array('action' => $method, 'id' => $this->_get_id())), $this->_encode($body)));
}
/**
** Calls a specified custom method with the DELETE verb on the current object via:
**
** @code
** DELETE /collection/id/method.xml
** DELETE /collection/id/method.xml?attr=value
** @endcode
**
** @param string $method The custom method name
** @param assoc $options Key/value pairs for query string parameters
** @return Object A simple object (StdClass) representing the result body (passed to the serializer)
*/
function delete($method, $options = array()) {
return $this->_decode_object($this->_delete(array_merge($options, array('action' => $method, 'id' => $this->_get_id()))));
}
/// Returns the data as a simple object
function to_object() {
return (object) $this->_get_data();
}
/// Returns wether this object has never been saved
function is_new_record() {
return $this->_get_id();
}
/// Generate URL based on object identity and options as query string parameters
protected function _url_for($options) {
if (is_string($options))
return $options;
if (isset($options['base_uri'])) {
$url = $this->site . $options['base_uri'];
}
elseif (isset($options['base_url'])) {
$url = $options['base_url'];
}
else {
$url = $this->site . $this->collection_name;
if (isset($options['id']) && $options['id']) {
$url .= "/" . $options['id'];
}
if (isset($options['action']) && $options['action']) {
$url .= "/" . $options['action'];
}
}
if ($this->serializer->extension) {
$url .= '.' . $this->serializer->extension;
}
$options = $this->_remove_path_keys($options);
if ($options) {
$url .= '?' . http_build_query($options);
}
return $url;
}
protected function _remove_path_keys($options) {
if (array_key_exists('id', $options)) unset($options['id']);
if (array_key_exists('action', $options)) unset($options['action']);
if (array_key_exists('base_uri', $options)) unset($options['base_uri']);
if (array_key_exists('base_url', $options)) unset($options['base_url']);
return $options;
}
protected function _encode($data) {
}
protected function _decode($text, $options = array()) {
// skip decoding if text is FALSE
if ($text === false)
return $text;
$retval = $this->serializer->decode($text, $options);
if ($this->serializer->error_code) {
$this->error_code = $this->serializer->error_code;
$this->error_message = "Serialization error " . $this->error_code;
}
// If we just decode an error message passed through HTTP, convert it to our error message.
if ($this->error_code >= 400) {
$this->error_message = implode("\r\n", $retval);
return false;
}
return $retval;
}
protected function _decode_object($text) {
return $this->_decode($text, array('raw' => true));
}
protected function _get($options) {
$retval = $this->transporter->get($this->_url_for($options));
$this->error_code = $this->transporter->error_code ? $this->transporter->error_code : ($this->transporter->http_code >= 400 ? $this->transporter->http_code : 0);
return $retval;
}
protected function _post($options, $data) {
$retval = $this->transporter->post($this->_url_for($options), $data);
$this->error_code = $this->transporter->error_code ? $this->transporter->error_code : ($this->transporter->http_code >= 400 ? $this->transporter->http_code : 0);
return $retval;
}
protected function _put($options, $data) {
$retval = $this->transporter->put($this->_url_for($options), $data);
$this->error_code = $this->transporter->error_code ? $this->transporter->error_code : ($this->transporter->http_code >= 400 ? $this->transporter->http_code : 0);
return $retval;
}
protected function _delete($options) {
$retval = $this->transporter->delete($this->_url_for($options));
$this->error_code = $this->transporter->error_code ? $this->transporter->error_code : ($this->transporter->http_code >= 400 ? $this->transporter->http_code : 0);
return $retval;
}
/*
* Sets multiple attributes for the record
*/
protected function _set_data($data) {
$this->_data = $data;
}
/*
* Sets an attribute for the record
*/
protected function _set_attribute($name, $value) {
$this->_data[$name] = $value;
}
/*
* Returns the attributes of the record
*/
protected function _get_data() {
return $this->_data;
}
/*
* Returns the id of the record or null if it is a freshly created one
*/
protected function _get_id() {
$data = $this->_get_data();
return isset($data['id']) ? $data['id'] : null;
}
/**
** Attribute style getter
*/
function __get($k) {
$data = $this->_get_data();
if (array_key_exists($k, $data)) {
return $data[$k];
}
return $this->{$k};
}
/**
** Attribute style setter
*/
function __set($k, $v) {
$data = $this->_get_data();
if (array_key_exists($k, $data)) {
$this->_set_attribute($k, $v);
return;
}
$this->{$k} = $v;
}
/**
** Chainable setter
*/
function set($k, $v = false) {
if (!$v && is_array($k)) {
foreach ($k as $key => $value) {
$this->_set_attribute($key, $value);
}
}
else {
$this->_set_attribute($k, $v);
}
return $this;
}
/**
** Return the string representation of this object.
** It will return the value of the first attribute present that is named after the ones
** stored in the array $to_string_attributes.
*/
public function __toString() {
$data = $this->_get_data();
foreach($this->to_string_attributes as $attribute_name) {
if (isset($data[$attribute_name]))
return $data[$attribute_name];
}
return parent::__toString();
}
}
?>