Jump to table of contents

Handlers

Every request is routed to a Handler, which is responsible for authorizing the request, fetching data and formatting the response.

Code inside of a Handler can not be reused, so Handlers should not perform these actions themselves. Instead, they should coordinate other classes to fulfil the request.

Handlers do not authorize the request and should not be used without appropriate authorization policies.

Tip: Handlers perform the role of Controllers in the MVC (Model-View-Controller) application architecture.

Page Handlers #

Page Handlers receive GET requests and return HTML output. The Page Handler must define a method for each op it supports.

import('classes.handler.Handler');
class IssueHandler extends Handler {
	/**
	 * Display the table of contents for the current issue
	 */
	public function current(Array $args, Request $request) {
		return '<html>...</html>';
	}
}

Learn more about page URLs, routes and ops.

Page Indexes #

Page Handlers may define an index op to handle URLs that do not contain an op.

import('classes.handler.Handler');
class IssueHandler extends Handler {
	/**
	 * Display a list of all issues
	 */
	public function index(Array $args, Request $request) {
		return '<html>...</html>';
	}
}

The router must declare the index op:

switch ($op) {
	case 'index':
		define('HANDLER_CLASS', 'IssueHandler');
		import('pages.issue.IssueHandler');
		break;
}

Page Arguments #

Any URL fragments that are appended after the op will be passed to the Handler’s method in the $args param.

Diagram indicating the parts of a URL for Page Handlers

class IssueHandler extends Handler {
	public function view(Array $args, Request $request) {
		$issueId = isset($args[0]) ? (int) $args[0] : null;
		return '<html>...</html>';
	}
}

Return a 404 error when page arguments request an entity that does not exist.

class IssueHandler extends Handler {
 public function view(Array $args, Request $request) {
	 $issueId = isset($args[0]) ? (int) $args[0] : null;
	 if (/* issue not found */) {
		 $this->getDispatcher()->handle404();
	 }
	 return '<html>...</html>';
 }
}

Responses #

Page Handlers return HTML code using the TemplateManager.

class IssueHandler extends Handler {
	public function current(Array $args, Request $request) {
		$templateMgr = TemplateManager::getManager($request);
		return $templateMgr->display('/path/to/template.tpl');
	}
}

Templates use the Smarty (v3) templating library.

API Handlers #

API Handlers use the Slim API framework and return JSON output. Requests are routed to endpoint callbacks defined in the Handler’s constructor.

$ curl https://example.org/publicknowledge/api/v1/submissions
import('lib.pkp.classes.handler.APIHandler');
class PKPSubmissionsHandler extends APIHandler {
	public function __construct() {
		$this->_endpoints = [
			'GET' => [
				[
					'pattern' => 'submissions',
					'handler' => [$this, 'getMany'],
				],
			],
		];
	}
	public function getMany($slimRequest, $response, $args) {
		return $response->withJson([...], 200);
	}
}

Path Variables #

Routing can identify named variables in the URL path that are part of the endpoint.

$ curl https://example.org/publicknowledge/api/v1/submissions/1
import('lib.pkp.classes.handler.APIHandler');
class PKPSubmissionsHandler extends APIHandler {
	public function __construct() {
		$this->_endpoints = [
			'GET' => [
				[
					'pattern' => 'submissions/{submissionId}',
					'handler' => [$this, 'get'],
				],
			],
		];
	}
	public function get($slimRequest, $response, $args) {
		$submissionId = (int) $args['submissionId'];
		return $response->withJson([...], 200);
	}
}

Query Params #

Query parameters in the URL can be accessed through the $slimRequest object.

$ curl https://example.org/publicknowledge/api/v1/submissions?searchPhrase=barnes
import('lib.pkp.classes.handler.APIHandler');
class PKPSubmissionsHandler extends APIHandler {
	public function __construct() {
		$this->_endpoints = [
			'GET' => [
				[
					'pattern' => 'submissions',
					'handler' => array($this, 'getMany'),
				],
			],
		];
	}
	public function getMany($slimRequest, $response, $args) {
		$params = $slimRequest->getQueryParams(); // ['searchPhrase' => 'barnes']
		return $response->withJson([...], 200);
	}
}

Request Body #

POST and PUT requests include data in the body of the request, which can be accessed with $slimRequest->getParsedBody().

$ curl https://example.org/publicknowledge/api/v1/submissions/1 \
  -d '{"contactEmail": "editor@example.org"}' \
  -H "Content-Type: application/json" \
  -X PUT
import('lib.pkp.classes.handler.APIHandler');
class PKPSubmissionsHandler extends APIHandler {
	public function __construct() {
		$this->_endpoints = [
			'PUT' => [
				[
					'pattern' => 'submissions/{submissionId}',
					'handler' => array($this, 'edit'),
				],
			],
		];
	}
	public function edit($slimRequest, $response, $args) {
		$data = $slimRequest->getParsedBody(); // ['contactEmail' => 'editor@example.org']
		...
	}
}

Responses #

API Handlers are passed a $response object which should be returned using the $request->withJson() method.

import('lib.pkp.classes.handler.APIHandler');
class PKPSubmissionsHandler extends APIHandler {
	public function __construct() {
		$this->_endpoints = [...];
	}
	public function get($slimRequest, $response, $args) {
		return $response->withJson([
			'id' => 1,
			'title' => 'Example Submission',
		], 200);
	}
}

Always return the correct HTTP Status Code.

import('lib.pkp.classes.handler.APIHandler');
class PKPSubmissionsHandler extends APIHandler {
	public function __construct() {
		$this->_endpoints = [...];
	}
	public function get($slimRequest, $response, $args) {
		if (/* no submission found */) {
			return $response->withJson(false, 404);
		}
		if (/* submission access not allowed */) {
			return $response->withJson(false, 403);
		}
		return $response->withJson([
			'id' => 1,
			'title' => 'Example Submission',
		], 200);
	}
}

Error responses from the API should pass a locale key that describes the error.

import('lib.pkp.classes.handler.APIHandler');
class PKPSubmissionsHandler extends APIHandler {
	public function __construct() {
		$this->_endpoints = [...];
	}
	public function get($slimRequest, $response, $args) {
		if (/* no submission found */) {
			return $response->withStatus(404)->withJsonError('api.submissions.404.submissionNotFound');
		}
		...
	}
}

Request Objects #

The $slimRequest object is a PSR 7 object created by the Slim API framework. It is not an instance of the application’s main Request object that is passed to other Handler ops. The application’s main Request object can be accessed with APIHandler::getRequest().

class PKPSubmissionsHandler extends APIHandler {
	public function get($slimRequest, $response, $args) {
		$request = $this->getRequest();
		...
	}
}

Slim Framework #

Read the Slim API Framework usage guide to learn more about the $slimRequest and $response objects.

Controller Handlers (deprecated) #

Controller Handlers receive requests from UI Controllers and return JSON output. They act like Page Handlers except that they serve individual interactive UI components, such as a submission’s list of files, discussions or participants.

Controller Handlers are deprecated. New features should be built using the UI Library components that interact with API Handlers. However, they are common throughout the application and will remain in use for some time.

Learn more about controller URLs, routes and ops.

JSONMessage Response #

Use the JSONMessage class to format a response for Controller Handlers.

import('classes.controllers.grid.issues.IssueGridHandler');
class BackIssueGridHandler extends IssueGridHandler {
	public function deleteIssue($args, $request)
		...
		return JSONMessage(true);
	}
}

A Controller Handler will often return an HTML string in the JSON data package, which will be used by the UI Controllers to update the DOM.

import('classes.controllers.grid.issues.IssueGridHandler');
class BackIssueGridHandler extends IssueGridHandler {
	public function editIssue($args, $request)
		$templateMgr = TemplateManager::getManager($request);
		return JSONMessage(true, $templateMgr->display('/path/to/template.tpl'));
	}
}

Learn how the user is authenticated.