Reputation: 981
A continuation from previous question: Custom lithium routing scenario
Note: This is specific to the Lithium Framework
The Problem
I've inherited a URL scheme that doesn't really have any convention to differentiate between pages, items and categories. So, I've got a very generic router that passes to a catch-all controller.
This catch-all controller (PagesController) uses the url as key to retrieve Page Type from a database. The PagesController then runs methods and chooses templates based on a Page Type. I store this information in Memcached indefinitely, so lookups are quite fast.
However, as more page types come into play, I can see this controller becoming too bloated and inflexible. Ideally, I would like to break the different page types out into their own controllers.
The Solution?
Would it be possible to have a routing scenario that checks the database to determine the correct controller?
My first thought is to subclass lithium\net\http\Router
and use custom logic in Router::connect()
and Router::_parseController()
.
It would be nice if could query the database in bootstrap\routes.php and create a new lithium\net\http\Route
object based on the results. Then, simply pass this to Router::connect()
. This seems like an awful hack.
Either way, Router::connect()
in it's design isn't meant to be that dynamic.
Upvotes: 2
Views: 342
Reputation: 5770
It's hard to be very specific, without seeing more examples of different page types and other example URL (I did look at the previous question, but I get the sense that that doesn't give the whole picture), but as with the previous question, it sounds like the answer will once again be custom routing with a handler.
Router::connect('/{:pageType}/{:pageKey}', ['pageKey' => null], function($req) {
$types = ['list', 'of', 'page', 'types'];
$controller = 'pages';
$action = 'view';
if (in_array($req->pageType, $types)) {
// It's a proper type, do a database lookup and set
// `$controller` accordingly
// Here I'm assuming category vs. item view for the action:
$action = $req->pageKey ? 'view' : 'index';
} elseif (!$req->pageKey) {
// It's a `/custom` URL, figure out what to do with it
}
// This lets you return custom, arbitrary parameters that Lithium
// will use for dispatch
return compact('controller', 'action') + $req->params;
});
As you can see, with handlers, you can do pretty much whatever you want. Parse the parameters, run your database calls, and pass back a completely arbitrary set of routing parameters for Lithium to dispatch.
The only other thing you might want to add to the above is a separate class that can manage the pairings between page types/custom pages, and routing parameters, if your rules start getting complicated.
Upvotes: 3
Reputation: 57729
You can build a router that works like a stack of filters.
You start at the top of the list and ask each filter if it matches the given URL. If it doesn't match, it will continue to the next filter etc. all the way down the stack.
Filters at the top of the stack have a higher priority. The priority can be because of matching rules or simply performance. ie. a database looking is slow, so add a pre-filter that tries to reject non-matching URLs as quickly as possible.
You can build a very generic Router class where you can add Filters. The Router is just a container for the (stack of) Filters with a method to start a run with a given set of input data.
class Router {
public function addFilter(Filter $filter) {}
public function run($input) {} // returns a route
}
interface Filter {
public function findRoute($input) {} // returns false or a route
}
This allows for a very modular structure. You can dynamically add filters as your modules are loaded.
An example filter could be:
class NewsFilter implements Filter {
public function findRoute($input) {
if (preg_match(":^/news/item/([0-9]+)$:", $input["url"], $matches)) {
$item = $this->news->findItem($matches[1]);
if (false === $item) {
return false;
} else {
return new NewsRoute($item);
}
}
return false;
}
}
$router = new Router();
$newsfilter = new NewsFilter();
$router->addFilter($newsfilter);
...
$router->run($input);
Upvotes: -1