So I've started studying MVC (real MVC, not framework MVC) a bit more in-depth, and I'm attempting to develop a small framework. I'm working by reading other frameworks such as Symphony and Zend, seeing how they do their job, and attempt to implement it myself.
The place where I got stuck was the URL routing system:
<?php
namespace Application\Common;
class RouteBuilder {
public function create($name, $parameters) {
$route = new Route($name);
$route->resource = array_keys($parameters)[0];
$route->defaults = $parameters["defaults"];
$notation = $parameters["notation"];
$notation = preg_replace("/\[(.*)\]/", "(:?$1)?", $notation);
foreach ($parameters["conditions"] as $param => $condition) {
$notation = \str_replace($param, $condition, $notation);
}
$notation = preg_replace("/:([a-z]+)/i", "(?P<$1>[^/.,;?\n]+)", $notation);
//@TODO: Continue pattern replacement!!
}
}
/* How a single entry looks like
* "main": {
"notation": "/:action",
"defaults": {
"resource" : "Authentication",
},
"conditions": {
":action" : "(login)|(register)"
}
},
*/
I just can't get my head wrapped around it properly. What is the application workflow from here?
The pattern is generated, probably a Route
object to be kept under the Request
object or something, then what? How does it work?
P.S. Looking for a real, well explained answer here. I really want to understand the subject. I would appreciate if someone took the time to write a real elaborate answer.
The router class, from my framework. The code tells the story:
A
Router
class (orDispatcher
as some would call it) examines the URL of an HTTP request and attempts to match its constituent components to a concreteController
and a method (a.k.a action or command) defined in that controller. It also passes arguments to the desiredController
's method, if any are present in the URL.Standard URL: Query String Format
In the Apache HTTP Server, without using mod_rewrite, the URL in an HTTP request would probably be in query string format:
Rewritten URL: Desired Format
A URL, after rewriting by a web server, tends to look like this:
Without URL rewriting, you will need a class that resolves the
route
param in the query sting from the standard URL. That is the first thing aRouting
class might do. In this case, it resolves theparam
to:If all goes well, something similar to this will occur:
A
Router
class instantiates the requested, concrete childController
, calls the requested method from the controller instance, and passes the controller method its arguments (if any).Now, the class that you are showing resolves a requested 'route' to the right controller/action. So, for example the URL below:
... is an English URL. Hence the word news in the query string. Suppose you wanted the URL to work in Dutch.
That would mean the concrete
Controller
would be callednieuwsController.php
, but it does not exist. That is where your example comes in to play: theRouteBuilder
class.1) Your
Router
class should first check to see if there is a concreteController
that it can instantiate (using the name as found in the URL, plus the word "Controller"). If the controller is found, test for the presence of the requested method (action).2) If the
Router
cannot find and load the necessary PHP at runtime (using an autoloader is advised) to instantiate a concreteController
child, it should then check an array (typically found in another class nameRoute
) to see if the requested URL matches, using regular expressions, any of the elements contained within. A basic skeleton of aRoute
class follows.Note:
.*?
= Zero, or more, of any character, non-capturing.Why use a regular expression? One is not likely to get reliable matching accomplished for data after the second forward slash in the URL.
/controller/method/param1/param2/...
, where param[x] could be anything!Warning: It is good practice change the default regular expression pattern delimiter ('/') when targeting data contains the pattern delimiter (in this case, forward slashes '/'. Almost any non-valid URL character would be a great choice.
A method of the
Router
class will iterate over theRoute::routes
array to see if there is a regular expression match between the target URL and thestring
value associated with a 2nd levelurl
index. If a match is found, theRouter
then knows which concreteController
to instantiate and the subsequent method to call. Arguments will be passed to the method as necessary.Always be wary of edge cases, such as URLs representing the following.