0

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.

3
  • 1
    I'm not at all familiar with ZF3, but it doesn't sound correct that you'd throw an exception in order to send a redirect. Are you sure your if statement's first condition logic is correct? Maybe you want to use something like this: docs.zendframework.com/zend-mvc/plugins/#redirect-plugin Commented Apr 24, 2018 at 7:07
  • Yes, statements are correct. The deal is zf1-2 produces Exception redirection mechanism. But i dont know how to use it (or may be it was removed in zf3). Commented Apr 24, 2018 at 7:48
  • 1
    This type of exception handling needs a listener, which is triggered every time a specific exception is thrown. All these listeners observe the MvcEvent::EVENT_DISPATCH_ERROR event. There are different approaches to solve your problem. This has never been a native ZF mechanism. Commented Apr 24, 2018 at 22:30

1 Answer 1

1

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 = '[email protected]';
        $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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.