[eluser]Yorick Peterse[/eluser]
I'd love to say that makes sence, but a lot of it doesn't (the functions do, but the structure etc doesn't). I just wish there was more information about using REST in PHP, rather than just giving you the good and saying "bla bla bla goes without saying.".
[eluser]Yorick Peterse[/eluser]
I modified my controller as following, should be more "rest like" now. I simply got rid of all REST functions and replaced them with an Index function, the type(e.g. GET) should now be determined with an HTTP request. I also added a Rest_Controller.php file in the libraries directory.
Code: <?php
/**
* @name Api controller
* @version 1.0
* @author Yorick Peterse
*
* This is the main API controller, it will handle the incoming requests and will decide what to do with them.
* Do note that all functions in this controller which aren't supposed to be accessed through the URL, should be declared as private. This to prevent security issues
*
*/
class Api extends Rest_Controller {
// Variables
private $format;
private $method;
// Constructor function
function __construct() {
// Load the parent constructor
parent::Controller();
// Set the format, this will be set to XML by default
$this->format = strpos($_SERVER['HTTP_ACCEPT'],'xml') ? 'xml' : 'json';
// Set the method, by default this will be 'get'
$this->method = $_SERVER['REQUEST_METHOD'];
echo $this->method;
}
/**
* Main REST functions
*
* By adding these functions to the URL, Flork will determine what has to be executed.
*/
// Function that will handle incoming data
function index() {
//Switch statement to determine which type of request was sent (PUT,POST,DELETE or GET)
// ......
}
// DEBUG FUNCTION, ONLY TO BE USED TO DEBUG THE INDEX FUNCTION BY SENDING REQUESTS
function debug_request() {
}
}
?>
Rest controller
Code: <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Rest_Controller extends Controller {
// Constructor function
function __construct() {
parent::Controller();
}
// Function to generate a HTTP response code, based on the $status variable defined in sendResponse()
function getResponseCode($status) {
// Create an array containing all response codes and their messages
$codes = array (
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => '(Unused)',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported'
);
// Verify if the status message has been set and return it
return (isset($codes[$status]) ? $codes[$status] : '');
}
// Function to flush all data
function flushData() {
}
// Function to verify the request
function verifyRequest() {
}
}
?>
[eluser]Yorick Peterse[/eluser]
Right now the very basic idea of the REST idea is working, the code looks as following:
Controller (ignore the debug function, I'm using it to test the API)
Code: <?php
/**
* @name Api controller
* @version 1.0
* @author Yorick Peterse
*
* This is the main API controller, it will handle the incoming requests and will decide what to do with them.
* Do note that all functions in this controller which aren't supposed to be accessed through the URL, should be declared as private. This to prevent security issues
*
*/
class Api extends Rest_Controller {
// Variables
private $format;
private $method;
// Constructor function
function __construct() {
// Load the parent constructor
parent::Controller();
// Set the format, this will be set to XML by default
$this->format = strpos($_SERVER['HTTP_ACCEPT'],'xml') ? 'xml' : 'json';
// Set the method, by default this will be 'get'
$this->method = $_SERVER['REQUEST_METHOD'];
}
/**
* Main REST functions
*
* By adding these functions to the URL, Flork will determine what has to be executed.
*/
// Index function, this function will redirect to the systems function
function index() {
// Load the URL helper
$this->load->helper('url');
redirect('Api/system');
}
// Function that will handle incoming data
function system() {
// Load the model
$this->load->model('Model_api');
// Get the table from the URL
$table = $this->uri->segment(3);
$field = $this->uri->segment(4);
$value = $this->uri->segment(5);
// Do a switch statement to see what the request method is
switch($this->method) {
// Get
case 'GET':
$this->model_api->getData($table,$field,$value,$this->format);
break;
// Post
case 'POST':
break;
// Put
case 'PUT':
break;
// Delete
case 'DELETE':
break;
}
}
// DEBUG FUNCTION, ONLY TO BE USED TO DEBUG THE INDEX FUNCTION BY SENDING REQUESTS
function debug_request() {
// Initialize Curl
$curl = curl_init();
// Set the options for Curl
curl_setopt($curl,CURLOPT_URL,'http://localhost/Big_Projects/Flork/index.php/Api/system/users');
curl_setopt($curl,CURLOPT_CUSTOMREQUEST,'GET');
curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
// Store the results in the $data variable and echo them
$data = curl_exec($curl);
curl_close($curl);
echo $data;
}
}
?>
The Model
Code: <?php
/*
* @name Model_API
* @version 1.0
* @author Yorick Peterse
*
* The model for the API, what it does goes without saying.
*
*/
class Model_api extends Model {
// Constructor function
function __construct() {
parent::Model();
}
// Function to output data in XML or JSON
function getData($table,$field,$value,$format) {
// Create the query
if($field != null AND $value != null) {
$query = $this->db->get_where("$table",array($field => $value));
}
else {
$query = $this->db->get($table);
}
// Convert the array to the format
$results = $query->result();
print_r($results);
}
// Function to store the data that was sent using an HTTP request.
function postData($table,$data) {
}
// Function to update existing data
function putData($table,$id,$data) {
}
// Function to delete data
function deleteData($table,$id) {
}
/**
* Converting data
*
* 2 main functions, getXML and getJSON. These functions will convert an array to either XML or JSON
*/
// Function to convert an array to XML format
private function getXML($array) {
// First check if the data is actually an array
if(is_array($array)) {
}
// Return false, in case the $array variable isn't an array
else {
return false;
}
}
// Function to convert an array to JSON format
private function getJSON($array) {
}
}
?>
[eluser]Phil Sturgeon[/eluser]
Sorry for my comments on this previously, my understanding of REST had been warped by my fellow developers on this project who are just using REST as a normal API platform and using GET/POST for everything including delete, etc. This post cleared a few things up for me.
I will be using what you have started here to make a modular REST controller and set of routes that can be combined with Matchbox (or HMVC) to integrate REST into any existing modular site layout. Watch this space.
[eluser]Phil Sturgeon[/eluser]
How about this.
libraries/REST_controller.php
Code: <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class REST_Controller extends Controller {
private $method;
private $format;
private $args;
// List all supported methods, the first will be default
private $supported_formats = array(
'xml' => 'application/xml',
'json' => 'application/json',
'serialize' => 'text/plain',
'php' => 'text/plain'
);
// Constructor function
function __construct()
{
parent::Controller();
$this->args = $this->uri->uri_to_assoc();
$this->format = $this->_detect_format();
$this->method = $this->_detect_method();
}
function _remap($object_called)
{
$controller_method = $object_called.'_'.$this->method;
if($controller_method)
{
$this->$controller_method();
}
}
// Main function to output data
function response($data = '', $http_code = 200)
{
$this->output->set_status_header($http_code);
// If the method exists, call it
if(method_exists($this, '_'.$this->format))
{
// Set a XML header
$this->output->set_header('Content-type: '.$this->supported_formats[$this->format]);
$formatted_data = $this->{'_'.$this->format}($data);
$this->output->set_output( $formatted_data );
}
else
{
$this->output->set_output( $data );
}
}
// Detect which format should be used to output the data
private function _detect_format()
{
if(array_key_exists('format', $this->args) && array_key_exists($this->args['format'], $this->supported_formats))
{
return $this->args['format'];
}
// If a HTTP_ACCEPT header is present...
if($this->input->server('HTTP_ACCEPT'))
{
// Check to see if it matches a supported format
foreach(array_keys($this->supported_formats) as $format)
{
if(strpos($this->input->server('HTTP_ACCEPT'), $format) !== FALSE)
{
return $format;
}
}
}
// If it doesnt match any or no HTTP_ACCEPT header exists, uses the first (default) supported format
list($default)=array_keys($this->supported_formats);
return $default;
}
private function _detect_method()
{
$method = strtolower($this->input->server('REQUEST_METHOD'));
if(in_array($method, array('get', 'delete', 'post', 'put')))
{
return $method;
}
return 'get';
}
// FORMATING FUNCTIONS ---------------------------------------------------------
// Format XML for output
private function _xml($data = array(), $structure = NULL, $basenode = 'xml')
{
// turn off compatibility mode as simple xml throws a wobbly if you don't.
if (ini_get('zend.ze1_compatibility_mode') == 1)
{
ini_set ('zend.ze1_compatibility_mode', 0);
}
if ($structure == NULL)
{
$structure = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><$basenode />");
}
// loop through the data passed in.
foreach($data as $key => $value)
{
// no numeric keys in our xml please!
if (is_numeric($key))
{
// make string key...
//$key = "item_". (string) $key;
$key = "item";
}
// replace anything not alpha numeric
$key = preg_replace('/[^a-z0-9_-]/i', '', $key);
// if there is another array found recrusively call this function
if (is_array($value))
{
$node = $structure->addChild($key);
// recrusive call.
$this->_xml($value, $node, $basenode);
}
else
{
// add single node.
$value = htmlentities($value, ENT_NOQUOTES, "UTF-8");
$UsedKeys[] = $key;
$structure->addChild($key, $value);
}
}
// pass back as string. or simple xml object if you want!
return $structure->asXML();
}
// Encode as JSON
private function _json($data = array())
{
return json_encode($data);
}
// Encode as Serialized array
private function _serialize($data = array())
{
return serialize($data);
}
// Encode raw PHP
private function _php($data = array())
{
return var_export($data);
}
}
?>
Then you can call it in your api controller(s) like so:
controllers/api.php
Code: <?php
class Api extends REST_Controller {
function something_get()
{
if($user = $this->some_model->get($this->args['id']))
{
$this->response($user, 200); // 200 being the HTTP response code
}
else
{
$this->response(NULL, 404);
}
}
function something_delete()
{
$this->some_model->delete($this->args['id']);
$this->response($user, 200); // 200 being the HTTP response code
}
}
?>
It supports xml, json, serialize and raw php output. The first two will be auto-detected (xml is defaulted if nothing more useful is detected) and latter of the formats require 'format/serialize' to be added to the URI.
This seems to work like a charm, and new formats can be added a piece of piss. This can also be used in a modular environment perfectly!
Feedback?
[eluser]Phil Sturgeon[/eluser]
Aha, worked out the rest. I updated the example above for the methods to be properly REST and not just GET calls.
That could be called via example.com/api/something with HTTP_ACCEPT set to GET or DELETE and to add support PUT for POST, just write a new method in.
Params come from $this->uri->uri_to_assoc() and passed back to the controller via the $this->args property. Then we can use URL's like...
Code: example.com/api/something/id/1
You'd be able to send HTTP_ACCEPT as get or delete as they are the only two listed in the controller, anything else would fail.
[eluser]Yorick Peterse[/eluser]
The code does look interesting, but right now I already have about 50% of the API working. It supports both JSON and XML as a format, and right now the GET and POST request are working.
See the post below.
[eluser]Phil Sturgeon[/eluser]
Got mine fully working with support for JSON, XML, CSV, HTML tables, PHP export and serialization.
It also supports PUT, GET, POST and DELETE perfectly with custom methods for each.
Check out the final code in this article.
Another bonus is you can support multiple puts, deletes, posts, etc within a single controller. That means you can have one rest.php or api.php file for your ENTIRE site, or using modules/sub-directories you can have one for each section.
Quote:/blogs/api/article/id/21
/blog/api/articles/limit/10
/blog/api/comments/limit/5/format/html
/gallery/api/comments/limit/5/format/json
[eluser]Dam1an[/eluser]
Looking very good Phil
Thanks
Ps, I'm sure you're is just as nice Yorick
|