Reputation: 798
in my ZF2 application I am adding the following event listener, however I want to make the execution of the action actually stop, however this doesnt happen.
public function setEventManager(EventManagerInterface $events)
{
parent::setEventManager($events);
$controller = $this;
$events->attach('dispatch', function ($e) use ($controller) {
$request = $e->getRequest();
$method = $request->getMethod();
$headers = $request->getHeaders();
// If we get here, based on some conditions, the original intended action must return a new JsonViewModel()...
return new JsonViewModel([]); // However, this doesn't do anything.
}, 100); // execute before executing action logic
}
Upvotes: 1
Views: 1161
Reputation: 13558
Based on your comments, I am assuming you are doing some sort of authentication. You can perfecty use the event manager for this. However, I would not tie the listener to a single controller. If your API increases, you might want to split the API over several controllers and you get into trouble with your authentication.
My solution is to create a listener which listens to the dispatch event of the Zend\Mvc\Application. This is an event which is triggered before the event in the controllers itself.
use Zend\Mvc\MvcEvent;
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$em = $app->getEventManager();
$sm = $app->getServiceManager()->getSharedManager();
$listener = new Listener\Authentication();
$identifier = 'MyModule\Controller\ApiController';
$em->attach($identifier, MvcEvent::EVENT_DISPATCH, $listener, 1000);
}
This way, the listener is attached to all controllers which are identified with MyModule\Controller\ApiController
. The listener will be triggered on every dispatch call of those controllers. Your listener can short-circuit the complete dispatch loop in case you need it:
use Zend\Http\Request as HttpRequest;
use Zend\Mvc\MvcEvent;
use Zend\Json\Json;
use Zend\View\Model\JsonModel;
class Authentication
{
public function __invoke(MvcEvent $e)
{
$request = $e->getRequest();
if (!$request instanceof HttpRequest) {
// Don't run on CLI requests
return;
}
if ($result->isValid()) {
// Say you get auth result and all is OK
return;
}
// Case 1: short-circuit and return response, this is the fast way
// The response I use here is the HTTP problem API
$message = array(
'httpStatus' => 401,
'title' => 'Unauthorized',
'detail' => 'You are unauthorized to perform this request',
);
$response = $e->getResponse();
$response->setStatusCode(401);
$response->getHeaders()->addHeaderLine('Content-Type', 'application/json');
$response->setContent(Json::encode($message);
return $response;
// Case 2: don't short circuit and stop propagation, you're using the JSON renderer
$e->getResponse()->setStatusCode(401);
$message = array(
'httpStatus' => 401,
'title' => 'Unauthorized',
'detail' => 'You are unauthorized to perform this request',
);
$model = new JsonModel($message);
return $model;
}
}
I would advice you to use the first method (returning the response yourself) because you'll short circuit the complete dispatch process and skip the complete finishing of the request. If you really rely on the view and response senders, use the second case.
Now if you need a controller which is authenticated via this system, add the identifier to this controller:
namespace MyModule\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class MyFooBarApiController extends AbstractActionController
{
protected $eventIdentifer = 'MyModule\Controller\ApiController';
// your code here
}
If you need to allow certain requests without validation (I would always use a whitelist!), you can do this in your listener:
use Zend\Mvc\Route\RouteMatch;
$routematch = $e->getRouteMatch();
if (!$routematch instance of RouteMatch) {
// It's a 404, don't perform auth
return;
}
$route = $routematch->getMatchedRouteName();
if (
($request->isPost() && 'foo/bar' === $route)
|| ($request->isGet() && 'baz/bat' === $route)
) {
// We allow these requests to pass on without auth
return;
}
In your code you can explicitly check request method and route name. If you need parameters of the route, you can access it with $routematch->getParam('id')
.
Upvotes: 5