How to end the execution of an action via an event - zend-framework2

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
}

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').

Use the following in your event:
$e->stopPropagation();

Related

Electron: Can same channel name use for ipcMain.on and ipcMain.handle?

Can we register the same channel for ipcMain.on method and ipcMain.handle()?
For eg:
ipcMain.handle('test', async(event,args) => {
let result = await somePromise();
return result;
});
ipcMain.on('test', async(event,args) => {
event.returnValue = await somePromise();
});
Will the above code, give the error No handler register for 'test'? if ipcRenderer called this via invoke and sendSync in an order?
for eg:
ipcRenderer.invoke('test', data).then(result => {
console.log(result);
return result;
});
someFunction(data) {
return ipcRenderer.sendSync('test', data);
}
This is one of those things that you can easily test out.
Looking at their code for ipcMain.handle, they store the channel name in an _invokeHandlers map that seems isolated from the rest of the module (meaning from ipcMain.on).
In fact, ipcMain extends an EventEmitter, which is a Node class that maintains its own internal structure for managing events (This is the module where on and once are defined).
So you should be able to safely use both ipcMain.on("test", ...) and ipcMain.handle("test", ...) as long as you trigger them using the appropriate mechanism: send/sendSync corresponds with on/once and invoke corresponds with handle/handleOnce.

Zend framework 2 session exists or not, in Module.php

I am trying to call the session in public function onBootstrap(MvcEvent $e) function in Module.php
public function onBootstrap(MvcEvent $e)
{
if( $user_session->offsetExists('user_email_id')){
//code here
}
else {
header("Location: ". $this->serverUrl() . "/register");
}
}
How can i achieve this?
i am not getting the echo $this->serverUrl(); inside the OnBootstrap function
There a number of problems with this code.
You need to create a new session container (Zend\Session\Container) to set/get your session data.
You are trying to set headers manually, although this would work, there are better ways to do so in ZF2.
Redirection in the onBootstrap method is probably not the best 'time' to do so.
You attempt to use a view helper in Module.php (\Zend\View\Helper\ServiceUrl) to redirect. View helpers can should only be called in the view. You can use them, however you would need to fetch it via the ViewPluginManager, rather than using $this->.
With these points in mind I would consider adding a event listener either late onRoute or early onDispatch.
For example:
namespace FooModule;
use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\EventManager\EventInterface;
use Zend\Session\Container;
use Zend\Mvc\MvcEvent;
class Module implements BootstrapListenerInterface
{
public function onBootstrap(EventInterface $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, [$this, 'isLoggedIn'], 100);
}
public function isLoggedIn(MvcEvent $event)
{
$data = new Container('user');
if (! isset($data['user_email_id'])) {
$serviceManager = $event->getApplication()->getServiceManager();
$controllerPluginManager = $serviceManager->get('ControllerPluginManager');
// Get the \Zend\Mvc\Controller\Plugin\Redirect
$redirect = $controllerPluginManager->get('redirect');
return $redirect->toRoute('some/route/path', ['foo' => 'bar']);
}
// use $data here
}
}

ZF2 set custom URL/route and view

I am using ZF2 as a component of another application.
I am looking for a way to set the URL and View Template of the application between an init() and a run() call. I would like a way to either modify the Request and Response objects, or regenerate them with a different URL.
I currently use ob_start() and ob_get_clean() and a view template that simply generates the_content, thus injecting the output of ZF2 inside a page of another application.
Any suggestions on methodology would be appreciated.
In Module.php you can attach event to event manager for exemple.
class Module
{
public function onBootstrap($e)
{
$eventManager = $e->getApplication()->getEventManager();
$serviceManager = $e->getApplication()->getServiceManager();
$eventManager->attach(MvcEvent::EVENT_ROUTE, function($e) use ($eventManager, $serviceManager){
// your code here
}, -1000);
}
}
Or your action in your controller can dispatch another action and get the result
in action method :
$return = $this->forward()->dispatch('controllerName', array('action' => 'actionName', 'param1' => 'value', ...));
The following code inside another application can be used to set the calling URL and View Template from outside of the application:
$bootstrap = \Zend\Mvc\Application::init( include( '/zf2/config/application.config.php' ) );
$event = $bootstrap->getMvcEvent( );
/* Modify the event with a custom request. */
$request = new \Zend\Http\Request( );
$request->setMethod( \Zend\Http\Request::METHOD_GET );
$request->setUri( $custom_url );
$event->setRequest( $request );
/* Modify the view. */
$event->getViewModel()->setTemplate('layout/custom-layout');
ob_start( );
$bootstrap->run( );
$html = ob_get_clean( );

Zend Framework 2 - Cookie Concept

I have surfed a lot. I would like to assign and retrieve a value using a COOKIE. How can i do in ZF2? I saw a lot of examples for assigning value in cookie. Please explain that how to retrieve a value from cookie.
A cookie in HTTP (see RFC 2109 simply something stored in the request and send every time a request is made. A response can add other parameters to be stored additionally to the already existing cookies.
So the cookie retrieval is done via the Request, to update a cookie you use the Response. According to RFC 2109 you use respectively the Cookie header and the Set-Cookie header. You can thus directly access these headers via
$this->getRequest()->getHeaders()->get('Cookie')->foo = 'bar';
Or set cookies via:
$this->getResponse()->getHeaders()->get('Set-Cookie')->foo = 'bar';
Things are made a little bit easier though because there is a proxy at the request and response to directly access the cookie:
public function fooAction()
{
$param = $this->getRequest()->getCookie()->bar;
$this->getResponse()->getCookie()->baz = 'bat';
}
Keep in mind the Cookie and Set-Cookie headers implement the ArrayObject object. To check whether a cookie is present in the request, you can thus use offsetExists:
if ($cookie->offsetExists('foo')) {
$param = $cookie->offsetGet('foo');
}
/update:
If you want to modify properties of the cookie, you are also here modifying the Set-Cookie header. Take a look at the class on Github for all the methods available.
A slight summary:
$cookie = $this->getResponse()->getCookie();
$cookie->foo = 'bar';
$cookie->baz = 'bat';
$this->setDomain('www.example.com');
$this->setExpires(time()+60*60*24*30);
The cookie access via $this->getResponse()->getCookie() works but is long-winded and tiresome. So what I did, I extended Response and Request classes. Here what it looks like:
'service_manager' => array (
'factories' => array (
'Request' => 'Application\Mvc\Request\Factory',
'Response' => 'Application\Mvc\Response\Factory',
)
);
module/Application/src/Application/Mvc/Request/Factory.php
namespace Application\Mvc\Request;
use Zend\Console\Request as ConsoleRequest;
use Zend\Console\Console;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Factory implements FactoryInterface
{
public function createService (ServiceLocatorInterface $serviceLocator)
{
if (Console::isConsole ())
{
return new ConsoleRequest ();
}
return new HttpRequest ();
}
}
module/Application/src/Application/Mvc/Request/HttpRequest.php
namespace Application\Mvc\Request;
use Zend\Http\PhpEnvironment\Request;
class HttpRequest extends Request
{
public function hasCookie ($name)
{
assert ('is_string($name)');
$cookie = $this->getCookie();
if (empty ($cookie))
{
return false;
}
if (isset ($cookie [$name]))
{
return true;
}
return false;
}
public function cookie ($name, $default = null)
{
assert ('is_string($name)');
if ($this->hasCookie($name))
{
$cookie = $this->getCookie();
return $cookie [$name];
}
return $default;
}
}
module/Application/src/Application/Mvc/Response/Factory.php
namespace Application\Mvc\Response;
use Zend\Console\Response as ConsoleResponse;
use Zend\Console\Console;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Factory implements FactoryInterface
{
public function createService (ServiceLocatorInterface $serviceLocator)
{
if (Console::isConsole ())
{
return new ConsoleResponse ();
}
return new HttpResponse ();
}
}
module/Application/src/Application/Mvc/Response/HttpResponse.php
namespace Application\Mvc\Response;
use Zend\Http\PhpEnvironment\Response;
use Zend\Http\Header\SetCookie;
class HttpResponse extends Response
{
public function addCookie ($name, $value, $expires = null, $path = null, $domain = null, $secure = false, $httponly = false, $maxAge = null, $version = null)
{
$cookie = new SetCookie ($name, $value, $expires, $path, $domain, $secure, $httponly, $maxAge, $version);
$this->getHeaders ()
->addHeader ($cookie);
}
}
Now, I enjoy much easier access to cookies.
$this->getRequest ()->cookie ('ptime');
$this->getRequest ()->cookie ('alarm', 'last');
and
$this->getResponse ()->addCookie ('ptime', time ());

ZF2 redirect with POST

Before user tries an unauthorized action I save:
1) Controller Name
2) Action
3) POST params
Then when user has a successful login I redirect it to...
$params["controller"] = "manage";
$params["action"] = $lastRequest["action"];
$params["name"] = "Ignacio";
$params["email"] = "ignacio#gmail.com";
return $this->redirect()->toRoute("user-create", $params);
It does redirect, but NO posted params.
How can I emulate POST request on ZF2 from a controller?
The point is I do not know where the user it is going to be redirected, so it could be GET or POST and any controller.
This is the way I save a request and use it later to redirect to the correct action.
1) Unauthorized action saves the request with all GET/POST params.
$session = new Container('base');
$session->offsetSet("lastRequest", $event->getRequest());
2) After success login, redirect to requested
$session = new Container('base');
if($lastRequest = $session->offsetGet("lastRequest")) {
//Just redirect, because I could NOT find a way to POST params
return $this->redirect()->toUrl($lastRequest->getRequestUri());
}
3) Before controller action, retrieve all POST/GET params
class Module {
//...
public function init($moduleManager)
{
$sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, \Zend\Mvc\MvcEvent::EVENT_DISPATCH, array($this, 'preDispatch'), 100);
}
public function preDispatch($event)
{
//Unauthorized request after success login
$session = new Container('base');
if($lastRequest = $session->offsetGet("lastRequest")) {
$event->getTarget()->getRequest()->setMethod($lastRequest->getMethod());
$event->getTarget()->getRequest()->setPost($lastRequest->getPost());
$event->getTarget()->getRequest()->setQuery($lastRequest->getQuery());
//Delete request
$session->offsetSet("lastRequest", null);
}
}
4) Just use the request on any destination action as normal
class ManageController extends AbstractActionController {
public function createAction() {
if ($this->getRequest()->isPost()) {
$post = $this->getRequest()->getPost()->toArray();
}
}
You can't redirect user with POST data, but ZF2 provide functionality to simulate this:
http://framework.zend.com/manual/2.0/en/modules/zend.mvc.plugins.html#the-post-redirect-get-plugin
This solution might help but instead of using redirect, it forwards to another controller.
Before forwarding it to the other controller, replace (or update) the post params on the current request. The receiving controller then will see the replaced (or updated) post params.
// Controller that handles unauthorized access
class YourController extends AbstractActionController
{
// ...
$request = $this->getEvent()->getRequest();
$postParam = new \Zend\Stdlib\Parameters();
$postParam->set('controller', 'manage');
$postParam->set('action', $lastRequest['action']);
$postParam->set('name', 'your-name';
$postParam->set('email', 'your-email';
$request->setPost($postParam);
return $this->forward()->dispatch('Your\Other\UserCreateController', [
'action' => 'userCreate',
]);
// ...
}

Resources