2

Is there any examples of using __call in a controller to replace a specific action method.

I have a situation where I need to provide different endpoints to the same controller. A reference to each of these end points is available in the database and so instead of hardcoding a method per endpoint I would like every request that hits the controller to be handled by one method.

The only way I have got this to come close to working is by sticking exit and other similar undesirable functions within the __call method. Surely it's possible to handle the request from here and tell magento not to look for a specific method?

2 Answers 2

5

Alternatively, you could extend the preDispatch method of your controller, check which actions are allowed and which not (I recommend you against calling exit, send a proper HTTP status instead), and redirect the request to a single action in your controller, say foobarAction, by using $this->_forward('foobar').

UPDATE

I tested my solution, have a look at Mage_Core_Controller_Varien_Router_Standard::match. This calls a method hasAction on your controller. If you extend that method in the controller and implement your checks (or simply return true, even though I don't recommend it), then you can do what I suggested. Also, since everything happens in preDispatch, to prevent the loop you get add something like this:

if ($this->getRequest()->getActionName() !== 'foobar') {
    $this->_forward('foobar');
}

Here for your reference is what I did:

class Acme_Module_FoobarController extends Mage_Core_Controller_Front_Action
{
    public function preDispatch()
    {
        parent::preDispatch();
        if ($this->getRequest()->getActionName() !== 'foobar') {
            $this->_forward('foobar');
        }
        return $this;
    }

    public function foobarAction()
    {
        $this->getResponse()->setBody('Hey!');
    }

    public function hasAction($action)
    {
        return true;
    }
}
6
  • hey thanks for the answer. I'm getting a 404 though. I have a function named testAction and i call it in preDispath() with $this->_forward('test');. If I go to a url based on a method that exists in the controller there is no problem so I think the controller is set up correctly, Commented Nov 17, 2015 at 9:34
  • I also get an exception if the method exists in the controller Front controller reached 100 router match iterations Commented Nov 17, 2015 at 9:35
  • Sorry, I haven't tested my answer, but now I did and it does work. I'm going to update my answer.
    – fmrng
    Commented Nov 17, 2015 at 10:21
  • Still getting a 404 on that. My controller is now extending Mage_Core_Controller_Varien_Router_Standard and match() returns true. In my preDispath() function I added your code if ($this->getRequest()->getActionName() !== 'foobar') { $this->_forward('foobar'); }. Fyi foobar() exists as foobarAction() Commented Nov 17, 2015 at 10:47
  • @develophper see my code above, works for me.
    – fmrng
    Commented Nov 17, 2015 at 10:54
4

[Edit]
It seams you cannot use __call in controllers starting from version CE - 1.8 (and EE 1.13)
it was possible up to 1.7 because of the method Mage_Core_Controller_Varien_Action::hasAction.
in 1.7 and lower it looked like this:

public functionhasAction($action)
{
    return is_callable(array($this, $this->getActionMethodName($action)));
}

Starting 1.8 it looks like

public functionhasAction($action)
{
    return method_exists(array($this, $this->getActionMethodName($action)));
}

This method is called in the standard router when handling the request:

Mage_Core_Controller_Varien_Router_Standard::match

...
if (!$controllerInstance->hasAction($action)) {
    continue;
}
...

So starting from CE 1.8 the method somethingAction really has to exist, not just be callable.

[Original Answer - works only for versions before CE-1.8]
yes, you can definitely use __call in a controller.
Check this example
but your __call will be called for each method that does not exist in the controller.
So you have to make sure that your method called ends with Action.
Something like this:

public function __call($function, $args){
    if (substr($function, -6) == 'Action') {
         $this->loadLayout('some_default_layout_handle');
         $this->renderLayout();
    }
    else {//otherwise throw an exeption
        throw new Exception ("Method {$function} cannot be called in ". __CLASS__);
    }
}

or, if you want to also get the function name you can do this:

public function __call($function, $args){
    if (substr($function, -6) == 'Action') {
         $functionName = substr($function, 0, strlen($function) - 6);
         //do something with $functionName
    }
    else {//otherwise throw an exeption
        throw new Exception ("Method {$function} cannot be called in ". __CLASS__);
    }
}

so if you call ROOT/module/controller/something (and somethingAction() does not exist) you end up in the __call method.
in the first case you end up with a default page you set, in the second case $functionName will be equal with something.

4
  • Thanks @marius. I'm trying to get the second option you gave to work by just putting echo 'this is a test'; exit; in the if statement but all I'm getting is a 404. Given the controller takes request to other methods I think that part is set up correctly. Are there any other steps you can think of? Commented Nov 17, 2015 at 9:22
  • what version of Magento are you using? I want to check if this still works on newer versions. It worked for me in 1.7
    – Marius
    Commented Nov 17, 2015 at 9:29
  • enterprise edition 1.14.2.2 Commented Nov 17, 2015 at 9:30
  • @develophper. I checked again. It seams you cannot use __call starting from ce1.8. I've updated the answer. You will have to go a different direction. Like using a custom router for example.
    – Marius
    Commented Nov 17, 2015 at 9:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.