The problem with this setup is that the __call()
method itself will add a route to the list of routes. So wrapping those calls within a call to prefix()
doesn't change that, the __call()
will still be executed first.
So it has nothing to do with how anonymous functions / closures work, the problem is the order of execution.
I suggest you choose a slightly different approach:
- Create a class that will represent a single route (
Route
). You can manage it more easily this way. - Have the
__call()
method create and return a Route object, but don't have it add the route to the list. - Have the
prefix()
method create newRoute
objects based on the routes you pass to it. - Implement a separate
add()
that will add the route to the list.
This could look something like this:
class Router
{
/**
* @type array
*/
private $routes = array();
/**
* @param Route|Route[] $routes
*/
public function add($routes)
{
foreach ((array)$routes as $route) {
$this->routes[] = $route;
}
}
/**
* @param string $prefix
* @param Route[] $routes
* @return Route[]
*/
public function prefix($prefix, array $routes)
{
$prefixedRoutes = array();
foreach ($routes as $route) {
$prefixedRoutes[] = new Route(
$route->getMethod(),
$prefix . $route->getUri(),
$route->getController()
);
}
return $prefixedRoutes;
}
/**
* @param string $name
* @param array $arguments
* @return Route
*/
public function __call($name, array $arguments)
{
$method = $name;
$uri = $arguments[0];
$controller = $arguments[1];
return new Route($method, $uri, $controller);
}
}
class Route
{
/**
* $var string
*/
private $method;
/**
* @var string
*/
private $uri;
/**
* @var callable
*/
private $controller;
/**
* @param string $method
* @param string $uri
* @param callable $controller
*/
public function __construct($method, $uri, $controller)
{
$this->method = $method;
$this->uri = $uri;
$this->controller = $controller;
}
/**
* @param string $prefix
*/
public function prefix($prefix)
{
$this->uri = $prefix . $this->uri;
}
/**
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* @return string
*/
public function getUri()
{
return $this->uri;
}
/**
* @return callable
*/
public function getController()
{
return $this->controller;
}
}
You can use it like this:
$router->add(
$router->get('/', function() {
// ...
});
);
$router->add(
$router->prefix('/blog', array(
$router->get('/(\d+)', function() {
// ...
}),
$router->post('/(\d+)', function() {
// ...
})
))
);
Now this is a very, very simple setup. And it could be solved a lot more elegantly.
I strongly suggest you take a look at Silex (which is a micro-framework based on Symfony2 components). It has a router that looks a lot like what you're trying to achieve. You could take inspiration from that code.