John Crawford
John Crawford

Reputation: 10031

Basic RESTful API example using PHP

I'm working on getting a basic RESTful API example. Currently I'm using the example I found here however it has some bugs and is incomplete.

I've already added the following lines to my .htaccess as the example stated.

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule api/v1/(.*)$ api/v1/MyAPI.php?request=$1 [QSA,NC,L]
</IfModule>

My problem however is simply getting the concrete class of mine to print "12". I don't know which URL to visit to get the message "12" to be shown on the screen.

Here is my abstract class code:

<?php
abstract class API 
{
    /** 
     * Property: method
     * The HTTP method this request was made in, either GET, POST, PUT or DELETE
     */
    protected $method = ''; 
    /** 
     * Property: endpoint
     * The Model requested in the URI. eg: /files
     */
    protected $endpoint = ''; 
    /** 
     * Property: verb
     * An optional additional descriptor about the endpoint, used for things that can
     * not be handled by the basic methods. eg: /files/process
     */
    protected $verb = ''; 
    /** 
     * Property: args
     * Any additional URI components after the endpoint and verb have been removed, in our
     * case, an integer ID for the resource. eg: /<endpoint>/<verb>/<arg0>/<arg1>
     * or /<endpoint>/<arg0>
     */
    protected $args = Array();
    /** 
     * Property: file
     * Stores the input of the PUT request
     */
     protected $file = Null;

    /** 
     * Constructor: __construct
     * Allow for CORS, assemble and pre-process the data
     */
    public function __construct($request) {
        header("Access-Control-Allow-Orgin: *"); //any origin can be processed by this page
        header("Access-Control-Allow-Methods: *"); //any HTTP method can be accepted
        header("Content-Type: application/json");

        $this->args = explode('/', rtrim($request, '/'));
        $this->endpoint = array_shift($this->args);
        if (array_key_exists(0, $this->args) && !is_numeric($this->args[0])) {
            $this->verb = array_shift($this->args);
        }

        $this->method = $_SERVER['REQUEST_METHOD'];
        if ($this->method == 'POST' && array_key_exists('HTTP_X_HTTP_METHOD', $_SERVER)) {
            if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'DELETE') {
                $this->method = 'DELETE';
            } else if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'PUT') {
                $this->method = 'PUT';
            } else {
                throw new Exception("Unexpected Header");
            }
        }

        switch($this->method) {
        case 'DELETE':
        case 'POST':
            $this->request = $this->_cleanInputs($_POST);
            break;
        case 'GET':
            $this->request = $this->_cleanInputs($_GET);
            break;
        case 'PUT':
            $this->request = $this->_cleanInputs($_GET);
            $this->file = file_get_contents("php://input");
            break;
        default:
            $this->_response('Invalid Method', 405);
            break;
        }
    }

    /**
     * Determine if the concrete class implements a method for the endpoint that the client requested. If it does, then it calls that method, otherwise a 404 
     * response is returned
     */
    public function processAPI() {
        if ((int)method_exists($this->endpoint) > 0) {
            return $this->_response($this->{$this->endpoint}($this->args));
        }
        return $this->_response('', 400);
    }

    private function _response($data, $status = 200) {
        header("HTTP/1.1 " . $status . " " . $this->_requestStatus($status));
        return json_encode($data);
    }

    private function _cleanInputs($data) {
        $clean_input = Array();
        if (is_array($data)) {
            foreach ($data as $k => $v) {
                $clean_input[$k] = $this->_cleanInputs($v);
            }
        } else {
            $clean_input = trim(strip_tags($data));
        }
        return $clean_input;
    }

    private function _requestStatus($code) {
        $status = 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');
        return ($status[$code])?$status[$code]:$status[500];
    }
}

And here is my concrete class code (trouble on line 8):

<?php
require_once 'API.php';
class MyAPI extends API 
{
    protected $User;

    public function __construct($request, $origin)
    {
        echo "12";
        parent::__construct($request);

        // Abstracted out for example
        //$APIKey = new Models\APIKey();
        //$User = new Models\User();

        if (!array_key_exists('apiKey', $this->request)) {
            throw new Exception('No API Key provided');
        } else if (!$APIKey->verifyKey($this->request['apiKey'], $origin)) {
            throw new Exception('Invalid API Key');
        } else if (array_key_exists('token', $this->request) && !$User->get('token', $this->request['token'])){
            throw new Exception('Invalid User Token');
        }

        //$this->User = $User;
    }   

    /** 
     * Example of an Endpoint
     */
    protected function example()
    {
        if ($this->method == 'GET') {
            return "Your name is " . $this->User->name;
        } else {
            return "Only accepts GET requests";
        }
    }   
}

As you can see line 8 on the concrete class (in the constructor) never gets printed. Currently I'm trying to make my example work by going to:

www.mysite.com/api/myAPI.php?request=get

Upvotes: 2

Views: 9471

Answers (2)

MJacobsen
MJacobsen

Reputation: 31

Not sure if you still need an answer on this, but I just got mine working, with a few tweaks. There are a few problems with your setup. First is the .htaccess file. You have it pointing to MyAPI.php, but if you read the tutorial, he actually has a third file, api.php, that instantiates the MyAPI class. The line:

RewriteRule api/v1/(.*)$ api/v1/MyAPI.php?request=$1 [QSA,NC,L]

should be:

RewriteRule api/v1/(.*)$ api/v1/api.php?request=$1 [QSA,NC,L]

You shouldn't be trying to access the MyAPI.php file directly. Rather, apache should point to api.php, passing the request details along. The contents of the api.php file (per CM's site) are:

<?php
require_once 'MyAPI.php';
if (!array_key_exists('HTTP_ORIGIN', $_SERVER)) {
    $_SERVER['HTTP_ORIGIN'] = $_SERVER['SERVER_NAME'];
}

try {
    $API = new MyAPI($_REQUEST['request'], $_SERVER['HTTP_ORIGIN']);
    echo $API->processAPI();
} catch (Exception $e) {
    echo json_encode(Array('error' => $e->getMessage()));
}
?>

The second problem is with your URL - you should follow his example of passing an endpoint (in his/my case, /api/v1/example). This will produce both the text inside the MyAPI constructor (if you leave that in yours), and the message from the example endpoint function. For what it's worth, I stripped out all of the key and user stuff just so I could get it running. So my MyAPI.php file looks like:

<?php
require_once 'AbstractAPI.php';
class MyAPI extends API 
{
    protected $testmessage;
    public function __construct($request, $origin) {
        parent::__construct($request);
        $this->testmessage = "Test String";
    }
    protected function example()
    {
        return $this->testmessage;  
    }
}
?>

I took the "start simple, dress it up later" approach. Good luck!

Upvotes: 3

Umair
Umair

Reputation: 398

In my opinion this URL is against REST principles

www.mysite.com/api/myAPI.php?request=get

If one mentions in the url request=get then the purpose of using HTTP GET dies.
A RESTful url should be something like

 www.mysite.com/api/myAPI.php

and a HTTP GET request to this url should do the work.

Upvotes: 2

Related Questions