Skip to main content

Ben Cromwell

Custom HTTP routes in Zend Framework 1 MVC

Scenario: you’re stuck with Zend 1 (which will support PHP7 - great news for those of us with legacy apps!) and you’re building an API.

You’ve got routes like /api/categories, and /api/products but you want to provide some nice, nested resource routes, such as /api/categories/42/products to get all products in a given category. This would be an alternative to /api/products?categoryId=42.

I poked around at ZF1’s router and came up with the following. It allows you to have both routes functional, giving your end users flexibility, whilst only requiring a single Controller.

Zend’s Bootstrap:



		protected function _initApiRoute()
		{
			$front = $this->_frontController;
			$router = $front->getRouter();

			if ($router instanceof Zend_Controller_Router_Rewrite) {
				(new \Api\Routes($front, $router))->initRoutes();
			}
		}

Routes:



namespace Api;

use Zend_Controller_Front as FrontController;
use Zend_Rest_Route as RestRoute;
use Zend_Controller_Router_Rewrite as Router;
use Zend_Controller_Router_Route_Regex as RouteRegex;

class Routes
{

	/** @var FrontController */
	private $front;

	/** @var Router */
	private $router;

	public function __construct(FrontController $front, Router $router)
	{
		$this->front = $front;
		$this->router = $router;
	}

	public function initRoutes()
	{
		// zf1 matches routes in reverse order, so we start with defaults and then add more specific overrides
		// most normal rest resources are covered by this route
		$this->router->addRoute('api', new RestRoute($this->front, array(), array('api')));

		// match specific overrides

                // this example would find all products in the given category ID
                // equivalent to doing /api/products?categoryId=42
		$this->addRoute('api/categories/(.+)/products',      'products',     ['categoryId']);
	}

	/**
	 * @param string $regex
	 * @param string $controller
	 * @param array  $params
	 */
	private function addRoute($regex, $controller, $params = [])
	{
		// remap params to have keys starting with 1
		$keyedParams = [];
		$i = 1;
		foreach ($params as $param) {
			$keyedParams[$i] = $param;
			$i++;
		}

		$this->router->addRoute($regex, new RouteRegex(
			$regex,
			[
				'module' => 'api',
				'controller' => $controller,
			],
			$keyedParams
		));
	}

}