PHP call_user_func alternative - php

I am looking for a pretty equivalent in PHP to its function call_user_func.
The problem I am encountering with this function is that it does not go into an "object mode". By this, I mean I cannot use $this and other stuff in the class, so pretty much in two words: not oop.
I need this basically as I am dealing with the requested url, parsing it, and seeing if everything is ok and so on, and then doing the following lines:
call_user_func(array(ucfirst( $controller . "Controller" ), '_initAction'), $param);
call_user_func(array(ucfirst( $controller . "Controller" ), $action . 'Action'), $param);
as I want to dynamically call the "Controller" and its actions. But I cannot use $this in the $action methods as it is not OOP.
Here the message I get:
Fatal error: Using $this when not in object context in E:\htdocs\wit\application\controller\InformationController.php on line 6
So I hope that somebody could help me.
Could you also tell me if I am approaching this problem in a wrong way?
PS: Please don't recommend me any MVC frameworks that take care of this stuff. I like Zend, but sometimes, its just too heavy :(( I need a lightweight setup for this.

You can call the object method by passing object in first element of the callback:
$class = ucfirst($controller . "Controller");
$controller = new $class();
call_user_func(array($controller, $action . 'Action'), $param);
Actually, you can even use
$controller = new $class();
$controller->{$action . 'Action'}();

Take a look at how Glue calls user defined functions. It may point you in the right direction, since users define classes to handle routes.
<?php
require_once('glue.php');
$urls = array(
'/' => 'index'
);
class index {
function GET() {
echo "Hello, World!";
}
}
glue::stick($urls);
?>

If I completely miss the point of your question, I'm sorry. It sounds to me like you're trying to call an object method using call_user_func. You can do that, you just pass an array with the object as the first index, and the string name of the method as the second index.
For example, say you have your controller "IndexController" with "index" as your action/method.
class IndexController {
public function index() {
// $this available here
}
}
$controller = new IndexController();
// if you know your parameters all ahead of time
call_user_func(array($controller, 'index'), $param1, $param2);
// if you want to pass an unknown number of params
call_user_func_array(array($controller, 'index'), $params);

$view = View::getInstance( $config['view'] );
$err = new ErrorController(array(), $view);
//load the class and its method
//pass the params to it
$class = ucfirst( $controller . "Controller" );
if ( class_exists($class) )
{
$con = new $class($param, $view);
$act = $action . 'Action';
if ( method_exists($con, $act) )
{
$con->$act();
}
else
{
$view->setController("error");
$view->setAction('index');
$err->indexAction();
}
}
else
{
$view->setController("error");
$view->setAction('index');
$err->indexAction();
}
So this is my solution how i solved my problem. it is based on #AlexAtNet solution and parts of glue that was recommend to me by #Sean

Also you can try this way:
$controller = new $class();
$method = $action . 'Action';
[$controller, $method]()

Related

How are controller actions and views rendered through a single entry script in php frameworks?

For example in a Yii Framework application the url is in this format
www.example.com/index.php?r=foo/bar
Which renders the script inside the actionBar() method of class FooController. Further, this class (or its parent class) implements a render() method which can render a view file.
All the url's are handled through the entry script index.php.
I would like to write my own class which can handle url's through this way.
Can someone give me a very basic 'hello world' example of writing such a script ?
I'll give it a shot:
// index.php
$r = $_REQUEST['r']; // 'foo/bar'
$rParts = explode('/',$r);
$foo = $rParts[0];
$bar = $rParts[1];
$controller = new $foo; // foo
echo $controller->$bar();
Here is what I did for a friend recently, when teaching him how frameworks works. This is a basic example, but it demonstrates how a container works, how to handle the router, giving the controller a request and a response and handling redirects and the like.
<?php
require 'autoload.php';
$container = [];
$container['controller.elephant'] = function() {
return new Controller\Elephant();
};
$routes = [];
$routes['/babar'] = 'controller.elephant:babar';
$routes['/celeste'] = 'controller.elephant:celeste';
$request = new Request();
if (!isset($routes[$request->path()])) {
http_response_code(404);
exit;
}
$route = $routes[$request->path()];
list($class, $method) = explode(':', $route);
$controller = $container[$class]();
$response = $controller->{$method}($request, new Response());
if ($response->isRedirect()) {
http_response_code($response->status());
header('Location: '.$response->destination());
} else {
echo $response->content();
}
exit;
I won't include anything more than that (albeit there is other files) because it would bloat the answer needlessly (I can send them to you by other means if you want to).
I highly advise you to look at the Slim Framework code, as it is a micro framework that basically do just that.
Well in the Symfony documentation you have this page: http://symfony.com/doc/current/components/http_kernel/introduction.html
where it explains how is the life cycle of a request, it's just a flow diagram.
But it will give you a really good idea on how you should build yours
If you are more interested in how based on a url you get the controller you should read the RoutingComponent in symfony
http://symfony.com/doc/current/components/routing/introduction.html
http://symfony.com/doc/current/components/routing/hostname_pattern.html
But if you want to write your own class, you should use something like regex expression groups where you can detect the url parts separated by i.e: '/' then you somehow map the url to the controller i.e associative array 'Hash'
someurl.com/someController/someAction
$mappings = [
...
'someController' => 'The\Controller\Class'
]
$controller = new $mappings[$urlControllerPart]();
$response = $controller->{$urlActionPart}($request);
return $response;

OpenCart Call Different Controller

I have a custom module, and now want to call the add() function from checkout/cart. How do I call the controller and function?
I have tried $this->load->controller('checkout/cart'); but this returns a fatal exception.
I am using OpenCart v 1.5.6.4
In OpenCart 1.5.*, getChild is used to load other controllers. Specifically, it is running a route to the desired controller and function. For example, common/home would load the home controller from the common group/folder. By adding a third option we specify a function. In this case, 'add' - checkout/cart/add.
class ControllerModuleModule1 extends Controller {
protected function index() {
ob_start();
$this->getChild('checkout/cart/add');
$this->response->output();
$response = ob_get_clean();
}
}
Most controllers don't return or echo anything, but specify what to output in the $this->response object. To get what is being rendered you need to call $this->response->output();. In the above code $response is the json string that checkout/cart/add echos.
To solve the same issue, I use $this->load->controller("checkout/cart/add").
If I use getChild, this exception get thrown : "Call to undefined method Loader::getChild()".
What is the difference between the 2 methods? Is getChild better?
The problem with getChild() is it only works if the controller calls $this->response->setOutput() or echo at the end - producing actual output. If on the other hand, you want to call a controller method that returns a variable response, it isn't going to work. There is also no way to pass more than one argument since getChild() accepts only one argument to pass, $args.
My solution was to add this bit in 1.5.6.4 to system/engine/loader.php which allows you to load a controller and call it's methods in the same way as you would a model:
public function controller($controller) {
$file = DIR_APPLICATION . 'controller/' . $controller . '.php';
$class = 'controller' . preg_replace('/[^a-zA-Z0-9]/', '', $controller);
if (file_exists($file)) {
include_once($file);
$this->registry->set('controller_' . str_replace('/', '_', $controller), new $class($this->registry));
} else {
trigger_error('Error: Could not load controller ' . $controller . '!');
exit();
}
}
Now you can do this:
$this->load->controller('catalog/example');
$result = $this->controller_catalog_example->myMethod($var1, $var2, $var3);

Yii Overwrite createUrl but keep parameters

I am extending and overwriting the createUrl method to make pretty URLs.
Here is a snippet:
public function createUrl($manager, $route, $params, $ampersand) {
if($route == 'widget/view'){
$widget = Widget::model()->findByPk($params['id']);
return 'widget/' . $params['id'] . '/' . SEOUrlRule::slug($widget->title);
}
}
Lots of things don't matter much here. And I removed irrelevant parts.
It works fine. However, sometimes the method may be called with additional parameters such as an anchor tag, or GET parameters to put at the end of the URL.
Using my method, these parameters will be lost. To pass them forward in the new URL, do I have to re-do whatever the original createUrl method did manually? Or is there a nice object oriented way to pass the job along to a competent method?
You've made every argument required - is that intentional?
You can have Yii construct the parameter string by using the createUrl from CApplication, e.g.:
public function createUrl($manager, $route, $params, $ampersand) {
if($route == 'widget/view'){
$widget = Widget::model()->findByPk($params['id']);
$url = 'widget/' . $params['id'] . '/' . SEOUrlRule::slug($widget->title);
return Yii::app()->createUrl(trim($url,'/'),$params,$ampersand);
}
}

How to access the params given to the controllers by the routing system within a hook in CodeIgniter?

I want to log the requests that my app receive using a post_controller_constructor hook.
The hook's code below illustrate what I need:
public function log() {
$controller = $this->router->fetch_class();
$action = $this->router->fetch_method();
$post_params = $this->input->post();
$get_params = $this->input->get();
$url_params = ???;
...
$this->log_model->log($id_user, $controller, $action, time(), $post_params, $get_params, $url_params);
}
function __get($key){
$CI =& get_instance();
return $CI->$key;
}
For what I saw, the solution has to be related with 'URI' Class and the segments extracted from there.
But I cannot rely on this aproach because I have that some controllers are deeper than others like this examples:
/folder/set_controllers/controller_a/action/(:any)
/folder/set_controllers/controller_a/action/7 the value for $url_params is array('7').
/folder/controller_b/action/(:any)/(:any)
/folder/controller_b/action/first_param/second_param/7 the value for $url_params is array('first_param', 'second_param').
/controller_c/action/(:any)
/controller_c/action/hello the value for $url_params is array('hello').
And I need to know what values $url_params have.
Thanks in advance.
Maybe the answer of this question can help you How to get Controller, Action, URL informations with CodeIgniter it looks you are looking for the same, more or less.

how to build a good router for php mvc

I'm experimenting with php mvc and I'm stucked with the following issue. My request and router classes are really simple and I would like to extend theme to can handle controller calls from sub folders and to controller classes functions should be able to pick up url variables send it threw get and post.
my router looks as it follows
class Router{
public static function route(Request $request){
$controller = $request->getController().'Controller';
$method = $request->getMethod();
$args = $request->getArgs();
$controllerFile = __SITE_PATH.'/controllers/'.$controller.'.php';
if(is_readable($controllerFile)){
require_once $controllerFile;
$controller = new $controller;
if(!empty($args)){
call_user_func_array(array($controller,$method),$args);
}else{
call_user_func(array($controller,$method));
}
return;
}
throw new Exception('404 - '.$request->getController().'--Controller not found');
}
}
and Request class
private $_controller;
private $_method;
private $_args;
public function __construct(){
$parts = explode('/',$_SERVER['REQUEST_URI']);
$this->_controller = ($c = array_shift($parts))? $c: 'index';
$this->_method = ($c = array_shift($parts))? $c: 'index';
$this->_args = (isset($parts[0])) ? $parts : array();
}
public function getController(){
return $this->_controller;
}
public function getMethod(){
return $this->_method;
}
public function getArgs(){
return $this->_args;
}
}
The problem is:when I try to send threw ajax, variables to a controller method this are not recognized because of its url structure.
For example
index/ajax?mod_title=shop+marks&domain=example
is accepted just if it look
index/ajax/shop+mark/example
Your code contains what is known as an LFI vulnerability and is dangerous in its current state.
You should whitelist your what can be used as your $controller, as otherwise an attacker could try to specify something using NUL bytes and possibly going up a directory to include files that SHOULD NOT be ever included, such as /etc/passwd, a config file, whatever.
Your router is not safe for use; beware!
edit: example on whitelisting
$safe = array(
'ajax',
'somecontroller',
'foo',
'bar',
);
if(!in_array($this->_controller, $safe))
{
throw new Exception(); // replace me with your own error 404 stuff
}
Since your Request class uses a URI segments approach for identifying controller, action and arguments, global variables such as $_GET or $_REQUEST are not taken into account from within your Request.
What you need to do is to make some additions to your Request code. Specifically:
Remove the line:
$this->_args = (isset($parts[0])) ? $parts : array();
And add the following:
$all_parts = (isset($parts[0])) ? $parts : array();
$all_parts['get'] = $_GET;
$this->_args = $all_parts;
This way, $_GET (ie variables passed via the url) variables will be available in the actions called, as they will be in $args (they will be available as $args['get'] actually, which is the array that holds the $_GET vars, so you will be able to have access to domain=example by using $args['get']['domain']).
Ofcourse, you can add one more method in your Request class (e.g. query) that might look like that:
public function query($var = null)
{
if ($var === null)
{
return $_GET;
}
if ( ! isset($_GET[$var]) )
{
return FALSE;
}
return $_GET[$var];
}
This way, you can get a single variable from the url (e.g. $request->query('domain')) or the whole $_GET array ($request->query()).
That's because php will put "?mod_title=..." in the $_GET array automatically. Your getArgs() function should check for $_GET, $_POST or $_REQUEST.
If you're trying for a minimal MVC approach, have a look at rasmus' example: http://toys.lerdorf.com/archives/38-The-no-framework-PHP-MVC-framework.html
If your use case is going to get more complex, have a look at how Zend (http://framework.zend.com/manual/en/zend.controller.html) or Symfony (https://github.com/symfony/symfony/tree/master/src/Symfony/Component/Routing) do their stuff.
Choose any popular MVC to see how they implement it under the hood. In addition, spl_autoload_register and namespace are your friends.

Categories