Welcome Guest, Not a member yet? Register   Sign In
Working on a REST API, critique requested
#11

[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.".
#12

[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() {
        
    }
}
?>
#13

[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) {
    
    }
}
?>
#14

[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.
#15

[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("&lt;?xml version='1.0' encoding='utf-8'?&gt;<$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);
    }
}
?&gt;

Then you can call it in your api controller(s) like so:

controllers/api.php

Code:
&lt;?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
    }
}

?&gt;

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?
#16

[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.
#17

[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.
#18

[eluser]Yorick Peterse[/eluser]
Controller : http://pastebin.com/m67409a27
Model: http://pastebin.com/m46a5f903
Rest_controller: http://pastebin.com/m565aee43
#19

[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
#20

[eluser]Dam1an[/eluser]
Looking very good Phil Smile
Thanks

Ps, I'm sure you're is just as nice Yorick Wink




Theme © iAndrew 2016 - Forum software by © MyBB