Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

Forward action, send response and stop dispatching

Hi everyone. I saw a lot of topics with such titles but none of them solved my problem.

Case. I have a controller with index action:

class TrackController extends ControllerBase {
    public function indexAction(){
        $this->eventsManager->fire('track:beforeStart', $this);

        // Some other logic
    }
}

Event "track" binded in dispatcher:

// ...
$evManager->attach('track', new Track);
// ...

And the Track listener:

class Track extends Generic
{
    /**
     * @param Event $event
     */
    public function beforeStart(Event $event)
    {
        if($someCondition == true)
        {
            $this->dispatcher->forward(
                [
                    'controller' => 'forwardController',
                    'action'     => 'forwardAction'
                ]
            );
        }
    }
}

I want to dispatch if($someCondition == true) forward, send response and then stop dispatching. Now it doesn't work, after forwardAction it returns to TrackController::indexAction and continue work after fire.

I've tried to remove all events manually via $this->eventsManager->detachAll(), tried to run dispatch and send response manually but I can't make it work as I wish. The only way is to call exit, but this is a bad way and it's really hard to test.

Please help with advice. Is it possible to end dispatching within event or may be another (better) solution exists. I know I can do it this way:

class TrackController extends ControllerBase
{
    public function beforeExecuteRoute()
    {
        if($someCondition == true)
        {
            $this->dispatcher->forward(
                [
                    'controller' => 'forwardController',
                    'action'     => 'forwardAction'
                ]
            );
        } else {
            // run another method
        }
    }
}

But don't want to copypaste it in some other controllers. I thought the event-listener model is the best option.

Thanks in advance.

return  $this->dispatcher->forward(
                [
                    'controller' => 'forwardController',
                    'action'     => 'forwardAction'
                ]
            );

?

edited Jul '17

First of all forward is a void function. Anyway this trick will not prevent indexAction to continue execute code after ->fire.

edited Jul '17

It doesn't matter it's void function, it will prevent indexAction to continue execute because you do return. Return in any method/function will stop execution. No matter if something is returned from executed scope or not. You can also just do:

class Track extends Generic
{
    /**
     * @param Event $event
     */
    public function beforeStart(Event $event)
    {
        if($someCondition == true)
        {
            $this->dispatcher->forward(
                [
                    'controller' => 'forwardController',
                    'action'     => 'forwardAction'
                ]
            );
            return false;
        }
    }
}

Return false will stop execution as well.

Well also just use builtin beforeExecuteRoute event.

Return in indexAction will be just level-syntax stoping execution - like return in any method will stop further METHOD execution(but it will fire other events etc. Return false in beforeExecuteRoute will stop dispatch execution by design(it will go just to next action like your forwarded). https://github.com/phalcon/cphalcon/blob/master/phalcon/dispatcher.zep#L571

edited Jul '17

It doesn't matter it's void function, it will prevent indexAction to continue execute because you do return.

No, its not, because we call return in the listener class, not controller's action. And I'm pretty sure that both of us know fundamental PHP rules, this is not topic related thing.

Return false will stop execution as well.

In your example return false only affects listener class, not TrackController::indexAction. I wrote in the end of my first message I know I can do it this way about beforeExecuteRoute, but this is not what I'm looking for. I don't want to check some if..else... statements in controller itself, I want to move this logic to the listener.

Returning false from the beforeStart event will not prevent indexAction to stop, this is 100% information. You can use Xdebug to check this out very easy.

P.S. PHP 7.1.7, Phalcon 3.2.0



3.0k

Hi

But don't want to copypaste it in some other controllers. I thought the event-listener model is the best option.

why do not do it in ControllerBase

class TrackController extends ControllerBase
{
    public function beforeExecuteRoute()
    {
        if($someCondition == true)
        {
            $this->dispatcher->forward(
                [
                    'controller' => 'forwardController',
                    'action'     => 'forwardAction'
                ]
            );
        } else {
            // run another method
        }
    }
}

and all other controllers extends this class Your code is present only on time

also you can use Traits

why do not do it in ControllerBase

Because I don't want to implement this feature in all classes. Trait is much better solution and may be I'll use it if no other ideas come, thanks.

What is the main benefit for events: I should write just one line of code with fire, set independent listener for each controller and add more functionality in future just in listener class. I just need to keep in mind "okay, I want to do this before action start and I already have an event, just put additional code to the track:beforeStart event and that's it".

Thanks both of you, guys. If it's not possible to use events for this task I'll use traits or built-in events.

P.S. - This is really uncool... I can forward to another action, send response and the only one way to do this immediately is exit code.



102.8k
Accepted
answer
edited Jul '17
  1. To stop method execution you need to make it return something(like anything), examples can be return $this->dispatcher->forward() or just return;
  2. To stop dispatch execution you need to return false in the event which can stop execution, like return false in beforeExecuteRoute

No - this is how php and other languages works. To stop some function/method execution you need to return something, or call exit, this is a way too. It has nothing to do with framework.

Forward doesn't stop execution of action - nothing fancy here.

You can use trait, just your custom event beforeStart doesn't have ability to stop dispatch exectuion. You would need to extend Phalcon dispatcher and write your own custom logic.

Also not sure - what's wrong with beforeExecuteRoute? It seems like you need exactly this event.

Also not sure - what's wrong with beforeExecuteRoute? It seems like you need exactly this event.

There is nothing wrong with beforeExecuteRoute event. I just wanted to move it from controller to separate file i.e. trackController:beforeExecuteRoute, but as I understand beforeExecuteRoute is a part of dispatch loop and I must declare it in each controller.

Thank you for the reply. No questions now.

No you dont have to to declare it in each controller. You can move it to trait, you can move it to seperate listener and attach it using $eventsManager->attach().



3.0k
edited Jul '17

A home, i see how i separate it

in app/common/controllers/ControllerBase

    public function beforeExecuteRoute()
    {
        // some code for all controllers
    }

in app/modules/moduleA/controllers/ControllerModule

class ControllerModule extends ControllerBase
{
    public function beforeExecuteRoute()
    {
        parent::beforeExecuteRoute();

        // Conditions and access controls here
        if($someCondition == true)
        {
            $this->dispatcher->forward(
                [
                    'controller' => 'forwardController',
                    'action'     => 'forwardAction'
                ]
            );
        } else {
            // run another method
        }
    }
}

Controllers in modules :

class MyController extends ControllerModule
{
        // do ControllerModule beforeExecuteRoute (and ControllerBase )
}

if i dont want execute module code :

class MyOtherController extends ControllerBase
{
        // do only ontrollerBase  beforeExecuteRoute
}