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

[eluser]Yorick Peterse[/eluser]
So I am working on a REST API for an application of mine. Since this is the first time I'm making a REST API I'd like to receive some critique and tips. Right now my API controller looks like the following piece of code:

Controller

Code:
<?php
class Api extends Controller {
    
    // Constructor function
    function __construct() {
    parent::Controller();
    }
    /**
     * Main REST functions
     *
     * By adding these functions to the URL, Flork will determine what has to be executed.
     */
    
    // Function to add data
    function post() {
    
    }
    
    // Function to get data
    function get() {
    
    }
    
    // Function to update data
    function put() {
    
    }
    
    // Function to delete data
    function delete() {
    
    }
    
    /**
     * General functions
     *
     * General functions, such as generating messages.
     */
    
    // Function to generate a response message
    private function sendResponse($status = '200',$body = '',$content_type = 'text/html') {
    
    }
    
    // Function to generate a HTTP response code, based on the $status variable defined in sendResponse()
    private 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
    if(isset($codes[$status])) {
        // Return the response code
        return $codes[$status];
    }    
    }
    
    // Function to flush all data
    private function flushData() {
    
    }
    
    // Function to verify the request
    private function verifyRequest() {
    
    }
}
?>

Model

Code:
<?php

class Model_api extends Model {
    
    // Constructor function
    function __construct() {
    parent::Model();
    }
    // Function to output data in XML or JSON
    function getData($table,$id,$format) {
    
    }
    
    // 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) {
    
    }
}
?>

Keep in mind that the controller looks really rough and will most likely be changed a billion times. However, what I'm asking of you is to look at it and give any information that could be useful, finding the right info about REST is quite hard.
#2

[eluser]Phil Sturgeon[/eluser]
This is incredibly restrictive and in my mind not what REST is about. We have a brilliant solution at work that I will try and explain in a modular CI layout.

Blogs = Module name
API = Controller name
get_blogs = Method name

URI: /blogs/api/get_blogs/param1/value1/param2/name2

This uses an associative arrary to give you a little more flexibility. Your idea of extending a controller is brilliant. Name one REST_Controller and put it in a REST_Controller.php file in libraries that is included in your MY_Conroller file.

So far, its done nothing crazy or REST-like, its just a normal CI call.

What you'll want to do is create the ability to select different formats for your outputs.

The way we do it is to optionally set a var in the controller like so:

Code:
var $format = 'xml';

This will make the entire controller use a default format for output.

Then in the URL we accept extra params /format/xml, /format/json, etc. This can be checked in the REST_Controller() using:

Code:
$arguments = $this->uri->uri_to_assoc(4); // We are using a module name so we want it from the 4th not the 3rd as usual

if(array_key_exists('format', $arguments))
{
    $this->output_format = $arguments['format']
}[/code]

That way, your format is specified by the URL but if none is mentioned it will use the controllers.

I have yet to work out how you would actually commit the output as right now our models just return the data and it is passed to a static object to match the format type.

As I said our system is pretty spot on, but is done in native PHP and applying it to CI is a little more tricky.

Right now all you are doing is using normal Controller code and I assume you'll be making views for each REST controller you use. Your model is just another abstraction on the database ActiveRecord and that doesn't really make it as flexible as it should be.
#3

[eluser]Yorick Peterse[/eluser]
I know that right now it's still more like a regular CI call, but I already made some changes to the controller. Also note that I'm seperating the Api module from the other modules. In this case I have 3 modules, Flork, Profile and API.

The flork module is the module that shows stuff which can be viewed by everyone, the other 2 speak for themselves Wink

Code:
<?php
class Api extends Controller {
    // Variables
    private $format;
    
    // 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';
    
    }
    /**
     * Main REST functions
     *
     * By adding these functions to the URL, Flork will determine what has to be executed.
     */
    
    // Function to add data
    function post() {
    
    }
    
    // Function to get data
    function get() {
    // Retrieve the information required to target the table
    $table     = $this->uri->segment(3);
    $id     = $this->uri->segment(4);
    
    $this->load->model('Model_api');
    $this->model_api->getData($table,$id,$this->format);
        
    }
    
    // Function to update data
    function put() {
    
    }
    
    // Function to delete data
    function delete() {
    
    }
    
    /**
     * General functions
     *
     * General functions, such as generating messages.
     */
    
    // Function to generate a response message
    private function sendResponse($status = '200',$body = '',$content_type = 'text/html') {
    
    }
    
    // Function to generate a HTTP response code, based on the $status variable defined in sendResponse()
    private 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
    private function flushData() {
    
    }
    
    // Function to verify the request
    private function verifyRequest() {
    
    }
}
?>
#4

[eluser]Phil Sturgeon[/eluser]
I dont understand why you need those get/delete/etc calls in your API controller. Is API meant to be a new type of controller or is it to be used as a controller?

Either way those REST calls and the private functions to handle status numbers REALLY shouldn't be in the same class.
#5

[eluser]Yorick Peterse[/eluser]
[quote author="Phil Sturgeon" date="1243629039"]I dont understand why you need those get/delete/etc calls in your API controller. Is API meant to be a new type of controller or is it to be used as a controller?

Either way those REST calls and the private functions to handle status numbers REALLY shouldn't be in the same class.[/quote]

The reason those main REST calls are in the controller is because that way you don't have to send them using an HTTP request, you can simply access them using the URL.

And about the other functions, for now I have put them in the same controller, but they'll most likely be moved to a different one.
#6

[eluser]Phil Sturgeon[/eluser]
Also, your Model_api is incredibly restrictive. What if I want to do complicated calls? Why is there any need for that model at all? Ever?

I think I make be looking at your code slightly different to you. If these are meant to be controllers and models and not a new controller type and model type that others will inherit, this almost makes sense.

If these are meant to be inherited by other models and controllers, this is a really bad method ^_^
#7

[eluser]Yorick Peterse[/eluser]
[quote author="Phil Sturgeon" date="1243629443"]Also, your Model_api is incredibly restrictive. What if I want to do complicated calls? Why is there any need for that model at all? Ever?

I think I make be looking at your code slightly different to you. If these are meant to be controllers and models and not a new controller type and model type that others will inherit, this almost makes sense.

If these are meant to be inherited by other models and controllers, this is a really bad method ^_^[/quote]

For now I'll keep the models as they are, I did make some changes to them (see below). But how would you do it without the model ? That will make you end up having data layer related code in your controller.


Model


Code:
<?php

class Model_api extends Model {
    
    // Constructor function
    function __construct() {
    parent::Model();
    }
    // Function to output data in XML or JSON
    function getData($table,$id,$format) {
    // Create the query
    if($id != null) {
        $query = $this->db->get_where("$table",array('id' => $id));
    }
    else {
        $query = $this->db->get("$table");
    }
    
    // Convert the array to the format
    $results = $query->result();
    
    echo $this->getXML($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)) {
        //...Convert it...
    }
    // 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) {
    
    }
}
?>
#8

[eluser]Phil Sturgeon[/eluser]
This is still not quite right. You do not need new models as you already have them in your current code. You should be able to use the same models you already use, perhaps with a little logic in there for each one.

Also, you need a single point of logic for the REST functionality. I am yet to work out if this should be a single controller or a controller type to extend.

You cant have it all in one controller and still call it REST, you are just echoing some XML. :-)
#9

[eluser]Yorick Peterse[/eluser]
[quote author="Phil Sturgeon" date="1243629800"]This is still not quite right. You do not need new models as you already have them in your current code. You should be able to use the same models you already use, perhaps with a little logic in there for each one.

Also, you need a single point of logic for the REST functionality. I am yet to work out if this should be a single controller or a controller type to extend.

You cant have it all in one controller and still call it REST, you are just echoing some XML. :-)[/quote]

I'm echoing for debugging reasons Wink

The reason for the models is because they will replace the existing models I'm using right now. Essentially this means that the existing application won't do much itself, other than calling the Api controller.
#10

[eluser]Phil Sturgeon[/eluser]
In that case, fair enough. If this is just for your app and there will only be one Api controller ever then its probably all you need.

An example of one the most basic REST API I have made today.

Code:
<?php

/**
* Multimedia class
*
* Covers all video and audio content for the site as its all based
* on the same basic tables.
*
* @author    Phil Sturgeon
* @created 29/05/09
*/
class Multimedia
{
        var $DefaultView = 'ViewAsRAWXML';

    /*
     * constructor
     */
    function __construct()
    {
        if(!empty($_GET['debug']))
        {
            ini_set('display_errors', 'On');
        }
    }
    
    /**
     *
     * @param int     $limit    Optional, limits number of resutls returned
     *
     */
    function getFundManagerInterviews()
    {
        $rc = new Remote_client("internet/scripts/remote_server_multimedia.php", 'get_fm_interviews');

        // Set debug level
        $rc->debug(0);
    
        // Execute request and obtain response
        return $rc->execute();
    }
    
}

This will be loaded by the restserver.php bootstrapper and use the ViewAsRAWXML static class to parse the output. I have made no new model for it, simply called "Remote_client("internet/scripts/remote_server_multimedia.php", 'get_fm_interviews');" which is essentially our version of models.

The ViewAsRAWXML class looks like this:

Code:
<?php

/**
* Version of ViewAsXML that allows more through in the way of tag names
*
* @todo Replace this with extension of ViewAsXML with a ViewAsXML::formatKey method
* when we've upgraded to PHP5.3 and can use late static binding
*/

class ViewAsRAWXML extends ViewAs
{
    static $ContentType = 'text/xml; charset=utf-8';
    
    /**
     * Unfortunately this needs to be defined everywhere, as we're not able to
     * work it out any other way
     */
    function getContentType()
    {
        return self::$ContentType;
    }

    public static function Fabricate($data, $structure, $basenode)
    {
    
        // 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.
                self::Fabricate($value, $node, $basenode);
            }
            else
            {
                // add single node.

                $value = preg_match('/!\w/', $value) ? self::EncodeEntity($value) : self::XMLEntities($value); //htmlentities($value, ENT_NOQUOTES, "UTF-8");

                //if (in_array($key, $UsedKeys)

                $UsedKeys[] = $key;

                $structure->addChild($key, $value);
            }

        }
        // pass back as string. or simple xml object if you want!
        return $structure->asXML();
    }

    function EncodeEntity($string)
    {
        return ('&lt;![CDATA[' . base64_encode($string) . ']]>');

    }

    function XMLEntities($string)
    {
        $string = str_replace(array('&'), array('&amp;'), $string);
        $string = preg_replace('/[^\x09\x0A\x0D\x20-\x7F]/e', 'self::_privateXMLEntities("$0")', $string);
        
        return $string;
    }

    /**
     * Abstraction of the xml2array() function in functions.php
     *
     * @param string $data The XML ext to convert to a native PHP array
     * @return array
     */
    public static function demolish($data)
    {
        return xml2array($data);
    }


}

?&gt;

We have bloody loads of these view files. Debug, HTML, JPEG, JSON, PNG, Serialize, YAML, etc all depending on what people need. The idea of rest is that its flexible, extendible and doesn't really require any new data interaction code to get it working. REST is literally just a way of getting data from one place to another in a format it can understand. Therefore making it support limitless formats of data with just adding a single extra class means its REALLY damn flexible.

See where im coming from?




Theme © iAndrew 2016 - Forum software by © MyBB