Joey Hipolito
Joey Hipolito

Reputation: 3166

Routing in Php and decorator pattern

I do not know if I am using the term 'routing' correctly, but here is the situation:

I created an .htaccess file to 'process' (dunno if my term is right) the url of my application, like this :

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f

RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]

Now I have this :

http://appname/controller/method/parameter
http://appname/$url[0]/$url[1]/$url[2]

What I did is:

  1. setup a default controller, in case it is not specified in the url
  2. setup a Controller wrapper

I did it like this

$target = new $url[0]()
$controller = new Controller($target)

The problem with that one is that I can't use the methods in the object I passed in the constructor of the Controller:

I resolved it like this :

class Controller {
  protected $target;
  protected $view;

  public function __construct($target, $view) {
    $this->target = $target;
    $this->view = $view;
  }

  public function __call($method, $arguments) {
    if (method_exists($this->target, $method)) {
        return call_user_func_array(array($this->target, $method), $arguments);
    }
  }
}

This is working fine, the problem occurs in the index where I did the routing, here it is

if(isset($url[2])){
    if(method_exists($controller, $url[1])){
         $controller->$url[1]($url[2])
    }
} else {
    if(method_exists($controller, $url[1])){
         $controller->$url[1]()
    }
}

where $controller = new Controller($target)

The problem is that the method doesn't exist, although I can use it directly without checking if method exist, how can I resolve this?

Upvotes: 2

Views: 2327

Answers (3)

Tom B
Tom B

Reputation: 2923

Use is_callable() instead of method_exists() and it will return the correct result.

Upvotes: 1

tereško
tereško

Reputation: 58444

Short answer: you are doing it wrong. Now a bit longer one ...

The main issue here is that you are trying to shoehorn the routing mechanism in the controller. Controller in modern MVC patterns is responsible for taking information from user input, and apply it to model layer and/or current view to change their states.

It should not be responsible for extracting the information from raw input data. That should be done by some form of routing mechanism, which is used before your code actually hits the MVC triad. Preferably in bootstrap/initiation stage of your application.

Basically you do something like:

$uri = isset( $_GET['url'] ) ? $_GET['url'] : '/';
$request = new Request;
$request->setUrl( $url );

$router = new Router;
$router->route( $request );

$class = $request->getParameter('controller');
$method = $request->getParameter('action');

if ( !class_exists( $class ))
{
    $class  = 'ErrorController';
    $method = 'missingController';
}

if ( !is_callable( [$class, $method] ))
{
    $class  = 'ErrorController';
    $method = 'missingAction';
}

$controller = new $class( /* inject some dependencies */ );
$controller->{$method}( $request );

This of course is simplified and somewhat clumsy example. Do not copy-paste in production code.

Upvotes: 1

RobMasters
RobMasters

Reputation: 4148

As you've already discovered, you aren't able to use method_exists when it is being handled by a magic __call method. However, you can add an extra public method to your Controller to get around this problem:

class Controller {
  protected $target;
  protected $view;

  public function __construct($target, $view) {
    $this->target = $target;
    $this->view = $view;
  }

  public function hasMethod($method) {
    return is_callable(array($this->target, $method));
  }

  public function __call($method, $arguments) {
    if (!is_callable(array($this->target, $method))) {
        throw new \BadMethodCallException("Method `$method` is not callable");
    }

    return call_user_func_array(array($this->target, $method), $arguments);
  }
}

So...

if(isset($url[2])){
    if($controller->hasMethod($url[1])){
         $controller->$url[1]($url[2])
    }
} else {
    if($controller->hasMethod($url[1])){
         $controller->$url[1]()
    }
}

EDIT: Changed method_exists to is_callable to ensure only public methods return true.

Upvotes: 2

Related Questions