Page 1 of 1

Please help me understand REST API code (Corey Maynards tut)

Posted: Thu Aug 03, 2017 11:49 am
by hybris
Hi,

I tried following Corey Maynards tutorial for RESTful API located at http://coreymaynard.com/blog/creating-a ... -with-php/

Im running the script at my page and after changing:

Code: Select all

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

Code: Select all

// Abstracted out for example
        $APIKey = "Test";
        $User = "Testuser";
I got it to work but I do not know how to supply the APIKey "Test" to the script in my call so I only get the error {"error":"No API Key provided"}

I run my script on http://fairdea.com/api/barcode/

So anything I write after barcode/ is sent to the script.. like /../barcode/GET/1234/Test where I assume the script gets the "GET/1234/Test" part..

I would also appreciate if someone could give some ideas of how the part

Code: Select all

// Abstracted out for example
        $APIKey = new Models\APIKey();
        $User = new Models\User();
is supposed to work?

Thanks :)

Re: Please help me understand REST API code (Corey Maynards

Posted: Thu Aug 03, 2017 11:57 am
by hybris
Full code for the lazy :D

File API.Class.php

Code: Select all

<?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: *");
        header("Access-Control-Allow-Methods: *");
        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;
        }
    }
    
    public function processAPI() {
        if (method_exists($this, $this->endpoint)) {
            return $this->_response($this->{$this->endpoint}($this->args));
        }
        return $this->_response("No Endpoint: $this->endpoint", 404);
    }

    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(  
            200 => 'OK',
            404 => 'Not Found',   
            405 => 'Method Not Allowed',
            500 => 'Internal Server Error',
        ); 
        return ($status[$code])?$status[$code]:$status[500]; 
    }
}
?>

api.php

Code: Select all

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

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

        // Abstracted out for example
        $APIKey = "Test"; //new Models\APIKey();
        $User = "Testuser"; //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";
        }
     }
 }
 
 
 // Requests from the same server don't have a HTTP_ORIGIN header
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()));
}


 ?>
above code written by Corey Maynards

Re: Please help me understand REST API code (Corey Maynards

Posted: Thu Aug 03, 2017 6:51 pm
by Celauran
hybris wrote:Im running the script at my page and after changing:

Code: Select all

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

Code: Select all

// Abstracted out for example
        $APIKey = "Test";
        $User = "Testuser";
APIKey and User are clearly objects in this example, so you can't just swap them out for strings and expect them to work.

hybris wrote:I got it to work but I do not know how to supply the APIKey "Test" to the script in my call so I only get the error {"error":"No API Key provided"}
Pass it in as a query string. /api/barcode/foo/bar/?apiKey=whatever
hybris wrote:I would also appreciate if someone could give some ideas of how the part

Code: Select all

// Abstracted out for example
        $APIKey = new Models\APIKey();
        $User = new Models\User();
is supposed to work?
It can be whatever you like. The user model is just there for demonstration purposes, as seen in

Code: Select all

    /**
     * Example of an Endpoint
     */
     protected function example() {
        if ($this->method == 'GET') {
            return "Your name is " . $this->User->name;
        } else {
            return "Only accepts GET requests";
        }
     }
APIKey needs to respond to a verifyKey message. A simple implementation would be to have a table containing valid keys, which you could then query against and have the verifyKey method return true if it finds the provided key and false if it does not.

Re: Please help me understand REST API code (Corey Maynards

Posted: Thu Aug 03, 2017 6:53 pm
by Celauran

Code: Select all

 // Requests from the same server don't have a HTTP_ORIGIN header
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()));
}
This doesn't belong in the same file as your MyAPI class. This is, very loosely, a front controller.

Re: Please help me understand REST API code (Corey Maynards

Posted: Fri Aug 04, 2017 5:31 am
by hybris
Celuran, I said it before but you truely are my Hero :) Thanks!!

Ok so now I know how to pass the apiKey and get the following error:

Fatal error: Call to a member function verifyKey() on string in /customers/d/7/2/fairdea.com/httpd.www/api/barcode/MyAPI.class.php on line 16

which is expected since I do not have a Models class yet.

what does the following part mean?

Code: Select all

$APIKey = new Models\APIKey();
$User = new Models\User();
I do get the lines create new objects from the class Models but I cant figure out the \APIKey() or \User() part. I only made classes that you call like /../ new MyClass($str1, $str2);
In my codewindow I see the Models part turn yellow and the \APIKey() part remain black why i figure in the codezippet above there is a class Models and it somehow contain APIKey and User.. I tried to google what it means to call a class like new class\extension but I dont know what to search for..what its called or how it works so I cant find any info about this practice?

So far I "cheated and made 2 classes one for key and one for user to get it to work:

in myClass.php

Code: Select all

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

Code: Select all

class ModelsAPI {
     public function verifyKey($key, $origin){
         if($key == 'Test'){
         return true;
         } else {
             return false;
         }
     }
 }
 
 class ModelsUSER {
     public function verifyKey($token, $origin){
         if($token == '1'){
             return "PETER";
         } elseif($token == '2') {
             return "Thomas";
         } else {
         
         return false;
         }
     }

Some other questions:
how do I supply "token" to this API request anything i write after /?apiKey=Test seems to be interpret as apiKey so if i write /?apiKey=Test/?token=user1 the script will think I provided the apiKey Test/?token=user1 which is not equal to Test and the api will tell me i have the wrong apiKey.

The test function "example" uses GET method

Code: Select all

protected function example() {
        if ($this->method == 'GET') {
            return "Your name is " . $this->User->name;
        } else {
            return "Only accepts GET requests";
        }
     }
from what I understand when i type in browser http://www.fairdea.com/api/barcode/example/?apiKey=Test
this is a GET method but how do I write a POST method to this API?
Say I have a function

Code: Select all

protected function test() {
        if ($this->method == 'POST') {
            return "Your name is " . $this->User->name;
        } else {
            return "Only accepts POST requests";
        }
     }
How would I call this in the browser?
http://www.fairdea.com/api/barcode/POST ... piKey=Test
Tells me POST is not an endpoint (since the endpoint is test which is moved one step to the left in the request).

Re: Please help me understand REST API code (Corey Maynards

Posted: Fri Aug 04, 2017 5:36 am
by Celauran
hybris wrote:what does the following part mean?

Code: Select all

$APIKey = new Models\APIKey();
$User = new Models\User();
I do get the lines create new objects from the class Models but I cant figure out the \APIKey() or \User() part.
Models is actually the namespace. It creates two classes of different types, an APIKey class and a User class. For example,

Code: Select all

<?php

namespace Models;

class APIKey
{
    public function verifyKey($key, $source)
    {
        // Do stuff here
    }
}

Re: Please help me understand REST API code (Corey Maynards

Posted: Fri Aug 04, 2017 7:04 am
by hybris
Aha, I never used namespaces before, thanks :)

I didn't see Your answer so I edited my post above with some more questions how to supply a token to this script and how to call a POST function (instead of GET function).

Sorry for the noob questions but I never worked with API:s before :( Your help is much appreciated :)

EDIT:
I downloaded the insomnia client to test the API and added a PUT function (test1) and a post function (test) (and the GET function example).

when I chose in the client GET and call .../barcode/example/?apiKey=Test it works. Same with the PUT command (test1) it works but when I try the POST command it suddenly say NO API KEY provided.. If i test the POST function (test) with a PUT command it reports correctly it only accepts POST commands so I guess I call it wrong somehow?

Code: Select all

protected function test() {
        if ($this->method == 'POST') {
            return "Your command is POST " . $this->User->name;
        } else {
            return "Only accepts POST requests";
        }
     }
(All the 3 functions ( test(POST), test1(PUT), example(GET) ) look the same I only change the POST to GET or PUT

Re: Please help me understand REST API code (Corey Maynards

Posted: Sat Aug 05, 2017 5:51 am
by Celauran
When you change the request type to POST, are you continuing to send the key as a query string, or are you including it in the body of the request?

Re: Please help me understand REST API code (Corey Maynards

Posted: Sat Aug 05, 2017 6:03 pm
by hybris
Celauran wrote:When you change the request type to POST, are you continuing to send the key as a query string, or are you including it in the body of the request?
Well I still try to send it as a query... When I put it in the body it .... works! :) Thanks!




How do I know when to put it in body and when to put it as a query string and why do you need to put it in the body for some commands instead of always using a query string or always put it in the body?

Why did my PUT command work when I put key as query, I thought PUT was hidden in a POST request???

I never worked with API or restAPI before and my brain is apparently still on vaccation :)

Do you know any good link where i can learn more about restAPI, especially how to call them (like pass key in query for GET requests and in body for POST requests...)

Cheers! :)

Re: Please help me understand REST API code (Corey Maynards

Posted: Sun Aug 06, 2017 7:20 am
by Celauran
hybris wrote:How do I know when to put it in body and when to put it as a query string and why do you need to put it in the body for some commands instead of always using a query string or always put it in the body?
Browsers only really support GET and POST, PHP has $_GET and $_POST superglobals, and the API has been designed accordingly. If you look at the code, he's setting the request property to be the contents of the $_GET superglobal when the request is GET or PUT, which is why they work the same.
hybris wrote:Do you know any good link where i can learn more about restAPI, especially how to call them (like pass key in query for GET requests and in body for POST requests...)
I believe Laracasts has a few series on building out REST APIs, and Phil Sturgeon has a good book on it. https://apisyouwonthate.com/

Re: Please help me understand REST API code (Corey Maynards

Posted: Sun Aug 06, 2017 12:26 pm
by hybris
I thought the GET is GET and
POST can include PUT or DELETE?

Code: Select all

Download ] [ Hide ] [ Select ] [ Expand ]


        $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");
            }
Or am I reading it wrong?
This is what I thought GET was apiKey from QUERY and POST, PUT and DELETE would work the same.

As it is now GET and PUT works the same (QUERY) and POST only work IF i provide the key in BODY and DELETE doesn't work at all?