Q:

Zend Framework 3 error handling

I'm trying to handle error from my CheckAccess plugin within ZF3. 
Plugin is attached like this in module's onBootstrap method
class Module {
    public function onBootstrap()
    {
        ...
        $em->attach(MvcEvent::EVENT_DISPATCH, new Plugin\CheckAccess($sm), 2);
        ...
    }
}

Here what i'm doing exactly inside the plugin's __invoke() when i need to redirect not logged-in user from some page to login page:
if ($user->isGuest()) {
        $rh = new RouteHelper($e->getRouter(), $e->getRouteMatch());
        $url = $rh->frontend('auth', 'login') . '? request=' .  urlencode($request->getRequestUri());

        throw new HttpRedirectException(HttpRedirectException::SEE_OTHER_303, $url);
    } else {
        throw new HttpClientException(HttpClientException::FORBIDDEN_403);
    }
}

The problem is those exceptions collapse the application.  I mean there is no any handling of such exceptions in the framework.  And i didnt find any help in documentation.  How can i solve that?  I guess there shoud be some best practices about "correct" access checking and access-exceptions handling in zf. 

A:

I never seen someone throw exceptions for this purpose, especially HttpRedirectException and HttpClientException so unfortunately I don't have an answer for that now. 
You can achieve what you want in a much more cleaner and easier way, without the need of throwing exceptions.  You can do something like:
$sharedEventManager->attach(AbstractActionController::class, 
                MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch'], 100);

And then check if the user is logged in, and based on that use this redirect method:
/**
 * Event listener method for the 'Dispatch' event.  We listen to the Dispatch
 * event to call the access filter.  The access filter allows to determine if
 * the current visitor is allowed to see the page or not.  If he/she
 * is not authorized and is not allowed to see the page, we redirect the user 
 * to the login page. 
 */
public function onDispatch(MvcEvent $event)
{
    // Get controller and action to which the HTTP request was dispatched. 
    $controller = $event->getTarget();
    $controllerName = $event->getRouteMatch()->getParam('controller', null);
    $actionName = $event->getRouteMatch()->getParam('action', null);

    // Convert dash-style action name to camel-case. 
    $actionName = str_replace('-', '', lcfirst(ucwords($actionName, '-')));

    // Get the instance of AuthManager service. 
    $authManager = $event->getApplication()->getServiceManager()->get(AuthManager::class);

    // Execute the access filter on every controller except AuthController
    // (to avoid infinite redirect). 
    if ($controllerName!=AuthController::class)
    {
        $result = $authManager->filterAccess($controllerName, $actionName);

        if ($result==AuthManager::AUTH_REQUIRED) {

            // Remember the URL of the page the user tried to access.  We will
            // redirect the user to that URL after successful login. 
            $uri = $event->getApplication()->getRequest()->getUri();

            // Make the URL relative (remove scheme, user info, host name and port)
            // to avoid redirecting to other domain by a malicious user. 
            $uri->setScheme(null)
                ->setHost(null)
                ->setPort(null)
                ->setUserInfo(null);
            $redirectUrl = $uri->toString();

            // Redirect the user to the "Login" page. 
            return $controller->redirect()->toRoute('login', [], 
                    ['query'=>['redirectUrl'=>$redirectUrl]]);
        }
        else if ($result==AuthManager::ACCESS_DENIED) {
            // Redirect the user to the "Not Authorized" page. 
            return $controller->redirect()->toRoute('not-authorized');
        }
    }
}

As you can see, this is just an example but it's very straight forward and you have check ACL's as well, and there is no need to throw exceptions...
However what I would do ( and I do ) to intercept all exceptions is to attach the following:
// The "init" method is called on application start-up and 
    // allows to register an event listener. 
    public function init(ModuleManager $manager)
    {
        // Get event manager. 
        $eventManager = $manager->getEventManager();
        $sharedEventManager = $eventManager->getSharedManager();
        // Register the event listener method.  
        $sharedEventManager->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH_ERROR,
                                    [$this, 'onError'], 100);
        $sharedEventManager->attach(__NAMESPACE__, MvcEvent::EVENT_RENDER_ERROR, 
                                    [$this, 'onError'], 100);
    }

    // Event listener method. 
    public function onError(MvcEvent $event)
    {
        // Get the exception information. 
        $exception = $event->getParam('exception');
        if ($exception!=null) {
            $exceptionName = $exception->getMessage();
            $file = $exception->getFile();
            $line = $exception->getLine();
            $stackTrace = $exception->getTraceAsString();
        }
        $errorMessage = $event->getError();
        $controllerName = $event->getController();

        // Prepare email message. 
        $to = 'admin@yourdomain.com';
        $subject = 'Your Website Exception';

        $body = '';
        if(isset($_SERVER['REQUEST_URI'])) {
            $body .= "Request URI: " .  $_SERVER['REQUEST_URI'] . " \n\n";
        }
        $body .= "Controller: $controllerName\n";
        $body .= "Error message: $errorMessage\n";
        if ($exception!=null) {
            $body .= "Exception: $exceptionName\n";
            $body .= "File: $file\n";
            $body .= "Line: $line\n";
            $body .= "Stack trace:\n\n" .  $stackTrace;
        }

        $body = str_replace("\n", "<br>", $body);

        // Send an email about the error. 
        mail($to, $subject, $body);
    }

This will catch all the errors and even send you an email!  LOL
I hope these snippets will help you understand how to better develop using ZF3. 
Credit for these snippets go to olegkrivtsov.