Developing Actions

Class Structure

If you have not already, please review the examples in the Creating Actions section, as these code samples are based on those.

Below is the generated code from the "Users" action we created in the example. It can be found in the FRAPI_PATH/src/frapi/custom/Action/Users.php file.


<?php
/**
 * Action Users 
 * 
 * This action handles a GET, POST, DELETE or HEAD request for a user
 * 
 * @link http://getfrapi.com
 * @author Frapi 
 * @link /users/:user_id
 */
class Action_Users extends Frapi_Action implements Frapi_Action_Interface
{

    /**
     * Required parameters
     * 
     * @var An array of required parameters.
     */
    protected $requiredParams = array();

    /**
     * The data container to use in toArray()
     * 
     * @var A container of data to fill and return in toArray()
     */
    private $data = array();

    /**
     * To Array
     * 
     * This method returns the value found in the database 
     * into an associative array.
     * 
     * @return array
     */
    public function toArray()
    {
        return $this->data;
    }

    /**
     * Default Call Method
     * 
     * This method is called when no specific request handler has been found
     * 
     * @return array
     */
    public function executeAction()
    {
        return $this->toArray();
    }

    /**
     * Get Request Handler
     * 
     * This method is called when a request is a GET
     * 
     * @return array
     */
    public function executeGet()
    {
        return $this->toArray();
    }

    /**
     * Post Request Handler
     * 
     * This method is called when a request is a POST
     * 
     * @return array
     */
    public function executePost()
    {
        return $this->toArray();
    }

    /**
     * Put Request Handler
     * 
     * This method is called when a request is a PUT
     * 
     * @return array
     */
    public function executePut()
    {
        return $this->toArray();
    }

    /**
     * Delete Request Handler
     * 
     * This method is called when a request is a DELETE
     * 
     * @return array
     */
    public function executeDelete()
    {
        return $this->toArray();
    }

    /**
     * Head Request Handler
     * 
     * This method is called when a request is a HEAD
     * 
     * @return array
     */
    public function executeHead()
    {
        return $this->toArray();
    }
}

Lets look at each part of the class now.

class Action_Users extends Frapi_Action implements Frapi_Action_Interface

All Frapi actions extend Frapi_Action class and implement the Frapi_Action_Interface.

protected $requiredParams = array();

The $requiredParams property of the class stores all the parameters defined as required in the admin interface. If there are any here Frapi will automatically enforce that they are passed and return the appropriate error code if not.

private $data = array();

The $data property of the class stores the data that will be returned from your action. Typically you build this up in your execute* methods.

public function toArray() {

The toArray() method simply returns the $data property of the class.

execute[Action|Get|Post|Put|Delete|Head]

These methods handle the appropriate request for this action. For example a GET request to /users/1 will be handled by the executeGet() method.

Accessing Parameters

For debugging purposes, FRAPI allows developers to retrieve all query parameters using the method $this->getParams(); from within any execute*() action. This method returns an associative array of parameter names and values — $_REQUEST.

As developers have a tendency to break things and not read documentation, at times, the data sent to your api API is not going to be of good type. FRAPI has another method to help you retrieve single parameters called. The $this->getParam(key [, type]); method .

The first argument is required and is the key of the parameter to retrieve. The second argument is optional and is the type to cast the parameter to.

Here's a code example for retrieving $_GET['name'] in different types:


<?php
class ActionX ... {
    public function executeGet() 
    {
        // Fetch $_GET['name'] as is.
        $nameNoCast   = $this->getParam('name');
        
        // Fetch the $_GET['name'] as a string.
        $nameAsString = $this->getParam('name', self::TYPE_STRING);
        
        // Fetch the $_GET['name'] as an 
        // htmlspecialentities/htmlspecialchars string.
        $nameAsHtmlEscaped = $this->getParam('name', self::TYPE_OUTPUT);
        
        // Fetch the $_GET['name'] as an int
        $nameAsInteger = $this->getParam('name', self::TYPE_INT);
    }
    
    public function executePost()
    {
        // Fetch the $_POST['age'] param as float
        $ageAsFloat = $this->getParam('age', self::TYPE_FLOAT);
    }
    
    public function executeDelete()
    {
        // Consider a batch delete
        // Fetch the $_REQUEST['users'] which is 
        // users[]=1&users[]=2&users[]=3 as an array.
        $users = $this->getParam('users', self::TYPE_ARRAY);
        foreach ($users as $key => $user) {
            echo $user;
        }
    }
}

As you can see above, the $this->getParam() method is not bound to a specific method type. Moreover, as noted above, there are many different types that parameters can be casted as — that's the second parameter

Obviously you don't have to use type casting, however it can greatly improve the security of the API and improve your ability to recognize bad behaviours.

Returning Data

When you are in an execute*() method, at any given time you can exit and return data to the third party developers by invoking:

$this->toArray();

The $this->toArray(); method is quite benign; it simply takes what's present in the $this->data parent property and handles it's output type — json, xml, php, printr, jsonp, etc.

To return data to your JSON output handler — or automatically any other handler for that matter — simply populate the $this->data from within your action and when you are finished, return $this->toArray();

Here's a use-case for a GET, a HEAD, and a DELETE request:


<?php
/**
 * Route: /x
 */
class ActionX ... {
    public function executeGet() 
    {
        $this->data['meta']    = array(
            'total' => 1, 'when', 'now'
        );
        
        $this->data['content'] = array(
            'users' => array('name' => 'david')
        );
        
        return $this->toArray();
    }

    public function executeHead()
    {
        $this->data['meta']    = array(
            'total' => 1, 'when', 'now'
        );
        
        return $this->toArray();
    }

    public function executeDelete()
    {
        $this->data['success'] = 1;
        return $this->toArray();
    }
}

If you execute curl -X GET -d {} http://api/x.json the output will be:

{"meta": {"total":1,"when":"now"}, "content": {"users": {"name":"david"}}}

For a curl -X DELETE -d {} http://api/x.json, the output will be:

{"success":1}

For a curl -X HEAD -d {} http://api/x.json, the output will be:

{"meta": {"total":1, "when": "now"}}

Returning Custom Responses

FRAPI always returns a 200 HTTP Response Code by default when an action is successful. However, it might happen that you want to return a 201 HTTP Response Code after having handled a POST correctly and after having created a resource.

In such case, from within an execute*() method, a developer can return a new Frapi_Response object.

The required parameters of a Frapi_Response are quite simple. It is an array. Beware this array currently contains 2 elements that will be useful to you. a code key, and a data key. The code key is the HTTP Response Code to return, and the data key is the data to return in the response body.

If you were in an action and you wanted to indicate that a resource had been created by returning a 201 HTTP Response Code and some array data — as if you were populating $this->data— you would do this:


    public function executePost()
    {
        return new Frapi_Response(array(
            'code' => 201,
            'data' => array('success' => 1)
        ));
    }
    

This will return the exact same thing than if you had done:


    public function executePost()
    {
        $this->data = array('success' => 1);
        return $this->toArray();
    }
    

But instead of HTTP Response Code 200, the response code will be 201 (Created).

Returning 4xx Errors

As you may have seen in creating errors FRAPI allows you to manage errors from the administration interface. Once you created an error in FRAPI with an HTTP Response Code, you can easily invoke this error at anytime, in any action method by invoking the error by the created name like such:


    // Name of error in frapi admin "NAME_OF_ERROR"
    throw new Frapi_Error('NAME_OF_ERROR');
    

By doing this in any execute*() method, you are going to throw an error with the associated name, description and message. If the requested output is XML, the error will look like:


<?xml version="1.0" encoding="UTF-8"?>
<response>
    <errors>	
        <error code="NAME_OF_ERROR">
            <message>Description of error</message>
            <name>NAME_OF_ERROR</name>
            <at></at>
        </error>
    </errors>
</response>

The most important part from throwing Frapi_Errors is that the HTTP Code is going to be thrown correctly as well.

For developers, it is also important to know that errors can be thrown at anytime without prior definition of the error code in the administration panel. In order to throw a 404 error about a resource that's not been found, a developer would do:


    // Name of error in frapi admin "NAME_OF_ERROR"
    throw new Frapi_Error('NOT_FOUND', 'This user was not found', 404);
    

If you look closely at the previous code example, you'd notice that we are invoking the Frapi_Error just as if we were using the errors defined in the administration panel but this is an on-the-fly error. What happens within FRAPI is that when Frapi_Error is invoked, it verifies if the first parameter exists in the cache of Administration defined errors and if it doesn't match anything, it creates a dynamic error.

With dynamic errors, there are 2 required parameters and the third is optional but probably the most important of all. The first parameter is the error name/code in the payload returned to the users. The second parameter is the description of the error or the message part of the payload. The third parameter is optional but the most important for RESTful APIs. It's the HTTP Code to return with the error. If nothing is passed as the third parameter, the default HTTP Response Code returned is 400. For instance, the throw new Frapi_Error('NOT_FOUND', 'This user was not found', 404) code snippet above, would generate the following XML with the HTTP Response Code 404:


<?xml version="1.0" encoding="UTF-8"?>
<response>
    <errors>	
        <error code="NOT_FOUND">
            <message>This user was not found</message>
            <name>NOT_FOUND</name>
            <at></at>
        </error>
    </errors>
</response>

The JSON output for such error would look like:


{
    "errors":[{
        "message":"This user was not found",
        "name":"NOT_FOUND",
        "at":""
    }]
}

We strongly recommend using the administration interface to define the errors as this is currently the only way to generate documentation using the automated documentation generator in FRAPI. We are currently working on a reverse-engineering solution that will scan all your custom Action code and identify the dynamic errors defined in-code and not in-admin and generate documentation for those errors but for now this functionality is lacking.