Tim Fountain
Tim Fountain

Reputation: 33148

Interrupting application flow from a non-MVC event

I'm working on an application which uses a REST API backend. This API has a login step which creates a token used for all subsequent API requests. I store this token in the auth storage, and I have an event hook that checks if the user is logged in, and if not, renders the login page:

$eventManager->attach(MvcEvent::EVENT_ROUTE, function($e) use ($view, $auth) {
    $match = $e->getRouteMatch();

    // No route match, this is a 404
    if (!$match instanceof RouteMatch) {
        return;
    }

    // Route is whitelisted
    $matchedRoute = $match->getMatchedRouteName();
    if (in_array($matchedRoute, array('login'))) {
        return;
    }

    // if they're logged in, all is good
    if ($auth->hasIdentity()) {
        return true;
    }

    [render login form and return response object]
}, -100);

This works great.

The API also sometimes expires the login tokens in a way that I can't easily predict, which means all API calls will return a 'Session expired' type error. I've written an event trigger after the API calls that I can hook into. I want to check for these 'session expired' responses and somehow render the login page in the same way I do above:

$events->attach('Api', 'call', function ($e) {
    $api = $e->getTarget();
    $params = $e->getParams();

    $result = $params['apiResult'];

    if ([result is a session expired response]) {
        // what can I do here?
    }

}, 999);

but since this isn't an MVC event, even if I could access the response object here, returning it wouldn't do anything. What would be the best way to interrupt the application flow in a non-MVC event?

Upvotes: 1

Views: 110

Answers (1)

Stefano Torresi
Stefano Torresi

Reputation: 686

I'm not sure but I'm assuming that your API events do occur in a dedicated EventManager instance (so your API may be an implementation of EventManagerAwareInterface) and not in the MVC one (which is the one you grab from the Zend\Mvc\Application instance).

If that's the case, you could inject both the main EventManager and the MvcEvent inside your API, and then short circuit the MVC cycle from the call listener.

I.e. assume your dependencies are in $mvcEvent and $mvcEventManager properties with getters, this is how you would listen for the call event:

$events->attach('call', function($e) {
    $api = $e->getTarget();
    $params = $e->getParams();

    $result = $params['apiResult'];

    if ([result is a session expired response]) {
        $mvcEvent = $api->getMvcEvent();
        $mvcEvent->setError('api error');
        $mvcEvent->setParam('exception', new \Exception('Session expired'));
        $api->getMvcEventManager()->trigger('dispatch.error', $mvcEvent);
    }

}, 999);

There are better ways to do it for sure, choosing the best will depend on the architecture of your API class.

You could use Zend\EventManager\ResponseCollection returned by your trigger, rather than using the MVC event inside the listener; that would enable your API event cycle to continue even if some error occurs. That's actually how Zend\Mvc\Application uses its own event manager in the run() method, so you can peek at that for an example.

Upvotes: 1

Related Questions