Dany
Dany

Reputation: 755

Get parameter passed to __call instance method( new Reflecction() ?)

General information

What i would like to achieve

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;
    }

Upvotes: 1

Views: 242

Answers (1)

Jasper N. Brouwer
Jasper N. Brouwer

Reputation: 21817

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.

Upvotes: 1

Related Questions