Issues

ZF-8584: Add format to rest routes

Issue Type: Improvement Created: 2009-12-18T11:52:30.000+0000 Last Updated: 2012-11-20T21:38:06.000+0000 Status: Open Fix version(s): Reporter: Doug P (doug316) Assignee: None Tags: - Zend_Rest_Route

Related issues: Attachments:

Description

The goal is to be able to request a specific content type in a REST URL:

/controller.json or /controller/index.json /controller/22.json

This patch will remove the extension after the period and place it in params['format'].

Code assumes that actions don't use periods (i.e. index.xml mapping to indexXmlAction).

<pre class="literal"> 
--- Route.php   2009-11-19 16:36:49.000000000 -0800
+++ Route2.php  2009-12-18 11:50:26.000000000 -0800
@@ -67,6 +67,12 @@ class Zend_Rest_Route extends Zend_Contr
     protected $_front;
 
     /**
+     * Array keys to use for format. Should be taken out of request.
+     * @var string
+     */
+    protected $_formatKey = 'format';
+   
+    /**
      * Constructor
      *
      * @param Zend_Controller_Front $front Front Controller object
@@ -129,10 +135,14 @@ class Zend_Rest_Route extends Zend_Contr
             // Determine Controller
             $controllerName = $this->_defaults[$this->_controllerKey];
             if (count($path) && !empty($path[0])) {
-                if ($this->_checkRestfulController($moduleName, $path[0])) {
-                    $controllerName = $path[0];
-                    $values[$this->_controllerKey] = array_shift($path);
+               preg_match('/([^\.]+)(\.(.+))?/', $path[0], $matches);
+                if ($this->_checkRestfulController($moduleName, $matches[1])) {
+                    $controllerName = $matches[1];
+                    $values[$this->_controllerKey] = $matches[1];
+                   if (!empty($matches[3]))
+                       $values[$this->_formatKey] = $matches[3];
                     $values[$this->_actionKey] = 'get';
+                   array_shift($path);
                 } else {
                     // If Controller in URI is not found to be a RESTful
                     // Controller, return false to fall back to other routes
@@ -145,13 +155,23 @@ class Zend_Rest_Route extends Zend_Contr
 
             // Check for leading "special get" URI's
             $specialGetTarget = false;
-            if ($pathElementCount && array_search($path[0], array('index', 'new')) > -1) {
-                $specialGetTarget = array_shift($path);
+           preg_match('/([^\.]+)(\.(.+))?/', $path[0], $matches);
+            if ($pathElementCount && array_search($matches[1], array('index', 'new')) > -1) {
+                $specialGetTarget = $matches[1];
+               if (!empty($matches[3]))
+                   $values[$this->_formatKey] = $matches[3];
+                array_shift($path);
             } elseif ($pathElementCount && $path[$pathElementCount-1] == 'edit') {
                 $specialGetTarget = 'edit';
-                $params['id'] = $path[$pathElementCount-2];
+                preg_match('/([^\.]+)(\.(.+))?/', $path[$pathElementCount-2], $matches);
+                $params['id'] = $matches[1];
+               if (!empty($matches[3]))
+                   $values[$this->_formatKey] = $matches[3];
             } elseif ($pathElementCount == 1) {
-                $params['id'] = array_shift($path);
+                preg_match('/([^\.]+)(\.(.+))?/', array_shift($path), $matches);
+                $params['id'] = $matches[1];
+               if (!empty($matches[3]))
+                   $values[$this->_formatKey] = $matches[3];
             } elseif ($pathElementCount == 0 || $pathElementCount > 1) {
                 $specialGetTarget = 'index';
             }
@@ -199,13 +219,14 @@ class Zend_Rest_Route extends Zend_Contr
         $this->_values = $values + $params;
 
         $result = $this->_values + $this->_defaults;
-        
+
         if ($partial && $result)
            $this->setMatchedPath($request->getPathInfo());
-           
+
         return $result;
     }
 
+
     /**
      * Assembles user submitted parameters forming a URL path defined by this route
      *

Comments

Posted by Matthew Weier O'Phinney (matthew) on 2009-12-18T12:04:22.000+0000

Hmm... typically, this sort of thing should be specified in the Accept header, not as part of the URI. As such, you can determine the format dynamically, and dispatch to the appropriate view. This also can be done on-demand in plugins, without further complicating the route logic.

Posted by Doug P (doug316) on 2009-12-18T12:40:51.000+0000

Accept headers are nice but so is a tidy URI with a format specifier which simplifies API usage and eases debug. Rails routes support this by default.

Posted by Luke Crouch (lcrouch) on 2009-12-18T12:52:34.000+0000

This can already be easily done with ContextSwitch in the controller and a format parameter on the URI:

<pre class="highlight">
class NewsController
{
    public function init()
    {
        parent::init();
        $this->_contextSwitch->addActionContexts(array('index' => array('json', 'xml')));
        $this->_contextSwitch->initContext();
    }
}

/news/index/format/xml /news/index/?format=xml /news/index/format/json /news/index/?format=json

Posted by Doug P (doug316) on 2009-12-22T12:39:58.000+0000

The suggested workaround ideas are appreciated, but are not quite the same. There are good reasons for mapping URI's using file type extensions.

Not sure but it appears there might be a general resistance to extend the framework. The rest route appears to not yet have many important features which I suppose we'll implement in our own proprietary libraries. Nested resources, implicit controller specification, file type extensions, and overall the general ability to control what URI's look like are important.

Posted by Luke Crouch (lcrouch) on 2009-12-22T13:58:50.000+0000

"There are good reasons for mapping URI's using file type extensions."

Without knowing what these "good reasons" are, yes - I am still resisting this change. It has to do with the whole "URI opacity" axiom - http://www.w3.org/DesignIssues/Axioms.html#opaque I.e., according to REST architectural principles, there's no difference between /news/index.json and /news/index/?format=json

Furthermore, this route is just one method of implementing RESTful architecture in ZF - specifically, it's a method that leverages Zend_Controller_Router_Rewrite. We can easily use static or regexp routing to implement more URI control like RoR or django.

To implement features like nested resources, implicit controllers, etc. we should use the proposal process and not a single JIRA ticket. I've been working on a proposal to enhance the 'Resource' part of Zend_Rest here:

http://framework.zend.com/wiki/display/…

Finally, RESTful implementation could be going thru a renovation as we work on ZF 2.0 so we may want to make a new 'Zend_Rest_2.0' proposal.

In either case, the feature request of this ticket is more appropriate for a proposal I think.

Posted by Moritz Mertinkat (maurice) on 2010-02-26T03:12:14.000+0000

I really appreciate Doug's enhancement, although I've not yet screened the patch.

Currently we're using the Accept-header approach but when working with people that are not really familiar with HTTP headers it sometimes gets a bit confusing. For example, file_get_contents does not send any Accept headers (without defining a context).

@Luke: You're right that "there's no difference between /news/index.json and /news/index/?format=json" but I myself would prefer the former so that REST API users won't have to mess with additional parameters. It's just convenience and the way Rails is doing it.

+1 for this issue/enhancement.

Posted by Doug P (doug316) on 2010-03-03T10:30:05.000+0000

@Luke: No objections over standards such as URI opacity. However I haven't convinced myself there's no place for what I'm doing here, at least in development mode.

As I use Rails as my model for best practices, I'll have to research their justification for this with respect to the same.

@Moritz: thanks for the support.

Have you found an issue?

See the Overview section for more details.

Copyright

© 2006-2016 by Zend, a Rogue Wave Company. Made with by awesome contributors.

This website is built using zend-expressive and it runs on PHP 7.

Contacts