├── .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 |
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 Luxford48 | * @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 .= '' . $k . ">\n<" . $k . '{{attributes}}>'; 353 | } 354 | } 355 | } else { 356 | $res .= $this->_xml_entities ($v); 357 | } 358 | if (! is_numeric ($k)) { 359 | $res .= '' . $k . ">\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