Ticket Details

REST support

ENHANCEMENT Ticket (on hold)

###What was expected:###
I expected lithium add REST support to router.
it may like this:
{{{
Router::connect('/articles.{:type}', array('controller' => 'articles', 'action' => 'show') ,'get');
Router::connect('/articles/{:id:[0-9]+}.{:type}', array('controller' => 'articles', 'action' => 'edit') ,'get');
Router::connect('/articles.{:type}', array('controller' => 'articles', 'action' => 'create') ,'post');
Router::connect('/articles/{:id:[0-9]+}.{:type}', array('controller' => 'articles', 'action' => 'update') ,'put');
Router::connect('/articles/{:id:[0-9]+}.{:type}', array('controller' => 'articles', 'action' => 'destroy') ,'delete');
}}}
###My solution:###
I have modify Router and Route class in my libraies to support this.

in **lithium/http/Router.php**
{{{
	public static function connect($template, $params = array(), $method = 'get', $options = array()) {
		if ($template === null) {
			return static::__init();
		}

		if (!is_object($template)) {
			$params + array('action' => 'index');
			$class = static::$_classes['route'];
			$template = new $class(compact('template', 'params', 'method', 'options'));
		}
		return (static::$_configuration[] = $template);
	}
}}}

then in **lithium/http/Route.php**
{{{
class Route extends \lithium\core\Object {
	protected $_method = array();

	protected $_autoConfig = array(
		'template', 'pattern', 'keys', 'params', 'match', 'defaults', 'subPatterns' , 'method'
	);

	public function __construct($config = array()) {
		$defaults = array(
			'params' => array(),
			'template' => '/',
			'pattern' => '^[\/]*$',
			'match' => array(),
			'defaults' => array(),
			'keys' => array(),
			'method' => 'get',
			'options' => array()
		);
		parent::__construct((array) $config + $defaults);
	}

	public function parse($request) {
		if ($request instanceof \lithium\action\Request ){
			if(!$request->is($this->_method)){
				return false;
			}
		} elseif ($request instanceof \lithium\http\Request ){
			if(strcasecmp($request->method,$this->_method)!=0){
				return false;
			}
		}
		
		$url = '/' . trim($request->url, '/');
		if (preg_match($this->_pattern, $url, $match)) {
			$match['args'] = isset($match['args']) ?  explode('/', $match['args']) : array();
			$result = array_intersect_key($match, $this->_keys) + $this->_params + $this->_defaults;
			$result['action'] = $result['action'] ?: 'index';
			return $result;
		}
		return false;
	}

	public function export() {
		$result = array();
		$keys = array('template', 'pattern', 'keys', 'params', 'match', 'method', 'defaults', 'subPatterns');

		foreach ($keys as $key) {
			$result[$key] = $this->{'_' . $key};
		}
		return $result;
	}
}}}

###Finally,i add some test case.###

tests/http/RouteTest.php
{{{
public function testRouteParsingWithMethod() {
		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'GET'),
			'url' => '/articles.xml'
		));
		$route = new Route(array(
			'template' => '/articles.{:type}',
			'params' => array('controller' => 'articles', 'action' => 'show'),
			'method' => 'get'
		));
		$result = $route->parse($request);
		$this->assertEqual(array('controller' => 'articles', 'action' => 'show', 'type' => 'xml'), $result);

		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'POST'),
			'url' => '/articles.xml'
		));
		$result = $route->parse($request);
		$this->assertFalse($result);


		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'GET'),
			'url' => '/articles/1.xml'
		));
		$route = new Route(array(
			'template' => '/articles/{:id:\d+}.{:type}',
			'params' => array('controller' => 'articles', 'action' => 'edit'),
			'method' => 'get'
		));
		$result = $route->parse($request);
		$this->assertEqual(array('controller' => 'articles', 'action' => 'edit', 'id' => '1', 'type' => 'xml'), $result);

		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'POST'),
			'url' => '/articles/1.xml'
		));
		$result = $route->parse($request);
		$this->assertFalse($result);


		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'POST'),
			'url' => '/articles.xml'
		));
		$route = new Route(array(
			'template' => '/articles.{:type}',
			'params' => array('controller' => 'articles', 'action' => 'create'),
			'method' => 'post'
		));
		$result = $route->parse($request);
		$this->assertEqual(array('controller' => 'articles', 'action' => 'create', 'type' => 'xml'), $result);

		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'GET'),
			'url' => '/articles'
		));
		$result = $route->parse($request);
		$this->assertFalse($result);


		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'PUT'),
			'url' => '/articles/1.xml'
		));
		$route = new Route(array(
			'template' => '/articles/{:id:\d+}.{:type}',
			'params' => array('controller' => 'articles', 'action' => 'update'),
			'method' => 'put'
		));
		$result = $route->parse($request);
		$this->assertEqual(array('controller' => 'articles', 'action' => 'update', 'id' => '1', 'type' => 'xml'), $result);

		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'GET'),
			'url' => '/articles/1.xml'
		));
		$result = $route->parse($request);
		$this->assertFalse($result);


		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'DELETE'),
			'url' => '/articles/1.xml'
		));
		$route = new Route(array(
			'template' => '/articles/{:id:\d+}.{:type}',
			'params' => array('controller' => 'articles', 'action' => 'destroy'),
			'method' => 'delete'
		));
		$result = $route->parse($request);
		$this->assertEqual(array('controller' => 'articles', 'action' => 'destroy', 'id' => '1', 'type' => 'xml'), $result);

		$request = new \lithium\action\Request(array(
			'env' => array('REQUEST_METHOD' => 'GET'),
			'url' => '/articles/1.xml'
		));
		$result = $route->parse($request);
		$this->assertFalse($result);
	}
}}}

tests/http/RouterTest.php
{{{
	public function testRouteConnectionWithDefaultMethod() {
		$result = Router::connect('/{:controller}/{:action}');
		$expected = array(
			'template' => '/{:controller}/{:action}',
			'pattern' => '@^(?:/(?P<controller>[^\\/]+))(?:/(?P<action>[^\\/]+)?)?$@',
			'params' => array('action' => 'index'),
			'match' => array(),
			'defaults' => array('action' => 'index'),
			'keys' => array('controller' => 'controller', 'action' => 'action'),
			'subPatterns' => array(),
			'method' => 'get'
		);
		$this->assertEqual($expected, $result->export());
	}

	public function testRouteConnectionWithMethod() {
		$result = Router::connect('/articles.{:type}', array('controller' => 'articles', 'action' => 'show') ,'get');
		$expected = array(
			'template' => '/articles.{:type}',
			'pattern' => '@^/articles\.(?P<type>[^\/]+)$@',
			'params' => array('controller' => 'articles', 'action' => 'show'),
			'match' => array('controller' => 'articles', 'action' => 'show'),
			'defaults' => array(),
			'keys' => array('type' => 'type'),
			'subPatterns' => array(),
			'method' => 'get'
		);
		$this->assertEqual($expected, $result->export());

		$result = Router::connect('/articles/{:id}.{:type}', array('controller' => 'articles', 'action' => 'edit') ,'get');
		$expected = array(
			'template' => '/articles/{:id}.{:type}',
			'pattern' => '@^/articles(?:/(?P<id>[^\/]+))\.(?P<type>[^\/]+)$@',
			'params' => array('controller' => 'articles', 'action' => 'edit'),
			'match' => array('controller' => 'articles', 'action' => 'edit'),
			'defaults' => array(),
			'keys' => array('id' => 'id', 'type' => 'type'),
			'subPatterns' => array(),
			'method' => 'get'
		);
		$this->assertEqual($expected, $result->export());

		$result = Router::connect('/articles.{:type}', array('controller' => 'articles', 'action' => 'create') ,'post');
		$expected = array(
			'template' => '/articles.{:type}',
			'pattern' => '@^/articles\.(?P<type>[^\/]+)$@',
			'params' => array('controller' => 'articles', 'action' => 'create'),
			'match' => array('controller' => 'articles', 'action' => 'create'),
			'defaults' => array(),
			'keys' => array('type' => 'type'),
			'subPatterns' => array(),
			'method' => 'post'
		);
		$this->assertEqual($expected, $result->export());

		$result = Router::connect('/articles/{:id}.{:type}', array('controller' => 'articles', 'action' => 'update') ,'put');
		$expected = array(
			'template' => '/articles/{:id}.{:type}',
			'pattern' => '@^/articles(?:/(?P<id>[^\/]+))\.(?P<type>[^\/]+)$@',
			'params' => array('controller' => 'articles', 'action' => 'update'),
			'match' => array('controller' => 'articles', 'action' => 'update'),
			'defaults' => array(),
			'keys' => array('id' => 'id', 'type' => 'type'),
			'subPatterns' => array(),
			'method' => 'put'
		);
		$this->assertEqual($expected, $result->export());

		$result = Router::connect('/articles/{:id}.{:type}', array('controller' => 'articles', 'action' => 'destroy') ,'delete');
		$expected = array(
			'template' => '/articles/{:id}.{:type}',
			'pattern' => '@^/articles(?:/(?P<id>[^\/]+))\.(?P<type>[^\/]+)$@',
			'params' => array('controller' => 'articles', 'action' => 'destroy'),
			'match' => array('controller' => 'articles', 'action' => 'destroy'),
			'defaults' => array(),
			'keys' => array('id' => 'id', 'type' => 'type'),
			'subPatterns' => array(),
			'method' => 'delete'
		);
		$this->assertEqual($expected, $result->export());
	}

	public function testRouteMatchingWithMethod() {
		Router::connect('/articles.{:type}', array('controller' => 'articles', 'action' => 'show') ,'get');
		Router::connect('/articles/{:id}.{:type}', array('controller' => 'articles', 'action' => 'edit') ,'get');
		Router::connect('/articles.{:type}', array('controller' => 'articles', 'action' => 'create') ,'post');
		Router::connect('/articles/{:id}.{:type}', array('controller' => 'articles', 'action' => 'update') ,'put');
		Router::connect('/articles/{:id}.{:type}', array('controller' => 'articles', 'action' => 'destroy') ,'delete');

		$request = new Request(array(
			'env' => array('REQUEST_METHOD' => 'GET'),
			'url' => '/articles.xml')
		);
		$result = Router::parse($request);
		$expected = array('controller' => 'articles', 'action' => 'show', 'type' => 'xml');
		$this->assertEqual($expected, $result);

		$request = new Request(array(
			'env' => array('REQUEST_METHOD' => 'GET'),
			'url' => '/articles/1.xml')
		);
		$result = Router::parse($request);
		$expected = array('controller' => 'articles', 'action' => 'edit', 'id' => '1', 'type' => 'xml');
		$this->assertEqual($expected, $result);

		$request = new Request(array(
			'env' => array('REQUEST_METHOD' => 'POST'),
			'url' => '/articles.xml')
		);
		$result = Router::parse($request);
		$expected = array('controller' => 'articles', 'action' => 'create', 'type' => 'xml');
		$this->assertEqual($expected, $result);

		$request = new Request(array(
			'env' => array('REQUEST_METHOD' => 'PUT'),
			'url' => '/articles/1.xml')
		);
		$result = Router::parse($request);
		$expected = array('controller' => 'articles', 'action' => 'update', 'id' => '1', 'type' => 'xml');
		$this->assertEqual($expected, $result);

		$request = new Request(array(
			'env' => array('REQUEST_METHOD' => 'DELETE'),
			'url' => '/articles/1.xml')
		);
		$result = Router::parse($request);
		$expected = array('controller' => 'articles', 'action' => 'destroy', 'id' => '1', 'type' => 'xml');
		$this->assertEqual($expected, $result);
	}
}}}

Sorry for my poor english.
on 02.02.10 reported by: kaptin owned by: nate

Updates

on 07.10.10 by nate
  • version was changed to lithium-0.9.5
  • owner was changed to nate
  • status was changed to on hold
Hi kaptin, thanks for your hard work on this patch. It is now possible to create routes based on HTTP methods and verbs using the following syntax:

{{{
Router::connect('/{:controller}/{:id:[0-9]+}', array('http:method' => 'PUT', 'action' => 'edit'));
}}}

For right now we feel that this is sufficient, but we're holding this ticket for a future release, when a higher-level resources API can be decided on. Thanks again for your efforts.