Introduction;
The current way that the Zend Framework handles URI structure is simple enough. It serves a logical purpose and is easy break apart into it's components;
Zend Framework's URI structure:
/controller/action/key1/value1/key2/value/
Ok, so that pretty much makes sense. After a quick read of the framework manual, it should be clear to you what controllers, actions and key-pairs are. The structure is frozen like that however, and you can't make one URI node look a parent of another without cheating.
Example 1 (Semantically correct URI, with key-pair):
/tag/view/name/framework/Example 2 (Only key specified, no value):
/tag/view/framework/Example 3 (Using __call to catch tag-name):
/tag/framework/
The above (example 3) is how Technorati specified the URI for their view-tag page. After all, it's not preferable to waste URI segments, like the previous examples did. Also the web address for any given page should allow for the user to easily self-navigate to a parent node without difficulty.
Technorati breaks from this slightly in that /tag/ is used as the parent instead of the literal parent node /tags/. If /tag/ is accessed directly, you get the same content as /tags/. Personally I opt for a /tag/ controller to exist with a single index action, which would then use _redirect to forward the user to the literal parent node.
Another real-world example:
/article/*name*/comments/
In the above example, /article/ is a controller, but so is /comments/. To implement this in the current Zend Framework route procedure, you have to completely break the semantic rules. /article/ becomes the controller, __call becomes a dynamic view action, and /comments/ effectively becomes a GET value.
Key-Pair Problem;
When writing the patch I came across a problem. It becomes nessesary to choose between the following;
Method A)
Method B)
- Rely on __call to deal with dynamic pages (still partially cheating).
- Transfers key-pairs from redirecting to to target URI
- Redirection rules of higher level nodes (eg. /article/) are still activated for lower level children (eh. /article/view/).
- Enables semantically correct structure (/controller/action/key/val/)
- Is not reliant on __call.
- Doesn't transfer key-pairs.
- URI with keypairs are treated as seperate addresses
The reason for having to choose between tradeoffs is based on the fact that in the ZF structure, a key-pair looks exactly the same as an action or a controller. I propose a comprimise on this issue and include an equals sign in the key-pair URI syntax.
Proposed key-pair syntax improvement:
/controller/action/key1=value1/key2=value/
As well as providing a clearer distinction, and solving the above problem, the modification would have usability benefits. The ability for the user to self navigate to a higher level node is hindered in the current system. A user might just a key-pair value and nothing more. The ?key=value syntax is ugly sure, but at least a user is aware that's a parameter rather than a node.
In the config part of the patch, you can specify a flag to decide which or the above methods you want. Any comments specifically regarding this are very welcome.
The Patch;
The framework's plugin architecture exists now, but there is no documentation. If there is a simple way to currently make a plugin, and someone tells me, I will make this hack into one. Also, it is possible to subclass framework components to extend, but given that other from inserted code, the class stays exactly the same, i'm using a direct patch to the framework's Router class rather than subclassing.
Given this fact, the business code part of the patch should be inserted directly into the Framework's Zend_Controller_Router class, so you will need access to it's library directories.
Config Code:
(Inserted after Zend classfile included, and before Controller_Front Instantiation.)// A couple of example rules:
$uriMap = array(
'/people/index/' => '/people/',
'/person/*uname*/' => '/company/*uname*/'
);
Zend::register('uri-map', (object) $uriMap);
// 1 = Method A, 0 = Method B:
Zend::register('keep-pairs', (object) 1);
Business Code:
(Inserted at the start of route($dispatch) method, on line 49, in Zend/Controller/Router.php)/**
* PATCH - Hierarchical URI Pre-Processing
* @version 1.0
* @author Daniel Morris
*/
$uriMap = (array) Zend::registry('uri-map');
$uriMapKeys = array_keys($uriMap);
$uriSegments = explode('/', $_SERVER['REQUEST_URI']);
$transferVars = null;
$transferPairs = Zend::registry('keep-pairs')->scalar;
$uriTarget = null;
$uriWithoutKeyPairs = null;
$matchFound = false;
for($i = 0; $i < count($uriMap); $i++) {
reset($uriSegments);
$transferVars = array();
$countMatches = 0;
$countSegments = 0;
$uriMapSegments = explode('/', current($uriMapKeys));
for($j = 1; $j < count($uriMapSegments); $j++) {
$currentMapSegment = current($uriMapSegments);
if ($currentMapSegment) {
$countSegments++;
}
if (($currentMapSegment[0] == '*') && ($currentMapSegment[strlen($currentMapSegment)-1] == '*')) {
$transferVars[$currentMapSegment] = current($uriSegments);
} else {
if ($currentMapSegment == current($uriSegments)) {
$countMatches++;
}
}
next($uriSegments);
next($uriMapSegments);
}
if ($countMatches >= $countSegments) {
$matchFound = true;
$uriTarget = current($uriMap);
$uriWithoutKeyPairs = current($uriMapKeys);
break;
}
next($uriMap);
next($uriMapKeys);
}
for($i = 0; $i < count($transferVars); $i++) {
$uriTranslated = str_replace(key($transferVars), current($transferVars), $uriTarget);
$uriWithoutKeyPairs = str_replace(key($transferVars), current($transferVars), $uriWithoutKeyPairs);
next($transferVars);
}
if ($uriWithoutKeyPairs) {
$keyPairs = explode($uriWithoutKeyPairs, $_SERVER['REQUEST_URI']);
}
if ($transferPairs) {
$uriTranslated .= $keyPairs[1];
}
if (($transferPairs) || ($uriWithoutKeyPairs == $_SERVER['REQUEST_URI'])) {
$_SERVER['REQUEST_URI'] = $uriTranslated;
}
/** END-PATCH */
Credit;
The version of the Zend framework used in this article was 0.1.2
Author Dan Morris

Comments
Normally posts regarding framework suggestions are referred to the framework mailing list (found at the Zend Framework site). This post has been kept as the discussion about finding the best way of representing an URI is valuable regardless of the framework specific implementation.
What are your thoughts on rigid versus flexible models? Are there Search Engine Optimization (SEO) issues with the above suggestion of using name=value pairs within the URI even though not in the query string?
here are some thoughts of mine about a URI structure I would need for most of my projects. Hopefully it does fit in here. Having SEO in mind, I would need something like this:
/controller/action/world/europe/germany/hamburg/
The "controller" and "action" part of the URI should be clear. But the rest of the URI "world/europe/germany/hamburg/" is just one value without any parameter. I maybe even want to get rid of the "action" to shorten the URL.
I guess this should be able to get implemented. I just will not be able to use the key/value pair functionality. So the routing process should be able to handle this as well. Maybe it does it already, until now I have not really start on playing around with it yet.
Best Regards,
Ralf
http://website/controller/action/parent/child/child-of-the-child/etc...
is a very big advantage in some of the situations. But on the other hand, I also want to keep the traditional mapping of key => value which is so handy to have in quick updates. So, I started to play a little in the Action class, and, while trying not to modify the whole process of the framework, which is, by far, the most important reason why I use it, i tried to make a little hack. And I come with a solution very close to those constants you use in sql queries while building your wrapper class: SQL_ASSOC, SQL_INIT, etc. So, why not have a way to determine in WHAT WAY we call our params. We could have a situation when we need a traditional key=>pair controller, and another situation when we need a new, hierarchical controller (basically, in this new controller, the key is also the value, if this sounds cryptic will see later).
The function is _getAllParams() in the Action class. I modified like this:
final protected function _getAllParams($_byType = null)
{
switch ($_byType){
case '1':
// will have our params like this: country/city/street - and the keys are actually the values
// we can check our logic against those informations
foreach ($this->_params as $key=>$value){
$temp_array[$key] = $key;
if(isset($value)) $temp_array[$value] = $value;
}
return $temp_array;
case '2':
// the traditional key => value pair
return $this->_params;
default:
return $this->_params;
}
}
when we call our function from within the constructor will do something like this:
public function goAction()
{
echo "this is the action go, and those are the params: ".print_r($this->_getAllParams('1'));
echo "this is the action go, and those are the params: ".print_r($this->_getAllParams('2'));
}
// call it with an url like:
http://travel.com/go/Germany/Berlin
in the first case you will have something like: Array(Germany=>Germany, Berlin=>Berlin), in the second, will have something like Array(Germany=>Berlin).
I really don't know how error prone is this approach in the actual code, which is only a ten minutes hack, but maybe the general pattern could be analized by those great programmers at Zend and evaluated somehow. For me it would be nice to have a more flexible way in which you can initialize your params, and still not affect your business logic too much.
Maybe the whole function can be declared public and overrided when subclassing it by our own library of controllers. Dunno what deisgn challanges that throws...