├── .travis.yml ├── LICENSE ├── README.textile ├── composer.json ├── examples └── redmine.php ├── lib ├── ActiveResource.php └── ActiveResource │ └── ActiveResource.php ├── phpunit.xml.dist └── tests ├── ActiveResourceTest.php └── bootstrap.php /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.4 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | - 7.1 8 | - 7.2 9 | - 7.3 10 | 11 | before_script: 12 | - composer install --no-interaction --prefer-source 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 Johnny Broadway 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. PHP ActiveResource 2 | 3 | Click here to lend your support to: phpactiveresource and make a donation at www.pledgie.com ! 4 | 5 | This is a PHP class for accessing Ruby on Rails REST APIs in an ActiveResource style of coding. The benefit is easier use of RoR-based REST services without having to roll your own CURL-based client each time. Hopefully this class saves a few people some time coding in PHP against RoR-based REST services. It's by no means an exhaustive port, and some methods are missing, but it does try to cover all the basics. 6 | 7 | Note: You will need the php curl extension installed on your system. On Ubuntu, you can install it via: 8 |
sudo apt-get install php5-curl
9 | 10 | h2. Usage 11 | 12 | h3. With Composer 13 | 14 | Create a composer.json file with the following: 15 | 16 |
 17 | {
 18 | 	"require": {
 19 | 		"phpactiveresource/phpactiveresource": "dev-master"
 20 | 	}
 21 | }
 22 | 
23 | 24 | Now load the script via Composer's autoloader: 25 | 26 |
 27 | 
 41 | 
42 | 43 | h3. Without Composer 44 | 45 |
 46 |  'Joe Cocker', 'title' => 'A Little Help From My Friends'));
 57 | $song->save ();
 58 | 
 59 | // fetch and update an item, chaining statements
 60 | $song->find (44)->set ('title', 'The River')->save ();
 61 | 
 62 | // fetch and update, line by line
 63 | $song->find (44);
 64 | $song->title = 'The River';
 65 | $song->save ();
 66 | 
 67 | // get all songs
 68 | $songs = $song->find ('all');
 69 | 
 70 | // delete a song
 71 | $song->find (44);
 72 | $song->destroy ();
 73 | 
 74 | // custom method
 75 | $songs = $song->get ('by_year', array ('year' => 1999));
 76 | 
 77 | ?>
 78 | 
79 | 80 | h2. Extra URL params 81 | 82 | If you want to add extra params to the end of the url eg: an API key, you can set $extra_params 83 | 84 |
 85 | 
 95 | 
 96 | h2. Extra Http Headers
 97 | 
 98 | If you need to add extra http request headers you can set $request_headers
 99 | 
100 | 
101 | 
111 | 
112 | See the Github Wiki pages for more examples and documentation.


--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"name": "phpactiveresource/phpactiveresource",
 3 | 	"type": "library",
 4 | 	"description": "A PHP client library for easily accessing Ruby on Rails-based REST services.",
 5 | 	"keywords": ["activeresource","rails","ruby","ruby on rails","rest"],
 6 | 	"homepage": "https://github.com/jbroadway/phpactiveresource",
 7 | 	"license": "MIT",
 8 | 	"authors": [
 9 | 		{
10 | 			"name": "Johnny Broadway",
11 | 			"email": "johnny@johnnybroadway.com",
12 | 			"homepage": "http://www.johnnybroadway.com/"
13 | 		}
14 | 	],
15 | 	"require": {
16 | 		"php": ">=5.3.6"
17 | 	},
18 | 	"require-dev": {
19 | 		"phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5"
20 | 	},
21 | 	"autoload": {
22 | 		"psr-0": {
23 | 			"ActiveResource": "lib/"
24 | 		}
25 | 	}
26 | }
27 | 


--------------------------------------------------------------------------------
/examples/redmine.php:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env php
 2 | find(false, array('assigned_to' => 'Redmine Admin'));
13 | print_r($issues);
14 | 
15 | ?>
16 | 


--------------------------------------------------------------------------------
/lib/ActiveResource.php:
--------------------------------------------------------------------------------
1 | 


--------------------------------------------------------------------------------
/lib/ActiveResource/ActiveResource.php:
--------------------------------------------------------------------------------
  1 |  'Joe Cocker', 'title' => 'A Little Help From My Friends'));
 25 |  *     $song->save ();
 26 |  *     
 27 |  *     // fetch and update an item
 28 |  *     $song->find (44)->set ('title', 'The River')->save ();
 29 |  *     
 30 |  *     // line by line
 31 |  *     $song->find (44);
 32 |  *     $song->title = 'The River';
 33 |  *     $song->save ();
 34 |  *     
 35 |  *     // get all songs
 36 |  *     $songs = $song->find ('all');
 37 |  *     
 38 |  *     // delete a song
 39 |  *     $song->find (44);
 40 |  *     $song->destroy ();
 41 |  *     
 42 |  *     // custom method
 43 |  *     $songs = $song->get ('by_year', array ('year' => 1999));
 44 |  *     
 45 |  *     ?>
 46 |  *
 47 |  * @author John Luxford 
 48 |  * @version 0.14 beta
 49 |  * @license http://opensource.org/licenses/lgpl-2.1.php
 50 |  */
 51 | class ActiveResource {
 52 | 	/**
 53 | 	 * The REST site address, e.g., http://user:pass@domain:port/
 54 | 	 */
 55 | 	public $site = false;
 56 | 
 57 | 	/**
 58 | 	 * Add any extra params to the end of the url eg: API key
 59 | 	 */
 60 | 	public $extra_params = false;
 61 | 
 62 | 	/**
 63 | 	 * HTTP Basic Authentication user
 64 | 	 */
 65 | 	public $user = null;
 66 | 
 67 | 	/**
 68 | 	 * HTTP Basic Authentication password
 69 | 	 */	
 70 | 	public $password = null;
 71 | 	
 72 | 	/**
 73 | 	 * The remote collection, e.g., person or thing
 74 | 	 */
 75 | 	public $element_name = false;
 76 | 
 77 | 	/**
 78 | 	 * Pleural form of the element name, e.g., people or things
 79 | 	 */
 80 | 	public $element_name_plural = '';
 81 | 
 82 | 	/**
 83 | 	 * The data of the current object, accessed via the anonymous get/set methods.
 84 | 	 */
 85 | 	private $_data = array ();
 86 | 
 87 | 	/**
 88 | 	 * An error message if an error occurred.
 89 | 	 */
 90 | 	public $error = false;
 91 | 
 92 | 	/**
 93 | 	 * The error number if an error occurred.
 94 | 	 */
 95 | 	public $errno = false;
 96 | 
 97 | 	/**
 98 | 	 * The request that was sent to the server.
 99 | 	 */
100 | 	public $request_body = '';
101 | 
102 | 	/**
103 | 	 * The request headers that was sent to the server.
104 | 	 */
105 | 	public $request_headers = array ();
106 | 
107 | 	/**
108 | 	 * The complete URL that the request was sent to.
109 | 	 */
110 | 	public $request_uri = '';
111 | 
112 | 	/**
113 | 	 * The request method sent to the server.
114 | 	 */
115 | 	public $request_method = '';
116 | 
117 | 	/**
118 | 	 * The response code returned from the server.
119 | 	 */
120 | 	public $response_code = false;
121 | 
122 | 	/**
123 | 	 * The raw response headers sent from the server.
124 | 	 */
125 | 	public $response_headers = '';
126 | 
127 | 	/**
128 | 	 * The response body sent from the server.
129 | 	 */
130 | 	public $response_body = '';
131 | 
132 | 	/**
133 | 	 * The format requests should use to send data (url or xml).
134 | 	 */
135 | 	public $request_format = 'url';
136 | 
137 | 	/**
138 | 	 * Corrections to improper pleuralizations.
139 | 	 */
140 | 	public $pleural_corrections = array (
141 | 		'persons' => 'people',
142 | 		'peoples' => 'people',
143 | 		'mans' => 'men',
144 | 		'mens' => 'men',
145 | 		'womans' => 'women',
146 | 		'womens' => 'women',
147 | 		'childs' => 'children',
148 | 		'childrens' => 'children',
149 | 		'sheeps' => 'sheep',
150 | 		'octopuses' => 'octopi',
151 | 		'quizs' => 'quizzes',
152 | 		'axises' => 'axes',
153 | 		'buffalos' => 'buffaloes',
154 | 		'tomatos' => 'tomatoes',
155 | 		'potatos' => 'potatoes',
156 | 		'oxes' => 'oxen',
157 | 		'mouses' => 'mice',
158 | 		'matrixes' => 'matrices',
159 | 		'vertexes' => 'vertices',
160 | 		'indexes' => 'indices',
161 | 	);
162 | 
163 | 	/**
164 | 	 * Constructor method.
165 | 	 */
166 | 	public function __construct ($data = array ()) {
167 | 		$this->_data = $data;
168 | 		// Allow class-defined element name or use class name if not defined
169 | 		$this->element_name = $this->element_name ? $this->element_name : strtolower (get_class ($this));
170 | 
171 | 		// Detect for namespaces, and take just the class name
172 | 		if (stripos ($this->element_name, '\\')) {
173 | 			$classItems = explode ('\\', $this->element_name);
174 | 			$this->element_name = end ($classItems);
175 | 		}
176 | 
177 | 		// Get the plural name after removing namespaces
178 | 		$this->element_name_plural = $this->pluralize ($this->element_name);
179 | 
180 | 		// if configuration file (config.ini.php) exists use it (overwrite class properties/attribute values).
181 | 		$config_file_path = dirname (__FILE__) . '/' . 'config.ini.php';
182 | 		if (file_exists ($config_file_path)) {
183 | 			$properties = parse_ini_file ($config_file_path);
184 | 			foreach ($properties as $property => $value )
185 | 				$this->{$property} = $value;
186 | 		}
187 | 	}
188 | 
189 | 	/**
190 | 	 * Pluralize the element name.
191 | 	 */
192 | 	public function pluralize ($word) {
193 | 		$word .= 's';
194 | 		$word = preg_replace ('/(x|ch|sh|ss])s$/', '\1es', $word);
195 | 		$word = preg_replace ('/ss$/', 'ses', $word);
196 | 		$word = preg_replace ('/([ti])ums$/', '\1a', $word);
197 | 		$word = preg_replace ('/sises$/', 'ses', $word);
198 | 		$word = preg_replace ('/([^aeiouy]|qu)ys$/', '\1ies', $word);
199 | 		$word = preg_replace ('/(?:([^f])fe|([lr])f)s$/', '\1\2ves', $word);
200 | 		$word = preg_replace ('/ieses$/', 'ies', $word);
201 | 		if (isset ($this->pleural_corrections[$word])) {
202 | 			return $this->pleural_corrections[$word];
203 | 		}
204 | 		return $word;
205 | 	}
206 | 
207 | 	/**
208 | 	 * For backwards-compatibility.
209 | 	 */
210 | 	public function pleuralize ($word) {
211 | 		return $this->pluralize ($word);
212 | 	}
213 | 
214 | 	/**
215 | 	 * Saves a new record or updates an existing one via:
216 | 	 *
217 | 	 *     POST /collection.xml
218 | 	 *     PUT  /collection/id.xml
219 | 	 */
220 | 	public function save () {
221 | 		if (isset ($this->_data['id'])) {
222 | 			return $this->_send_and_receive ($this->site . $this->element_name_plural . '/' . $this->_data['id'] . '.xml', 'PUT', $this->_data); // update
223 | 		}
224 | 		return $this->_send_and_receive ($this->site . $this->element_name_plural . '.xml', 'POST', $this->_data); // create
225 | 	}
226 | 
227 | 	/**
228 | 	 * Deletes a record via:
229 | 	 *
230 | 	 *     DELETE /collection/id.xml
231 | 	 */
232 | 	public function destroy () {
233 | 		return $this->_send_and_receive ($this->site . $this->element_name_plural . '/' . $this->_data['id'] . '.xml', 'DELETE');
234 | 	}
235 | 
236 | 	/**
237 | 	 * Finds a record or records via:
238 | 	 *
239 | 	 *     GET /collection/id.xml
240 | 	 *     GET /collection.xml
241 | 	 */
242 | 	public function find ($id = false, $options = array ()) {
243 | 		if (! $id) {
244 | 			$id = $this->_data['id'];
245 | 		}
246 | 		$options_string = '';
247 | 		if (count ($options) > 0) {
248 | 			$options_string = '?' . http_build_query ($options);
249 | 		}
250 | 		if ($id == 'all' || $id == false) {
251 | 			// URL/plural.xml?someparam=1
252 | 			$url = $this->site . $this->element_name_plural . '.xml';
253 | 			return $this->_send_and_receive ($url . $options_string, 'GET');
254 | 		}
255 | 
256 | 		// URL/plural/id.xml?someparam=1
257 | 		return $this->_send_and_receive ($this->site . $this->element_name_plural . '/' . $id . '.xml' . $options_string, 'GET');
258 | 	}
259 | 
260 | 	/**
261 | 	 * Gets a specified custom method on the current object via:
262 | 	 *
263 | 	 *     GET /collection/id/method.xml
264 | 	 *     GET /collection/id/method.xml?attr=value
265 | 	 */
266 | 	public function get ($method, $options = array ()) {
267 | 		$req = $this->site . $this->element_name_plural;
268 |         if (isset ($this->_data['id']) && $this->_data['id']) { 
269 |           $req .= '/' . $this->_data['id'];
270 |         }
271 |         $req .= '/' . $method . '.xml';
272 | 		if (count ($options) > 0) {
273 | 			$req .= '?' . http_build_query ($options);
274 | 		}
275 | 		return $this->_send_and_receive ($req, 'GET');
276 | 	}
277 | 
278 | 	/**
279 | 	 * Posts to a specified custom method on the current object via:
280 | 	 *
281 | 	 *     POST /collection/id/method.xml
282 | 	 */
283 | 	public function post ($method, $options = array (), $start_tag = false) {
284 | 		$req = $this->site . $this->element_name_plural;
285 |         if (isset ($this->_data['id']) && $this->_data['id']) {
286 |           $req .= '/' . $this->_data['id'];
287 |         }
288 |         $req .= '/' . $method . '.xml';
289 | 		return $this->_send_and_receive ($req, 'POST', $options, $start_tag);
290 | 	}
291 | 
292 | 	/**
293 | 	 * Puts to a specified custom method on the current object via:
294 | 	 *
295 | 	 *     PUT /collection/id/method.xml
296 | 	 */
297 | 	public function put ($method, $options = array (), $options_as_xml = false, $start_tag = false) {
298 | 		$req = $this->site . $this->element_name_plural;
299 |         if (isset ($this->_data['id']) && $this->_data['id']) { 
300 |         	$req .= '/' . $this->_data['id'];
301 |         }
302 |         $req .= '/' . $method . '.xml';
303 | 		if ($options_as_xml) {
304 | 			return $this->_send_and_receive ($req, 'PUT', $options, $start_tag);
305 | 		}
306 | 		if (count ($options) > 0) {
307 | 			$req .= '?' . http_build_query ($options);
308 | 		}
309 | 		return $this->_send_and_receive ($req, 'PUT');
310 | 	}
311 | 
312 | 	/**
313 | 	 * Simple recursive function to build an XML response.
314 | 	 */
315 | 	public function _build_xml ($k, $v) {
316 | 		if (is_object ($v) && strtolower (get_class ($v)) == 'simplexmlelement') {
317 | 			return preg_replace ('/<\?xml(.*?)\?>\n*/', '', $v->asXML ());
318 | 		}
319 | 		$res = '';
320 | 		$attrs = '';
321 | 		if (! is_numeric ($k)) {
322 | 			$res = '<' . $k . '{{attributes}}>';
323 | 		}
324 | 		if (is_object ($v)) {
325 | 			$v = (array) $v;
326 | 		}
327 | 		if (is_array ($v)) {
328 | 			foreach ($v as $key => $value) {
329 | 				// handle attributes of repeating tags
330 | 				if (is_numeric ($key) && is_array ($value)) {
331 | 					foreach ($value as $sub_key => $sub_value) {
332 | 						if (strpos ($sub_key, '@') === 0) {
333 | 							$attrs .= ' ' . substr ($sub_key, 1) . '="' . $this->_xml_entities ($sub_value) . '"';
334 | 							unset ($value[$sub_key]);
335 | 							continue;
336 | 						}
337 | 					}
338 | 				}
339 | 
340 | 				if (strpos ($key, '@') === 0) {
341 | 					$attrs .= ' ' . substr ($key, 1) . '="' . $this->_xml_entities ($value) . '"';
342 | 					continue;
343 | 				}
344 | 				$res .= $this->_build_xml ($key, $value);
345 | 				$keys = array_keys ($v);
346 | 				if (is_numeric ($key) && $key !== array_pop ($keys)) {
347 | 					// reset attributes on repeating tags
348 | 					if (is_array ($value)) {
349 | 						$res = str_replace ('<' . $k . '{{attributes}}>', '<' . $k . $attrs . '>', $res);
350 | 						$attrs = '';
351 | 					}
352 | 					$res .= '\n<" . $k . '{{attributes}}>';
353 | 				}
354 | 			}
355 | 		} else {
356 | 			$res .= $this->_xml_entities ($v);
357 | 		}
358 | 		if (! is_numeric ($k)) {
359 | 			$res .= '\n";
360 | 		}
361 | 		$res = str_replace ('<' . $k . '{{attributes}}>', '<' . $k . $attrs . '>', $res);
362 | 		return $res;
363 | 	}
364 | 
365 | 	/**
366 | 	 * Returns the unicode value of the string
367 | 	 *
368 | 	 * @param string $c The source string
369 | 	 * @param integer $i The index to get the char from (passed by reference for use in a loop)
370 | 	 * @return integer The value of the char at $c[$i]
371 | 	 * @author kerry at shetline dot com
372 | 	 * @author Dom Hastings - modified to suit my needs
373 | 	 * @see http://www.php.net/manual/en/function.ord.php#78032
374 | 	 */
375 | 	public function _unicode_ord (&$c, &$i = 0) {
376 | 		// get the character length
377 | 		$l = strlen($c);
378 | 		// copy the offset
379 | 		$index = $i;
380 | 		
381 | 		// check it's a valid offset
382 | 		if ($index >= $l) {
383 | 			return false;
384 | 		}
385 | 		
386 | 		// check the value
387 | 		$o = ord($c[$index]);
388 | 		
389 | 		// if it's ascii
390 | 		if ($o <= 0x7F) {
391 | 			return $o;
392 | 		
393 | 		// not sure what it is...
394 | 		} elseif ($o < 0xC2) {
395 | 			return false;
396 | 		
397 | 		// if it's a two-byte character	
398 | 		} elseif ($o <= 0xDF && $index < $l - 1) {
399 | 			$i += 1;
400 | 			return ($o & 0x1F) <<	6 | (ord($c[$index + 1]) & 0x3F);
401 | 		
402 | 		// three-byte
403 | 		} elseif ($o <= 0xEF && $index < $l - 2) {
404 | 			$i += 2;
405 | 			return ($o & 0x0F) << 12 | (ord($c[$index + 1]) & 0x3F) << 6 | (ord($c[$index + 2]) & 0x3F);
406 | 			
407 | 		// four-byte
408 | 		} elseif ($o <= 0xF4 && $index < $l - 3) {
409 | 			$i += 3;
410 | 			return ($o & 0x0F) << 18 | (ord($c[$index + 1]) & 0x3F) << 12 | (ord($c[$index + 2]) & 0x3F) << 6 | (ord($c[$index + 3]) & 0x3F);
411 | 			
412 | 		// not sure what it is...
413 | 		} else {
414 | 			return false;
415 | 		}
416 | 	}
417 | 
418 | 	/**
419 | 	 * Makes the specified string XML-safe
420 | 	 *
421 | 	 * @param string $s
422 | 	 * @param boolean $hex Whether or not to make hexadecimal entities (as opposed to decimal)
423 | 	 * @return string The XML-safe result
424 | 	 * @author Dom Hastings
425 | 	 * @see http://www.w3.org/TR/REC-xml/#sec-predefined-ent
426 | 	 */
427 | 	public function _xml_entities ($s, $hex = true) {
428 | 		// if the string is empty
429 | 		if (empty($s)) {
430 | 			// just return it
431 | 			return $s;
432 | 		}
433 | 		$s = (string) $s;
434 | 		
435 | 		// create the return string
436 | 		$r = '';
437 | 		// get the length
438 | 		$l = strlen($s);
439 | 		
440 | 		// iterate the string
441 | 		for ($i = 0; $i < $l; $i++) {
442 | 			// get the value of the character
443 | 			$o = $this->_unicode_ord($s, $i);
444 | 			
445 | 			// valid characters
446 | 			$v = (
447 | 				// \t \n  
\r 448 | ($o >= 9 && $o <= 13) || 449 | // ! 450 | ($o == 32) || ($o == 33) || 451 | // # $ % 452 | ($o >= 35 && $o <= 37) || 453 | // ( ) * + , - . / 454 | ($o >= 40 && $o <= 47) || 455 | // numbers 456 | ($o >= 48 && $o <= 57) || 457 | // : ; 458 | ($o == 58) || ($o == 59) || 459 | // = ? 460 | ($o == 61) || ($o == 63) || 461 | // @ 462 | ($o == 64) || 463 | // uppercase 464 | ($o >= 65 && $o <= 90) || 465 | // [ \ ] ^ _ ` 466 | ($o >= 91 && $o <= 96) || 467 | // lowercase 468 | ($o >= 97 && $o <= 122) || 469 | // { | } ~ 470 | ($o >= 123 && $o <= 126) 471 | ); 472 | 473 | // if it's valid, just keep it 474 | if ($v) { 475 | $r .= $s[$i]; 476 | 477 | // & 478 | } elseif ($o == 38) { 479 | $r .= '&'; 480 | 481 | // < 482 | } elseif ($o == 60) { 483 | $r .= '<'; 484 | 485 | // > 486 | } elseif ($o == 62) { 487 | $r .= '>'; 488 | 489 | // ' 490 | } elseif ($o == 39) { 491 | $r .= '''; 492 | 493 | // " 494 | } elseif ($o == 34) { 495 | $r .= '"'; 496 | 497 | // unknown, add it as a reference 498 | } elseif ($o > 0) { 499 | if ($hex) { 500 | $r .= '&#x'.strtoupper(dechex($o)).';'; 501 | 502 | } else { 503 | $r .= '&#'.$o.';'; 504 | } 505 | } 506 | } 507 | 508 | return $r; 509 | } 510 | 511 | /** 512 | * Build the request, call _fetch() and parse the results. 513 | */ 514 | public function _send_and_receive ($url, $method, $data = array (), $start_tag = false) { 515 | $params = ''; 516 | $el = $start_tag ? $start_tag : $this->element_name; // Singular this time 517 | if ($this->request_format == 'url') { 518 | foreach ($data as $k => $v) { 519 | if ($k != 'id' && $k != 'created-at' && $k != 'updated-at') { 520 | $params .= '&' . $el . '[' . str_replace ('-', '_', $k) . ']=' . rawurlencode ($v); 521 | } 522 | } 523 | $params = substr ($params, 1); 524 | } elseif ($this->request_format == 'xml') { 525 | $params = '<' . $el . ">\n"; 526 | foreach ($data as $k => $v) { 527 | if ($k != 'id' && $k != 'created-at' && $k != 'updated-at') { 528 | $params .= $this->_build_xml ($k, $v); 529 | } 530 | } 531 | $params .= ''; 532 | } 533 | 534 | if ($this->extra_params !== false) { 535 | $url = $url . $this->extra_params; 536 | } 537 | 538 | $this->request_body = $params; 539 | $this->request_uri = $url; 540 | $this->request_method = $method; 541 | 542 | $res = $this->_fetch ($url, $method, $params); 543 | 544 | if ($res === false) { 545 | return $this; 546 | } 547 | 548 | // Keep splitting off any top headers until we get to the (XML) body: 549 | while (strpos($res, "HTTP/") === 0) { 550 | list ($headers, $res) = explode ("\r\n\r\n", $res, 2); 551 | $this->response_headers = $headers; 552 | $this->response_body = $res; 553 | if (preg_match ('/HTTP\/[0-9]\.[0-9] ([0-9]+)/', $headers, $regs)) { 554 | $this->response_code = $regs[1]; 555 | } else { 556 | $this->response_code = false; 557 | } 558 | 559 | if (! $res) { 560 | return $this; 561 | } elseif ($res == ' ') { 562 | $this->error = 'Empty reply'; 563 | return $this; 564 | } 565 | } 566 | 567 | // parse XML response 568 | $xml = new SimpleXMLElement ($res); 569 | 570 | // normalize xml element name in case rails ressource contains an underscore 571 | if (str_replace ('-', '_', $xml->getName ()) == $this->element_name_plural) { 572 | // multiple 573 | $res = array (); 574 | $cls = get_class ($this); 575 | foreach ($xml->children () as $child) { 576 | $obj = new $cls; 577 | foreach ((array) $child as $k => $v) { 578 | $k = str_replace ('-', '_', $k); 579 | if (isset ($v['nil']) && $v['nil'] == 'true') { 580 | continue; 581 | } else { 582 | $obj->_data[$k] = $v; 583 | } 584 | } 585 | $res[] = $obj; 586 | } 587 | return $res; 588 | } elseif ($xml->getName () == 'errors') { 589 | // parse error message 590 | $this->error = $xml->error; 591 | $this->errno = $this->response_code; 592 | return false; 593 | } 594 | 595 | foreach ((array) $xml as $k => $v) { 596 | $k = str_replace ('-', '_', $k); 597 | if (isset ($v['nil']) && $v['nil'] == 'true') { 598 | continue; 599 | } else { 600 | $this->_data[$k] = $v; 601 | } 602 | } 603 | return $this; 604 | } 605 | 606 | /** 607 | * Fetch the specified request via cURL. 608 | */ 609 | public function _fetch ($url, $method, $params) { 610 | if (! extension_loaded ('curl')) { 611 | $this->error = 'cURL extension not loaded.'; 612 | return false; 613 | } 614 | 615 | $ch = curl_init (); 616 | curl_setopt ($ch, CURLOPT_URL, $url); 617 | curl_setopt ($ch, CURLOPT_MAXREDIRS, 3); 618 | curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, 0); 619 | curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); 620 | curl_setopt ($ch, CURLOPT_VERBOSE, 0); 621 | curl_setopt ($ch, CURLOPT_HEADER, 1); 622 | curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 10); 623 | curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); 624 | curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0); 625 | 626 | /* HTTP Basic Authentication */ 627 | if ($this->user && $this->password) { 628 | curl_setopt ($ch, CURLOPT_USERPWD, $this->user . ":" . $this->password); 629 | } 630 | 631 | if ($this->request_format == 'xml') { 632 | $this->request_headers = array_merge ($this->request_headers, array ("Expect:", "Content-Type: text/xml", "Length: " . strlen ($params))); 633 | } 634 | switch ($method) { 635 | case 'POST': 636 | curl_setopt ($ch, CURLOPT_POST, 1); 637 | curl_setopt ($ch, CURLOPT_POSTFIELDS, $params); 638 | break; 639 | case 'DELETE': 640 | curl_setopt ($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); 641 | break; 642 | case 'PUT': 643 | curl_setopt ($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); 644 | curl_setopt ($ch, CURLOPT_POSTFIELDS, $params); 645 | break; 646 | case 'GET': 647 | default: 648 | break; 649 | } 650 | 651 | if (count ($this->request_headers)) { 652 | curl_setopt ($ch, CURLOPT_HTTPHEADER, $this->request_headers); 653 | } 654 | 655 | $res = curl_exec ($ch); 656 | 657 | $http_code = curl_getinfo ($ch, CURLINFO_HTTP_CODE); 658 | 659 | // Check HTTP status code for denied access 660 | if ($http_code == 401) { 661 | $this->errno = $http_code; 662 | $this->error = "HTTP Basic: Access denied."; 663 | curl_close ($ch); 664 | return false; 665 | } 666 | 667 | // Check HTTP status code for rate limit 668 | if ($http_code == 429) { 669 | if (preg_match ('/Retry-After: ([0-9]+)/', $res, $retry_after)) { 670 | sleep(intval($retry_after[1])); 671 | return $this->_fetch ($url, $method, $params); 672 | } 673 | $this->errno = $http_code; 674 | $this->error = "Too Many Requests"; 675 | curl_close ($ch); 676 | return false; 677 | } 678 | 679 | if (! $res) { 680 | $this->errno = curl_errno ($ch); 681 | $this->error = curl_error ($ch); 682 | curl_close ($ch); 683 | return false; 684 | } 685 | 686 | curl_close ($ch); 687 | return $res; 688 | } 689 | 690 | /** 691 | * Getter for internal object data. 692 | */ 693 | public function __get ($k) { 694 | if (isset ($this->_data[$k])) { 695 | return $this->_data[$k]; 696 | } 697 | return $this->{$k}; 698 | } 699 | 700 | /** 701 | * Setter for internal object data. 702 | */ 703 | public function __set ($k, $v) { 704 | if (isset ($this->_data[$k])) { 705 | $this->_data[$k] = $v; 706 | return; 707 | } 708 | $this->{$k} = $v; 709 | } 710 | 711 | /** 712 | * Quick setter for chaining methods. 713 | */ 714 | public function set ($k, $v = false) { 715 | if (! $v && is_array ($k)) { 716 | foreach ($k as $key => $value) { 717 | $this->_data[$key] = $value; 718 | } 719 | } else { 720 | $this->_data[$k] = $v; 721 | } 722 | return $this; 723 | } 724 | } 725 | 726 | ?> 727 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/ActiveResourceTest.php: -------------------------------------------------------------------------------- 1 | 'bar')); 9 | 10 | $this->assertEquals ($t->foo, 'bar'); 11 | $t->foo = 'asdf'; 12 | $this->assertEquals ($t->foo, 'asdf'); 13 | $this->assertEquals ($t->_data, array ('foo' => 'asdf')); 14 | $this->assertEquals ($t->element_name_plural, 'tests'); 15 | } 16 | 17 | function test_build_xml () { 18 | $t = new Test; 19 | 20 | // value only 21 | $this->assertEquals ($t->_build_xml (0, 'foo'), 'foo'); 22 | 23 | // no initial tag 24 | $this->assertEquals ($t->_build_xml (0, array ('foo' => 'bar')), "bar\n"); 25 | 26 | // tag and value 27 | $this->assertEquals ($t->_build_xml ('foo', 'bar'), "bar\n"); 28 | 29 | // sub-tags 30 | $this->assertEquals ($t->_build_xml ('foo', array ('bar' => 'asdf')), "asdf\n\n"); 31 | 32 | // attributes 33 | $this->assertEquals ($t->_build_xml ('foo', array ('@bar' => 'asdf')), "\n"); 34 | 35 | // attributes and content 36 | $this->assertEquals ($t->_build_xml ('foo', array ('@bar' => 'asdf', 'bar')), "bar\n"); 37 | 38 | // repeating tags 39 | $this->assertEquals ( 40 | $t->_build_xml ('foo', array (array ('bar'), array ('bar'))), 41 | "bar\nbar\n" 42 | ); 43 | 44 | // repeating tags with attributes 45 | $this->assertEquals ( 46 | $t->_build_xml ('foo', array (array ('@id' => 'one', 'bar'), array ('@id' => 'two', 'bar'))), 47 | "bar\nbar\n" 48 | ); 49 | 50 | // repeating tags with attributes and sub-tags 51 | $this->assertEquals ( 52 | $t->_build_xml ('foo', array (array ('@id' => 'one', 'bar' => 'asdf'), array ('@id' => 'two', 'bar' => 'qwerty'))), 53 | "asdf\n\nqwerty\n\n" 54 | ); 55 | 56 | // starting from a SimpleXMLElement 57 | $xml = new SimpleXMLElement ('what'); 58 | $this->assertEquals ($t->_build_xml (0, $xml), "what\n"); 59 | 60 | // testing objects converted to arrays 61 | $this->assertEquals ( 62 | $t->_build_xml ('foo', array ((object) array ('bar' => 'asdf'))), 63 | "asdf\n\n" 64 | ); 65 | 66 | // testing objects converted to arrays 67 | $this->assertEquals ( 68 | $t->_build_xml ('foo', array ((object) array ('bar' => (object) array ('asdf' => 'qwerty')))), 69 | "qwerty\n\n\n" 70 | ); 71 | } 72 | 73 | function test_pleuralize () { 74 | $t = new Test; 75 | 76 | $this->assertEquals ($t->pluralize ('person'), 'people'); 77 | $this->assertEquals ($t->pluralize ('people'), 'people'); 78 | $this->assertEquals ($t->pluralize ('man'), 'men'); 79 | $this->assertEquals ($t->pluralize ('woman'), 'women'); 80 | $this->assertEquals ($t->pluralize ('women'), 'women'); 81 | $this->assertEquals ($t->pluralize ('child'), 'children'); 82 | $this->assertEquals ($t->pluralize ('sheep'), 'sheep'); 83 | $this->assertEquals ($t->pluralize ('octopus'), 'octopi'); 84 | $this->assertEquals ($t->pluralize ('virus'), 'viruses'); 85 | $this->assertEquals ($t->pluralize ('quiz'), 'quizzes'); 86 | $this->assertEquals ($t->pluralize ('axis'), 'axes'); 87 | $this->assertEquals ($t->pluralize ('axe'), 'axes'); 88 | $this->assertEquals ($t->pluralize ('buffalo'), 'buffaloes'); 89 | $this->assertEquals ($t->pluralize ('tomato'), 'tomatoes'); 90 | $this->assertEquals ($t->pluralize ('potato'), 'potatoes'); 91 | $this->assertEquals ($t->pluralize ('ox'), 'oxen'); 92 | $this->assertEquals ($t->pluralize ('mouse'), 'mice'); 93 | $this->assertEquals ($t->pluralize ('matrix'), 'matrices'); 94 | $this->assertEquals ($t->pluralize ('vertex'), 'vertices'); 95 | $this->assertEquals ($t->pluralize ('vortex'), 'vortexes'); 96 | $this->assertEquals ($t->pluralize ('index'), 'indices'); 97 | $this->assertEquals ($t->pluralize ('sandwich'), 'sandwiches'); 98 | $this->assertEquals ($t->pluralize ('mass'), 'masses'); 99 | $this->assertEquals ($t->pluralize ('fax'), 'faxes'); 100 | $this->assertEquals ($t->pluralize ('pin'), 'pins'); 101 | $this->assertEquals ($t->pluralize ('touch'), 'touches'); 102 | $this->assertEquals ($t->pluralize ('sash'), 'sashes'); 103 | $this->assertEquals ($t->pluralize ('bromium'), 'bromia'); 104 | $this->assertEquals ($t->pluralize ('prophecy'), 'prophecies'); 105 | $this->assertEquals ($t->pluralize ('crisis'), 'crises'); 106 | $this->assertEquals ($t->pluralize ('life'), 'lives'); 107 | $this->assertEquals ($t->pluralize ('wife'), 'wives'); 108 | $this->assertEquals ($t->pluralize ('song'), 'songs'); 109 | $this->assertEquals ($t->pluralize ('try'), 'tries'); 110 | $this->assertEquals ($t->pluralize ('tree'), 'trees'); 111 | $this->assertEquals ($t->pluralize ('tries'), 'tries'); 112 | $this->assertEquals ($t->pluralize ('entry'), 'entries'); 113 | $this->assertEquals ($t->pluralize ('entries'), 'entries'); 114 | } 115 | 116 | function test_xml_entities () { 117 | $t = new Test; 118 | 119 | $this->assertEquals ($t->_xml_entities ('asdf'), 'asdf'); 120 | $this->assertEquals ($t->_xml_entities ('<>'), '<>'); 121 | $this->assertEquals ($t->_xml_entities ('"'), '"'); 122 | $this->assertEquals ($t->_xml_entities ('\''), '''); 123 | $this->assertEquals ($t->_xml_entities ('ä'), 'ä'); 124 | $this->assertEquals ($t->_xml_entities ('£'), '£'); 125 | $this->assertEquals ($t->_xml_entities ('•'), '•'); 126 | $this->assertEquals ($t->_xml_entities ('À'), 'À'); 127 | $this->assertEquals ($t->_xml_entities ('à'), 'à'); 128 | $this->assertEquals ($t->_xml_entities ('Á'), 'Á'); 129 | $this->assertEquals ($t->_xml_entities ('á'), 'á'); 130 | $this->assertEquals ($t->_xml_entities ('Â'), 'Â'); 131 | $this->assertEquals ($t->_xml_entities ('â'), 'â'); 132 | $this->assertEquals ($t->_xml_entities ('Ã'), 'Ã'); 133 | $this->assertEquals ($t->_xml_entities ('ã'), 'ã'); 134 | $this->assertEquals ($t->_xml_entities ('Ä'), 'Ä'); 135 | $this->assertEquals ($t->_xml_entities ('ä'), 'ä'); 136 | $this->assertEquals ($t->_xml_entities ('Å'), 'Å'); 137 | $this->assertEquals ($t->_xml_entities ('å'), 'å'); 138 | $this->assertEquals ($t->_xml_entities ('Æ'), 'Æ'); 139 | $this->assertEquals ($t->_xml_entities ('æ'), 'æ'); 140 | $this->assertEquals ($t->_xml_entities ('Ç'), 'Ç'); 141 | $this->assertEquals ($t->_xml_entities ('ç'), 'ç'); 142 | $this->assertEquals ($t->_xml_entities ('Ð'), 'Ð'); 143 | $this->assertEquals ($t->_xml_entities ('ð'), 'ð'); 144 | $this->assertEquals ($t->_xml_entities ('È'), 'È'); 145 | $this->assertEquals ($t->_xml_entities ('è'), 'è'); 146 | $this->assertEquals ($t->_xml_entities ('É'), 'É'); 147 | $this->assertEquals ($t->_xml_entities ('é'), 'é'); 148 | $this->assertEquals ($t->_xml_entities ('Ê'), 'Ê'); 149 | $this->assertEquals ($t->_xml_entities ('ê'), 'ê'); 150 | $this->assertEquals ($t->_xml_entities ('Ë'), 'Ë'); 151 | $this->assertEquals ($t->_xml_entities ('ë'), 'ë'); 152 | $this->assertEquals ($t->_xml_entities ('Ì'), 'Ì'); 153 | $this->assertEquals ($t->_xml_entities ('ì'), 'ì'); 154 | $this->assertEquals ($t->_xml_entities ('Í'), 'Í'); 155 | $this->assertEquals ($t->_xml_entities ('í'), 'í'); 156 | $this->assertEquals ($t->_xml_entities ('Î'), 'Î'); 157 | $this->assertEquals ($t->_xml_entities ('î'), 'î'); 158 | $this->assertEquals ($t->_xml_entities ('Ï'), 'Ï'); 159 | $this->assertEquals ($t->_xml_entities ('ï'), 'ï'); 160 | $this->assertEquals ($t->_xml_entities ('Ñ'), 'Ñ'); 161 | $this->assertEquals ($t->_xml_entities ('ñ'), 'ñ'); 162 | $this->assertEquals ($t->_xml_entities ('Ò'), 'Ò'); 163 | $this->assertEquals ($t->_xml_entities ('ò'), 'ò'); 164 | $this->assertEquals ($t->_xml_entities ('Ó'), 'Ó'); 165 | $this->assertEquals ($t->_xml_entities ('ó'), 'ó'); 166 | $this->assertEquals ($t->_xml_entities ('Ô'), 'Ô'); 167 | $this->assertEquals ($t->_xml_entities ('ô'), 'ô'); 168 | $this->assertEquals ($t->_xml_entities ('Õ'), 'Õ'); 169 | $this->assertEquals ($t->_xml_entities ('õ'), 'õ'); 170 | $this->assertEquals ($t->_xml_entities ('Ö'), 'Ö'); 171 | $this->assertEquals ($t->_xml_entities ('ö'), 'ö'); 172 | $this->assertEquals ($t->_xml_entities ('Ø'), 'Ø'); 173 | $this->assertEquals ($t->_xml_entities ('ø'), 'ø'); 174 | $this->assertEquals ($t->_xml_entities ('Œ'), 'Œ'); 175 | $this->assertEquals ($t->_xml_entities ('œ'), 'œ'); 176 | $this->assertEquals ($t->_xml_entities ('ß'), 'ß'); 177 | $this->assertEquals ($t->_xml_entities ('Þ'), 'Þ'); 178 | $this->assertEquals ($t->_xml_entities ('þ'), 'þ'); 179 | $this->assertEquals ($t->_xml_entities ('Ù'), 'Ù'); 180 | $this->assertEquals ($t->_xml_entities ('ù'), 'ù'); 181 | $this->assertEquals ($t->_xml_entities ('Ú'), 'Ú'); 182 | $this->assertEquals ($t->_xml_entities ('ú'), 'ú'); 183 | $this->assertEquals ($t->_xml_entities ('Û'), 'Û'); 184 | $this->assertEquals ($t->_xml_entities ('û'), 'û'); 185 | $this->assertEquals ($t->_xml_entities ('Ü'), 'Ü'); 186 | $this->assertEquals ($t->_xml_entities ('ü'), 'ü'); 187 | $this->assertEquals ($t->_xml_entities ('Ý'), 'Ý'); 188 | $this->assertEquals ($t->_xml_entities ('ý'), 'ý'); 189 | $this->assertEquals ($t->_xml_entities ('Ÿ'), 'Ÿ'); 190 | $this->assertEquals ($t->_xml_entities ('ÿ'), 'ÿ'); 191 | $this->assertEquals ($t->_xml_entities ('Ć'), 'Ć'); 192 | $this->assertEquals ($t->_xml_entities ('ć'), 'ć'); 193 | $this->assertEquals ($t->_xml_entities ('Č'), 'Č'); 194 | $this->assertEquals ($t->_xml_entities ('č'), 'č'); 195 | $this->assertEquals ($t->_xml_entities ('Đ'), 'Đ'); 196 | $this->assertEquals ($t->_xml_entities ('đ'), 'đ'); 197 | $this->assertEquals ($t->_xml_entities ('Š'), 'Š'); 198 | $this->assertEquals ($t->_xml_entities ('š'), 'š'); 199 | $this->assertEquals ($t->_xml_entities ('Ž'), 'Ž'); 200 | $this->assertEquals ($t->_xml_entities ('ž'), 'ž'); 201 | } 202 | } 203 | 204 | ?> 205 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 8 | --------------------------------------------------------------------------------