问题
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.
回答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 = '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.
来源:https://stackoverflow.com/questions/49984015/zend-framework-3-error-handling