Reputation: 3166
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:
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
Reputation: 2923
Use is_callable()
instead of method_exists()
and it will return the correct result.
Upvotes: 1
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
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