We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Forward and Stop Action From Within a Function

In my base controller I have:

public function fatalError($error)
{
    $this->flash->error($error);
    $this->dispatcher->forward(array(
        'controller' => $this->di['dispatcher']->getControllerName(),
        'action' => 'error'
    ));
    return false;
}

But of course the "return false" doesn't stop execution of the action the function is called from.

I want to be able to call this from anywhere and with a single line show a controller-specific error and NOT continue the code from where it is executed (whether in an action or another function).

Is this possible?

First off, you should centralize your error reporting. From the looks of it, you'll need an errorAction() function in each controller. It might be easier to just have an errorController().

Have you tried this? In my experience $this->dispatcher->forward() immediately starts executing the target controller & action. I guess you could call exit() from the function, or from the action after calling fatalError()



7.7k
edited Mar '14

The errorAction() is in the base controller, so its centralized and easy to manage (though each controller has its own error view, which is OK since I want the navigation, etc to be specific to the controller the error occurred in).

Calling "exit;" in the fatalError function just outputs the error on a blank page, without rendering a view.

Calling "return false;" in the original action, right after calling fatalError() works, but I'm trying to avoid that because I'm trying to get the same answer for a more complicated case. In my models I have methods like getContactById(), and when they don't find a match I want to output "Contact not found" from that method (with the option of suppressing the error and just returning false).

Finding a solution to that will save 4 lines of code each time I use the get*ByID() methods, and be more DRY:

$contact = Contacts::getContactById($id);  // Only this line will be necessary if I can find a solution to this
if(!$contact){
    $this->fatalError('Contact not found');
    return false;
}   


98.9k

You can call fatalError this way:

return $this->fatalError("my error");


7.7k

Thanks, that works perfect for the first use case.

Any ideas on the second use case in my previous comment?



98.9k

You can use an exception:

try {

    $contact = Contacts::getContactById(1);
    $contact = Contacts::getContactById(2);
    $contact = Contacts::getContactById(3);
    $contact = Contacts::getContactById(4);
    $contact = Contacts::getContactById(5);

} catch(NotFoundException $e) {
    return $this->fatalError($e->getMessage());
}
class Contacts
{
    public static function getContactById($id)
    {
        $record = self::findFirstById($id);
        if (!$record) {
            throw new NotFoundException("contact not found");
        }
    }
}


7.7k
edited Mar '14

Thanks for the reply, but the try...catch uses the same amount of code as the if method above.

I was hoping to have this in my Contacts model:

public function getContactById($id)
{       
    $contact = Contacts::findFirstById($id);
    if (!$contact) {
        $this->flash->error("Contact not found");
        $this->dispatcher->forward(array(
            'controller' => $this->di['dispatcher']->getControllerName(),
            'action' => 'error'
        ));
        return false;
    }       
    return $contact;
}

So in my controller I would only need:

$contacts = Contacts::getContactById($id);

I use the get*ByID() calls a lot, so I was hoping to keep the error handling centralized and condensed (inside the functions themselves), but it looks like it doesn't work that way. I was hoping for the return equivelant of break 2, but it doesn't look like it exists (in Phalcon or PHP).

Thanks!



98.9k
edited Mar '14

'return' and 'try/catch/exit?' are the only control flow statements PHP provide us, I don't see any other option. There's no way PHP can insert an implicit 'return' if the value returned by a method is 'false' without developer intervention.



125.7k
Accepted
answer

I think the way you're approaching things, ~metzen2k, is not ideal. In a proper MVC separation, the model shouldn't know about controllers or actions, or route forwarding. Your getContactById() method should simply return FALSE, as you're doing. It's up to the controller to render the view based on what the model returns.

In my opinion, having those 4 lines in your controller is superior to having the model affect routing - because the logic stays in the area it belongs.



7.7k

Thanks, both of you, for your replies. I'll try to come up with a better solution without breaking proper MVC separation.