I read about some of the best practices for ZF2. There, it was explained to attach the events from MVC in the init()-Method of the module's Module class:
class Module {
public function getAutoloaderConfig() {
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
);
}
public function init(ModuleManager $moduleManager) {
echo 'init<br>';
$em = $moduleManager->getEventManager();
$em->attach(MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch'));
$em->attach(MvcEvent::EVENT_ROUTE, array($this, 'onRoute'));
}
public function onDispatch(MvcEvent $e){
echo 'onDispatch<br>';
}
...
It results in getting no error, nice. But the event is not caught...
Any ideas? I tried the SharedManager too, but it only worked for the EVENT_DISPATCH ...
Unless for specific cases, it's better to register your events in onBootstrap.
init is for "early events".
I found a link that is quite clear : http://samsonasik.wordpress.com/2013/03/30/zend-framework-2-getting-closer-with-eventmanager/
You can find the order of defaults MVC events in Zend\ModuleManager\Listener\DefaultListenerAggregate::attch :
public function attach(EventManagerInterface $events)
{
$options = $this->getOptions();
$configListener = $this->getConfigListener();
$locatorRegistrationListener = new LocatorRegistrationListener($options);
// High priority, we assume module autoloading (for FooNamespace\Module classes) should be available before anything else
$this->listeners[] = $events->attach(new ModuleLoaderListener($options));
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE_RESOLVE, new ModuleResolverListener);
// High priority, because most other loadModule listeners will assume the module's classes are available via autoloading
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new AutoloaderListener($options), 9000);
if ($options->getCheckDependencies()) {
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new ModuleDependencyCheckerListener, 8000);
}
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new InitTrigger($options));
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new OnBootstrapListener($options));
$this->listeners[] = $events->attach($locatorRegistrationListener);
$this->listeners[] = $events->attach($configListener);
return $this;
}
You'll be better using the shared manager. The example bellow is for disabling layout when we got a xmlHttpRequest and the priority -95 is a key point to make things work.
public function onBootstrap(MvcEvent $e) {
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
// Hybrid view for ajax calls (disable layout for xmlHttpRequests)
$eventManager->getSharedManager()->attach('Zend\Mvc\Controller\AbstractController', MvcEvent::EVENT_DISPATCH, function(MvcEvent $event){
/**
* #var Request $request
*/
$request = $event->getRequest();
$viewModel = $event->getResult();
if($request->isXmlHttpRequest()) {
$viewModel->setTerminal(true);
}
return $viewModel;
}, -95);
}
See http://akrabat.com/zend-framework-2/module-specific-bootstrapping-in-zf2/
Only found this solution: attach listeners in onBootstrap()-Method in the Module.php class:
...
public function onBootstrap(MvcEvent $e){
echo 'onBootstrap<br>';
$em = $e->getApplication()->getEventManager();
$em->attach(MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch'));
$em->attach(MvcEvent::EVENT_ROUTE, array($this, 'onRoute'));
}
...
Related
In Zend Framework 3, is it possible to disable the layout for an entire controller, preferably in the __construct() or onDispatch() methods?
I know that I can disable the layout for specific actions, for example:
public function indexAction()
{
$view = new \Zend\View\Model\ViewModel();
$view->setTerminal(true);
return $view;
}
However, I would like to disable the layout for all actions in the controller without having to copy and paste the above code in every action.
In your Module class :
public function onBootstrap(MvcEvent $e)
{
$sharedEvents = $e->getApplication()
->getEventManager()
->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch',
function ($e) {
if ($e->getRouteMatch()->getParam('controller') == '[your controller name in lowercase]') {
$result = $e->getResult();
if ($result instanceof \Zend\View\Model\ViewModel) {
$result->setTerminal(true);
} else {
throw new \Exception(
__METHOD__ . ' expected \Zend\View\Model\ViewModel');
}
}
});
}
I have the following code inside my Admin Module.php
public function onBootstrap(MvcEvent $e)
{
$application = $e->getApplication();
$em = $application->getEventManager();
if(!SystemUtils::isApiRequest()){
$em->attach(\Zend\Mvc\MvcEvent::EVENT_DISPATCH, array($this,'initUser'),10000);
}
}
public function initUser(MvcEvent $e)
{
$isLoggedIn=false;
// determined elsewhere
$action = Pluto::registry('application_action');
if($action!=='login' && !$isLoggedIn){
$viewHelperManager = $e->getApplication()->getServiceManager()->get('ViewHelperManager');
$renderer = $viewHelperManager->getRenderer();
$url = $renderer->plutourl('login');
$response = $e->getApplication()->getServiceManager()->get('Response');
$response->getHeaders()->clearHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302)->sendHeaders();
exit();
}
}
The problem is this is only supposed to activate when inside a page within the admin module and you are not logged in but its operating for every request regardless of the module the code is inside
I want to limit this check to just when accessing the admin module.
Update
$sm = $e->getApplication()->getServiceManager();
$router = $sm->get('router');
$request = $sm->get('request');
$matchedRoute = $router->match($request);
returns the admin controller class name which i can use but i want it for all of the admin controller classes
You can use the shared event manager to attach to events in a given name space. In the example below we attach to an event only for the current modules namespace.
use Zend\Mvc\MvcEvent;
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$em = $app->getEventManager()->getSharedManager();
$sm = $app->getServiceManager();
$em->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH, function ($e) use ($sm) {
/**
* this will be triggered only by controlers in
* this module/namespace...
*/
$this->doSomething();
});
}
You could add this inside your admin module and only this event will only be listened to for classes inside this module / namespace.
You should use the MvcEvent object!
If all controllers are under a single namespace, for instance \Admin\Controller:
$namespace = 'Admin\\Controller\\';
$matchController = $e->getRouteMatch()->getParam('controller');
if( $namespace === substr($matchController, 0, strlen($namespace))
{
// check here if user is logged in.
}
else {
// not an admin controller, do nothing.
}
I searched for the same and got some answer about it, but did not help.
Those way I tried are as follows:
1)
in onBootstrap($e) function:
$e->getViewModel()->setVariable('test_variable', 'Hello World!');
//or
$e->getViewModel()->test_variable = 'Hello World!';
2)
I created a function in Module.php
function boforeDispatch(MvcEvent $event) {
$event->getViewModel()->setVariable('test_variable', 'Hello World!');
//or
$event->getViewModel()->test_variable = 'Hello World!';
}
and called this function inside the onBootstrap($e) function
$application = $e->getApplication();
$eventManager = $application->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, array($this, 'boforeDispatch'), 100);
when I tried in view
echo $test_variable;
it gives undefined variable test_variable in ... error all the times.
Any idea?
Try using render event (MvcEvent::EVENT_RENDER) instead:
public function onBootstrap($event){
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$events->attach(MvcEvent::EVENT_RENDER, array($this, 'addVariablesToViewModel'), 100);
}
public function addVariablesToViewModel($event)
{
$viewModel = $this->getEvent()->getViewModel();
$viewModel->setVariables(array(
'key' => 'value',
));
}
Your view model is probably not yet available on (pre) dispatch
In my Module.php I have attached a handler to catch exceptions. This works, but I'm not sure how I setup a response and view template.
My code below attempts to set the responce code to 409 and display the 409.phtml template. However it does not work, it just renders the original 500 error page.
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$eventManager->attach(\Zend\Mvc\MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'handleError'));
}
public function handleError(MvcEvent $event)
{
//get the exception
$exception = $event->getParam('exception');
if (is_a($exception, 'Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException'))
{
$response = $event->getResponse();
$response = $response ?: new Response();
$model = new ViewModel(array());
$model->setTemplate('error\409');
$event->getViewModel()->addChild($model);
$response->setStatusCode(409);
$event->setResponse($response);
return $response;
}
}
This seems to work, but it would be interesting to know what others think.
public function handleError(MvcEvent $event)
{
//get the exception
$exception = $event->getParam('exception');
if (is_a($exception, 'Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException'))
{
$event->getResponse()->setStatusCode(409);
$model = new ViewModel(array());
$model->setTemplate('error/409');
$event->getViewModel()->clearChildren();
$event->getViewModel()->addChild($model);
$event->stopPropagation(true);
}
}
I have found a bunch of examples how to unit test Zend_Controller, but I'm looking for examples on Zend_Rest_Controller Unit Testing. Any help is really appreciated. Thank you!
So, basically your question is how to emulate calling PUT and DELETE in your controller tests?
Since this apparently doesn't work:
$this->request->setMethod('PUT');
You can access both these actions with plain HTTP POST by providing _method parameter.
So to call PUT:
$this->request->setMethod('POST');
$this->dispatch('articles/123?_method=put');
To call DELETE:
$this->request->setMethod('POST');
$this->dispatch('articles/123?_method=delete');
More reading on how to deal with RESTful routing here - http://framework.zend.com/manual/en/zend.controller.router.html#zend.controller.router.routes.rest
/**
* Sample class to test a controller
*/
class ArticleControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public $bootstrap;
public function setUp()
{
// When bootstrap is called it will run function 'appBootstrap'
$this->bootstrap = array($this, 'appBootstrap');
parent::setUp();
}
public function appBootstrap()
{
$this->application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini');
$this->application->bootstrap();
$bootstrap = $this->application->getBootstrap();
$front = $bootstrap->getResource('FrontController');
$front->setParam('bootstrap', $bootstrap);
}
public function tearDown()
{
Zend_Controller_Front::getInstance()->resetInstance();
$this->resetRequest();
$this->resetResponse();
parent::tearDown();
}
public function testIndexAction()
{
$testCases = array(
'/article/',
'/article/id/123/',
'/article/authorId/777/limit/5/',
'/article/commentId/999/startDate/2011-06-01/endDate/2011-06-01/',
);
foreach ($testCases as $url) {
$this->request->setHeader('Content-Type', 'text/json');
$this->dispatch($url);
$this->assertResponseCode(200);
$this->assertModule('default');
$this->assertController('article');
$this->assertAction('get');
$body = json_decode($this->response->getBody(), true);
$this->assertNotEmpty($body);
...
$this->resetRequest();
$this->resetResponse();
}
}
public function testGetAction()
{
// Same as $this->testIndexAction()
}
public function testPostAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('POST'); before dispatch
// Change $this->assertResponseCode(200); to 201 as REST requires
}
public function testPutAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('PUT'); before dispatch
}
public function testDeleteAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('DELETE'); before dispatch
}
}