Skip to content

Instantly share code, notes, and snippets.

@onsails
Created November 26, 2010 17:01
Show Gist options
  • Save onsails/716958 to your computer and use it in GitHub Desktop.
Save onsails/716958 to your computer and use it in GitHub Desktop.
#!/usr/bin/env php
<?php
class ActiveResource {
/**
* The REST site address, e.g., http://user:pass@domain:port/
*/
var $site = false;
/**
* HTTP Basic Authentication user
*/
var $user = null;
/**
* HTTP Basic Authentication password
*/
var $password = null;
/**
* The remote collection, e.g., person or things
*/
var $element_name = false;
/**
* The data of the current object, accessed via the anonymous get/set methods.
*/
var $_data = array ();
/**
* An error message if an error occurred.
*/
var $error = false;
/**
* The error number if an error occurred.
*/
var $errno = false;
/**
* The request that was sent to the server.
*/
var $request_body = '';
/**
* The complete URL that the request was sent to.
*/
var $request_uri = '';
/**
* The request method sent to the server.
*/
var $request_method = '';
/**
* The response code returned from the server.
*/
var $response_code = false;
/**
* The raw response headers sent from the server.
*/
var $response_headers = '';
/**
* The response body sent from the server.
*/
var $response_body = '';
/**
* The format requests should use to send data (url or xml).
*/
var $request_format = 'url';
/**
* Constructor method.
*/
function __construct ($data = array ()) {
$this->_data = $data;
// Allow class-defined element name or use class name if not defined
$this->element_name = ($this->element_name ? $this->element_name . 's' : strtolower (get_class ($this)) . 's');
// if configuration file (config.ini.php) exists use it (overwrite class properties/attribute values).
$config_file_path = dirname (__FILE__) . '/' . 'config.ini.php';
if (file_exists ($config_file_path)) {
$properties = parse_ini_file ($config_file_path);
foreach ($properties as $property => $value )
$this->{$property} = $value;
}
}
/**
* Saves a new record or updates an existing one via:
*
* POST /collection.xml
* PUT /collection/id.xml
*/
function save () {
if (isset ($this->_data['id'])) {
return $this->_send_and_receive ($this->site . $this->element_name . '/' . $this->_data['id'] . '.xml', 'PUT', $this->_data); // update
}
return $this->_send_and_receive ($this->site . $this->element_name . '.xml', 'POST', $this->_data); // create
}
/**
* Deletes a record via:
*
* DELETE /collection/id.xml
*/
function destroy () {
return $this->_send_and_receive ($this->site . $this->element_name . '/' . $this->_data['id'] . '.xml', 'DELETE');
}
/**
* Finds a record or records via:
*
* GET /collection/id.xml
* GET /collection.xml
*/
function find ($id = false, $options = array ()) {
if (! $id) {
$id = $this->_data['id'];
}
if ($id == 'all') {
$url = $this->site . $this->element_name . '.xml';
if (count ($options) > 0) {
$url .= '?' . http_build_query ($options);
}
return $this->_send_and_receive ($url, 'GET');
}
return $this->_send_and_receive ($this->site . $this->element_name . '/' . $id . '.xml', 'GET');
}
/**
* Gets a specified custom method on the current object via:
*
* GET /collection/id/method.xml
* GET /collection/id/method.xml?attr=value
*/
function get ($method, $options = array ()) {
$req = $this->site . $this->element_name;
if ($this->_data['id']) {
$req .= '/' . $this->_data['id'];
}
$req .= '/' . $method . '.xml';
if (count ($options) > 0) {
$req .= '?' . http_build_query ($options);
}
return $this->_send_and_receive ($req, 'GET');
}
/**
* Posts to a specified custom method on the current object via:
*
* POST /collection/id/method.xml
*/
function post ($method, $options = array ()) {
$req = $this->site . $this->element_name;
if ($this->_data['id']) {
$req .= '/' . $this->_data['id'];
}
$req .= '/' . $method . '.xml';
return $this->_send_and_receive ($req, 'POST', $options);
}
/**
* Puts to a specified custom method on the current object via:
*
* PUT /collection/id/method.xml
*/
function put ($method, $options = array ()) {
$req = $this->site . $this->element_name;
if ($this->_data['id']) {
$req .= '/' . $this->_data['id'];
}
$req .= '/' . $method . '.xml';
if (count ($options) > 0) {
$req .= '?' . http_build_query ($options);
}
return $this->_send_and_receive ($req, 'PUT');
}
/**
* Simple recursive function to build an XML response.
*/
function _build_xml ($k, $v) {
if (is_object ($v) && strtolower (get_class ($v)) == 'simplexmlelement') {
return preg_replace ('/<\?xml(.*?)\?>/', '', $v->asXML ());
}
$res = '';
$attrs = '';
if (! is_numeric ($k)) {
$res = '<' . $k . '{{attributes}}>';
}
if (is_array ($v)) {
foreach ($v as $key => $value) {
if (strpos ($key, '@') === 0) {
$attrs .= ' ' . substr ($key, 1) . '="' . $this->_xml_entities ($value) . '"';
continue;
}
$res .= $this->_build_xml ($key, $value);
$keys = array_keys ($v);
if (is_numeric ($key) && $key != array_pop ($keys)) {
$res .= '</' . $k . ">\n<" . $k . '>';
}
}
} else {
$res .= $this->_xml_entities ($v);
}
if (! is_numeric ($k)) {
$res .= '</' . $k . ">\n";
}
$res = str_replace ('<' . $k . '{{attributes}}>', '<' . $k . $attrs . '>', $res);
return $res;
}
/**
* Converts entities to unicode entities (ie. < becomes &#60;).
* From php.net/htmlentities comments, user "webwurst at web dot de"
*/
function _xml_entities ($string) {
$trans = get_html_translation_table (HTML_ENTITIES);
foreach ($trans as $key => $value) {
$trans[$key] = '&#' . ord ($key) . ';';
}
return strtr ($string, $trans);
}
/**
* Build the request, call _fetch() and parse the results.
*/
function _send_and_receive ($url, $method, $data = array ()) {
$params = '';
$el = substr ($this->element_name, 0, -1);
if ($this->request_format == 'url') {
foreach ($data as $k => $v) {
if ($k != 'id' && $k != 'created-at' && $k != 'updated-at') {
$params .= '&' . $el . '[' . str_replace ('-', '_', $k) . ']=' . rawurlencode ($v);
}
}
$params = substr ($params, 1);
} elseif ($this->request_format == 'xml') {
$params = '<?xml version="1.0" encoding="UTF-8"?><' . $el . ">\n";
foreach ($data as $k => $v) {
if ($k != 'id' && $k != 'created-at' && $k != 'updated-at') {
$params .= $this->_build_xml ($k, $v);
}
}
$params .= '</' . $el . '>';
}
$this->request_body = $params;
$this->request_uri = $url;
$this->request_method = $method;
$res = $this->_fetch ($url, $method, $params);
list ($headers, $res) = explode ("\r\n\r\n", $res, 2);
$this->response_headers = $headers;
$this->response_body = $res;
if (preg_match ('/HTTP\/[0-9]\.[0-9] ([0-9]+)/', $headers, $regs)) {
$this->response_code = $regs[1];
} else {
$this->response_code = false;
}
if (! $res) {
return $this;
} elseif ($res == ' ') {
$this->error = 'Empty reply';
return $this;
}
// parse XML response
$xml = new SimpleXMLElement ($res);
if ($xml->getName () == $this->element_name) {
// multiple
$res = array ();
$cls = get_class ($this);
foreach ($xml->children () as $child) {
$obj = new $cls;
foreach ((array) $child as $k => $v) {
$k = str_replace ('-', '_', $k);
if (isset ($v['nil']) && $v['nil'] == 'true') {
continue;
} else {
$obj->_data[$k] = $v;
}
}
$res[] = $obj;
}
return $res;
} elseif ($xml->getName () == 'errors') {
// parse error message
$this->error = $xml->error;
$this->errno = $this->response_code;
return false;
}
foreach ((array) $xml as $k => $v) {
$k = str_replace ('-', '_', $k);
if (isset ($v['nil']) && $v['nil'] == 'true') {
continue;
} else {
$this->_data[$k] = $v;
}
}
return $this;
}
/**
* Fetch the specified request via cURL.
*/
function _fetch ($url, $method, $params) {
if (! extension_loaded ('curl')) {
$this->error = 'cURL extension not loaded.';
return false;
}
$ch = curl_init ();
curl_setopt ($ch, CURLOPT_URL, $url);
curl_setopt ($ch, CURLOPT_MAXREDIRS, 3);
curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, 0);
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($ch, CURLOPT_VERBOSE, 0);
curl_setopt ($ch, CURLOPT_HEADER, 1);
curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
/* HTTP Basic Authentication */
if ($this->user && $this->password) {
curl_setopt ($ch, CURLOPT_USERPWD, $this->user . ":" . $this->password);
}
if ($this->request_format == 'xml') {
curl_setopt ($ch, CURLOPT_HTTPHEADER, array ("Content-Type: text/xml", "Length: " . strlen ($params)));
}
switch ($method) {
case 'POST':
curl_setopt ($ch, CURLOPT_POST, 1);
curl_setopt ($ch, CURLOPT_POSTFIELDS, $params);
//curl_setopt ($ch, CURLOPT_HTTPHEADER, array ("Content-Type: application/x-www-form-urlencoded\n"));
break;
case 'DELETE':
curl_setopt ($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
case 'PUT':
curl_setopt ($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt ($ch, CURLOPT_POSTFIELDS, $params);
//curl_setopt ($ch, CURLOPT_HTTPHEADER, array ("Content-Type: application/x-www-form-urlencoded\n"));
break;
case 'GET':
default:
break;
}
$res = curl_exec ($ch);
// Check HTTP status code for denied access
$http_code = curl_getinfo ($ch, CURLINFO_HTTP_CODE);
if ($http_code == 401) {
$this->errno = $http_code;
$this->error = "HTTP Basic: Access denied.";
curl_close ($ch);
return false;
}
if (! $res) {
$this->errno = curl_errno ($ch);
$this->error = curl_error ($ch);
curl_close ($ch);
return false;
}
curl_close ($ch);
return $res;
}
/**
* Getter for internal object data.
*/
function __get ($k) {
if (isset ($this->_data[$k])) {
return $this->_data[$k];
}
return $this->{$k};
}
/**
* Setter for internal object data.
*/
function __set ($k, $v) {
if (isset ($this->_data[$k])) {
$this->_data[$k] = $v;
return;
}
$this->{$k} = $v;
}
/**
* Quick setter for chaining methods.
*/
function set ($k, $v = false) {
if (! $v && is_array ($k)) {
foreach ($k as $key => $value) {
$this->_data[$key] = $value;
}
} else {
$this->_data[$k] = $v;
}
return $this;
}
}
/**
* Basic implementation of the Ruby on Rails ActiveResource REST client.
* Intended to work with RoR-based REST servers, which all share similar
* API patterns.
*
* Usage:
*
* <?php
*
* require_once ('ActiveResource.php');
*
* class Song extends ActiveResource {
* var $site = 'http://localhost:3000/';
* var $element_name = 'songs';
* }
*
* // create new item
* $song = new Song (array ('artist' => 'Joe Cocker', 'title' => 'A Little Help From My Friends'));
* $song->save ();
*
* // fetch and update an item
* $song->find (44)->set ('title', 'The River')->save ();
*
* // line by line
* $song->find (44);
* $song->title = 'The River';
* $song->save ();
*
* // get all songs
* $songs = $song->find ('all');
*
* // delete a song
* $song->find (44);
* $song->destroy ();
*
* // custom method
* $songs = $song->get ('by_year', array ('year' => 1999));
*
* ?>
*
* @author John Luxford <[email protected]>
* @version 0.12 beta
* @license http://opensource.org/licenses/lgpl-2.1.php
*/
#include 'php-ActiveResource/src/ActiveResource/Base.php';
class User extends ActiveResource {
var $site = "http://localhost:3000/";
var $element_name = "user";
}
$user = new User(NULL);
//$user->save();
$users = $user->find("all");
#var_dump($users[0]->email);
$user->email = "[email protected]";
var_dump($user->save());
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment