Home  Support Page  The Manual  Use-cases

Ajax-integrated service

Contents

Overview

An ajax-integrated web-service is a service which may handle various types of requests: both simple HTML requests and background AJAX requests.

Say, a service should handle user requests and give a plain HTML as a result, rendering it using the model. At the same time, a service should use the same tools (a model, MVC) to handle background (AJAX) requests which differ from other requests by the protocol.

Phoebius has no restrictions on what should be sent to the client; but, it provides an architecture which helps a developer to make reusable applications even for unknown requirements.

This article will explain, how to integrate a layer that will serve simple AJAX requests and produce results in JSON.

Reusing the MVC stack

A common application stack is the following:

request -> routing over URL -> dispatcher -> controller -> action -> action result

To make application handle AJAX requests at the same stack, all we need to do is the following:

  • add a separate rule for ajax requests to catch them
  • introduce a separate abstract controller that will produce results with AJAX protocol conformance
  • use action result that makes an AJAX-compliant response

Extending router

An application has no need of a separate front-controller to accept background AJAX requests, it is enough to catch them via the internal routing system.

The easiest way to do this is to reserve a URL for AJAX requests in the following form:

/ajax/<controller>/<action>/

A code for adding the route may look like that:

// $router instance of ChainedRouter
$router->route(
	'ajax',
	'/ajax/(PublicAjax|AdminAjax):controller/:action/'
);

This code forces router to raise a custom PublicAjaxController or AdminAjaxController and then invoke an action which is determined from the second particle of a path in the URL. For example, a URL /ajax/PublicAjax/getUserList/ will force PublicAjaxController::action_get_user_list() to be invoked.

So, you see that AJAX requests can be routed similarly to other requests.

Preparing the controller

This step is obligatory in case when the application should serve a lot of different background requests. We recommend to implement an abstract controller that will be a base for other ajax controllers. It will simplify the development by providing the following features:

  • an invoked action method may return an array and it will be wrapped as a resulting data
  • an invoked action method may return a string and it will be treated as an error message, occurred during request handling
  • it will catch all uncaught exceptions and unknwon method invocations and build a correct error response to avoid AJAX crash (as AJAX protocol is error-prone and requires stability in handling errors)

To provide these features, BaseAjaxController just overloads the methods introduced by the ActionBasedController:

lib/BaseAjaxController.class.php

abstract class BaseAjaxController extends ActionBasedController {

	// allows your action methods (action_*) to return the
	// following:
	// 1. an array - then it is wrapped as a resulting adata
	// 2. a string -then it is treated as an error message
	protected function makeActionResult($actionResult)
	{
		if (is_array($actionResult)) {
			return new JsonResult(array(
				'protocol' => '1.0',
				'response' => $actionResult
			));
		}
		else if (is_string($actionResult) {
			return new JsonResult(array(
				'protocol' => '1.0',
				'error' => true,
				'msg' => $actionResult
			));
		}
			
		Assert::isUnreachable(
			'Ajax result can be of array or error message only'
		);
	}
	
	// redirect all exceptions occurred within actions to a normal error
	protected function processAction($action, ReflectionMethod $method)
	{
		try {
			return parent::processAction($action, $method);
		}
		catch (Exception $e) {
			return get_class($e) . ': ' . $e->getMessage();
		}
	}
	
	// if an unknown action is requested, then handle it too
	protected function handleUnknownAction($action)
	{
		return 'unknown action: ' . $action;
	}
}


After that we can create controllers based on our custom BaseAjaxController. Let say, that it we need two controllers: one for public functions (PublicAjaxController) which serves the requests without any restriction, and for administration functions (AdminAjaxController) which will also check whether client is authorized or not.

Public controller is ready to have actions. Let say, that it needs to have an action that returns the list of ids and names of all users, defined in the database:

lib/PublicAjaxController.class.php


class PublicAjaxController extends BaseAjaxController {

	// handles /ajax/PublicAjax/get_user_list/
	function action_get_user_list() {
		$yield = array();
		
		$users = User::dao()->getList();
		foreach ($users as $user) {
			$yield[] = array(
				'id' => $user->getId(),
				'name' => $user->getName()
			);
		}
		
		return $yield;
	}
}

Administrator's controller needs to have an extra check: whether the client is authorized or not, so it overloads a method that invokes an action method:

lib/AdminAjaxController.class.php

class AdminAjaxController extends BaseAjaxController {
	protected function processAction($action, ReflectionMethod $method) {
	
		/* check credentials here */
		if (!isAuthorized($this->getTrace()->getRequest()) {
			return 'client not authorized';
		}
	
		return parent::processAction($action, $method);
	}
	
	/* admin actions here */
}

Custom action result

In a mentioned example we used an JsonResult to represent an array as JSON result. You may want to implement your own, custom action results, that will produce response according to other protocol, e.g. XML. All you need to do is implement IActionResult.

A real-world example

Download a basic application - a simple directory browser - with background AJAX requests and ability to create new directories after successful authorization.