Tuesday, 10 June 2014 00:00

Developer's guide

jBackend provides a robust and extensible solution to expose content and services through a Joomla website. The component provides the basic structure that has in charge of managing requests, controlling accesses, and dispatching incoming requests to the corresponding module. The jBackend architecture is extensible because its service modules are implemented as Joomla plugins, so adding new plugins allows to add new services and expose the contents of new extensions.

In this section we will describe how to create custom modules, and what is the structure of a jBackend plugin. For this purpose it was created an "Hello World" plugin available for download here:

http://www.selfget.com/downloads/file/31-plg-jbackend-helloworld-j30.html

Structure of supported requests

Each HTTP request to an jBackend endpoint MUST include at least three parameters:

action=<action_type>
module=<module_name>
resource=<service_request>

The action would specify one of the HTTP methods used in a RESTful service (e.g. GET, PUT, POST, or DELETE). This is because all Joomla requests are made using GET, so this FIRST parameter introduces the missing information.

The module parameter indicates the name of the module that has in charge of managing this request (e.g. content). This can be implemented as a single jBackend plugin, or even a pool of plugins where each one implements a subset of module services. As example, for com_content core extension a content plugin already exists, which provides read-only access to content and categories. It could be possible to create a new plugin (e.g. plg_jbackend_content_admin) registered also as content module, which takes in charge editor functions like create content and categories.

The resource parameter specifies the service we are requesting to the module (e.g. articles of the content module, login service of the user module, etc.). This parameter must be validated and managed by the plugin itself, no checks will be made on this by jBackend (it cannot know about services provided by each plugin).

Considering all the required parameters (action, module, and resource), each request has this structure:

<end-point>?action=<action_type>&module=<module_name>&resource=<service_request>

If Joomla SEF is enabled, the request can assume this REST-like structure:

<end-point>/<action_type>/<module_name>/<service_request>

A request can also take additional parameters needed by the resource itself for the required service (e.g. the id of the article). This parameters can be added as a standard URL query string, so the request structure assumes this aspect:

<end-point>?action=<action_type>&module=<module_name>&resource=<service_request>&var1=<value1>&...&varN=<valueN>

Or can be (when SEF is enabled):

<end-point>/<action_type>/<module_name>/<service_request>?var1=<value1>&...&varN=<valueN>

An event onBeforeCheckModule is triggered just before to check the module name to dispatch. This permits to override the module variable (as well as action and resource variables) before the request dispatching process and can be useful, as example, when a plugin must deal with existing client requests that cannot be customized as required by jBackend.

Using onBeforeCheckModule it is possible, as example, to match a request like the following:

<endpoint>?var=catchme

This request doesn't include any of the required variables (action, module, resource), but we can "catch" the var and set the required variables:

  public function onBeforeCheckModule()
  {
    $app = JFactory::getApplication();
    $var = $app->input->getString('var');
    if ($var === 'catchme') {
      $app->input->set('action', 'get');
      $app->input->set('module', 'content');
      $app->input->set('resource', 'articles');
      $app->input->set('catid', '64');
      $app->input->set('id', '71');
    }
  }

Basic elements of a jBackend plugin

As a standard Joomla plugin, any jBackend plugin needs an XML installation file with the following structure:

<?xml version="1.0" encoding="utf-8"?>
<extension version="3.0" type="plugin" group="jbackend" method="upgrade">
    <name>plg_jbackend_helloworld</name>
...
</extension>

The plugin group must be "jbackend". All other XML elements are the same of a standard Joomla plugin. The PHP plugin code should have a generateError() function which builds and return all plugin specific errors:

  /**
   * This is the function to generate plugin specific errors
   * The error response is an array with the following structure:
   *    array(
   *     'status' => 'ko',
   *     'error_code' => $errorCode,
   *     'error_description' => <short error description>
   *    )
   *
   * @param  string  $errorCode  The error code to generate
   *
   * @return  array  The response of type error to return
   */
  public static function generateError($errorCode)
  {
    $error = array();
    $error['status'] = 'ko';
    switch($errorCode) {
      case 'REQ_ANS':
        $error['error_code'] = 'REQ_ANS';
        $error['error_description'] = 'Action not specified';
        break;
      case 'HWD_GEN':
        $error['error_code'] = 'HWD_GEN';
        $error['error_description'] = 'Generic hello world error';
        break;
    }
    return $error;
  }

It is not mandatory, but a good practice for the format of the error code is to use a pattern like XXX_YYY, where XXX is a code associated to the plugin (e.g. HWD for the helloworld plugin), and YYY for the specific error (e.g. GEN for generic error).

The other function that any jBackend plugin should have is the onRequest<Module>() function. This is called by jBackend during requests dispatching process. It has three parameters, an input parameter $module that is the module name and is used by the plugin to understand if it should handle the request, an output parameter $response that is the response object, and a $status parameter that can return additional information. The function returns true if there are no problems (status = ok), false in case of errors (status = ko):

public function onRequestHelloWorld($module, &$response, &$status = null)

The first thing the onRequest function should do is to check if it must handle the request or not. If not, it should return to improve the performance of the dispatching process:

if ($module !== 'helloworld') return true; // Check if this is the triggered module or exit

The second thing is to add the plugin to the module call stack:

// Add to module call stack
jBackendHelper::moduleStack($status, 'helloworld'); // Add this request to the module stack register

After these tasks, it is possible to get request vars and process the response:

$app = JFactory::getApplication();
$action = $app->input->getString('action');
$resource = $app->input->getString('resource');

// Check if the action is specified
if (is_null($action)) {
  $response = plgJBackendHelloWorld::generateError('REQ_ANS'); // Action not specified
  return false;
}

switch ($resource)
{
  case 'greeting':
    if ($action == 'get')
    {
      return $this->actionGreeting($response, $status);
    }
    break;
  case 'about':
    if ($action == 'get')
    {
      return $this->actionAbout($response, $status);
    }
    break;
}

return true;

Each function like actionGreeting() and actionAbout() is specific to manage the resource requested by the action. To maintain the code clean it is a good practice to create a different function for each action.

The following is an example of an action() function that shows the code structure and how to manage the errors:

  public function actionGreeting(&$response, &$status = null)
  {
    $app = JFactory::getApplication();

    // Get additional request parameters
    $id = $app->input->getInt('id');
    if (!is_null($id))
    {
      // Example of how to generate and return an error inside an action function
      if ($id == '101')
      {
        $response = plgJBackendHelloWorld::generateError('HWD_GEN'); // Generic hello world error
        return false;
      }
    }

    // Get plugin params
    $option_name = $this->params->get('option_name', 0);

    $response['status'] = 'ok';
    $response['message'] = $this->_greeting_msg;
    if ($option_name)
    {
      $response['option'] = 'true';
    }
    return true;
  }