Question

General information

  • I have a class that has an instance.
  • This instance has a __call magicc method defined
  • The magic method __call() defines the request method the instance will respond to.
  • The php version used is 5.5.

What i would like to achieve

  • The magic method __call() defines the request method the instance will respond to.
  • $request[0] is the regex to respond to and $request[1] the closure.
  • I want to define a prefix instance method.
  • First parameter of the prefix method is the stringto prepend to $request[0].
  • The second argument is an array of instance methods that use __call().

The magic __call() method if the instance :

public function __call($method, $request) {

    $this->_links[strtoupper($method)] [$this->_baseUrl . $request[0]] = $request[1];

    return $this;
}

The prefix method as it is now ;

public function prefix($prefix, $callbackmethods = array()) {

        foreach ($callbackmethods as $closure) {
                $reflection = new ReflectionFunction($closure);
        }

        return $this;
    }

Example of code structure i am trying to achieve.

# Normal method call to __call
$link->get('/', function(){

});


# Prefic the regex of multiple method calls to __call
$link->prefix('/admin', [
    $link->get('user/(\d+)', function(){

    }),
    $link->post('user/(\d+)', function(){

    }),
    ]);

What i have tried

What i want to achieve is to define a public function prefix($prefix, $callbackmethods = array()). This way the user can easlily prepend a string to the regex to be matched.

Is the reflection class the right way? Am i looking for the wrong thing? Can this be achieved with Php. Are there other options? Can anyone show me how to use this correctly, if the reflection class is the right way?

This is a learning project for me so please don't advice to use an existing routing class.

Update

I have resolved the problem differently with the following code.After that code the filtering is done inside the __call() method. See the answer provided below for a more elegant solution.

  /**
*
* @param type $prefix
* @param type $requestMethod
* @return \Link
*/
    public function prefix($prefix, $requestMethod = false) {

        $this->_applyPrefix = true;

        if ($requestMethod != false) {

            $this->_prefixMethod = strtoupper($requestMethod);
        }

        $this->_prefix = $prefix;

        return $this;
    }

    /**
*
* @return \Link
*/
    public function end() {

        $this->_applyPrefix = false;

        $this->_prefixMethod = false;

        $this->_prefix = false;

        return $this;
    }
Was it helpful?

Solution

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 new Route 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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top