Zend Framework 2 actions without layout by default - php

I would like set all actions because when these return a ViewModel class, this has the parameter setTerminal = true by default.
I want that my aplication have this behavior because the 90% of my calls are AJAX.
Thanks in advance.

Check the Creating and Registering Alternate Rendering and Response Strategies.
http://framework.zend.com/manual/2.0/en/modules/zend.view.quick-start.html#creating-and-registering-alternate-rendering-and-response-strategies.
namespace Application;
class Module
{
public function onBootstrap($e)
{
// Register a "render" event, at high priority (so it executes prior
// to the view attempting to render)
$app = $e->getApplication();
$app->getEventManager()->attach('render', array($this, 'registerJsonStrategy'), 100);
}
public function registerJsonStrategy($e)
{
$app = $e->getTarget();
$locator = $app->getServiceManager();
$view = $locator->get('Zend\View\View');
$jsonStrategy = $locator->get('ViewJsonStrategy');
// Attach strategy, which is a listener aggregate, at high priority
$view->getEventManager()->attach($jsonStrategy, 100);
}
}

Related

How to get module name using shardManager in Laminas?

I need to get the module name of dispatched request within Module.php onBootstrap method in a zf3 application. With the previous version (which was under Zend namespace) I was able to do that by using shared event manager as mentioned below.
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$eventManager = $app->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
$sharedEventManager->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, function($e) {
$controller = $e->getTarget();
$controllerClass = get_class($controller);
$moduleName = strtolower(substr($controllerClass, 0, strpos($controllerClass, '\\')));
// rest of code
// ..................
// ..................
}, 100);
}
But with the newer version of the framework (which is under Laminas namespace), it's not working. It's even not listening to the MvcEvent::EVENT_DISPATCH of the AbstractActionController::class target class. And I could found that they have removed the SharedEventManager functionality according to below link.
https://docs.laminas.dev/laminas-eventmanager/migration/removed/#sharedeventmanagerawareinterface
So the question is how to get the module name of the dispatched request within the onBootstrap method in new version of the framework?
Is there an any workaround to get module name ?
I don't know if this will help but to get the controller and module in a listener in Laminas I use.
In my module.php bootstrap method:
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(\Laminas\Mvc\MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch'), -100);
and in the onDispatch method:
public function onDispatch(\Laminas\EventManager\EventInterface $event)
{
$routeMatch = $event->getRouteMatch();
$controller = get_class($event->getTarget());
list($module) = explode("\\", $controller);
}
I hope this points you in the right direction.

Plugin for zend framework

$url = $_SERVER['SERVER_NAME'];
if(!filter_var($url, FILTER_VALIDATE_URL)){
return false;
}
return true;I need to connect plugin CheckDomain which loaded on pre dispatch with all modules except Admin.
Plugin is a class CheckDomain which could be called as a function CheckDomain() when it's called in that way it checks is domain equal to "test.example.com"
<?php
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Mvc\Controller\Plugin\FlashMessenger;
use Zend\Mvc\Controller\Plugin\Forward;
use Zend\Mvc\Controller\Plugin\Layout;
use Zend\Mvc\Controller\Plugin\Params;
use Zend\Mvc\Controller\Plugin\PostRedirectGet;
use Zend\Mvc\Controller\Plugin\Redirect;
use Zend\Mvc\Controller\Plugin\Url;
use Zend\View\Model\ViewModel;
class CheckDomainPlugin extends AbstractPlugin{
public function checkdomain()
{
$url = $_SERVER['SERVER_NAME'];
if(!filter_var($url, FILTER_VALIDATE_URL)){
return false;
}
return true;
}
}
I call it for every controller except Admin, but I need to use it once.
I mean is it possible to load automatically plugin for all modules axcept admin
if you wish to "hook up" into every module you should read the zf2 documentation about MVC Events and the EventManager class.
http://framework.zend.com/manual/2.3/en/modules/zend.mvc.mvc-event.html#the-mvcevent
http://framework.zend.com/manual/2.3/en/modules/zend.event-manager.event-manager.html
Here is a small example for your Application/Module.php
public function onBootstrap(MvcEvent $e)
{
$application = $e->getApplication();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedManager = $eventManager->getSharedManager();
// DISPATCH EVENT
$sharedManager->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function( MvcEvent $e) use ($serviceManager) {
$controller = $e->getTarget();
$controllerClass = get_class($controller);
$moduleNamespace = substr($controllerClass, 0, strpos($controllerClass, '\\'));
// this is the first segment from the module namespace
// if the Admin Namespace is something like this Admin/Controller/...
if( $moduleNamespace != 'Admin' ) {
$CheckDomainPlugin = $serviceManager->get('ControllerPluginManager')->get('CheckDomainPlugin');
// do something
}
}, 50 );
}
If you create a plugin, or any service, they will be available to all modules within the application. If there are areas in which the plugin should not be used then don't call it!
If I actually understand your problem; I would use an event listener for this. If you listen 'on dispatch' you can exclude all the admin controllers if you give them a unique interface.
// Module.php
public function onBootstrap($event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach(
'Zend\Mvc\Controller\AbstractActionController',
'dispatch',
function($e) {
$target = $e->getTarget(); // The dispatched controller
if ($controller instanceof AdminControllerInterface) {
return;
}
// Do something here
}
);
}

Pass object from dispatch listener to the controller

So I have a dispatch event listener in Module.php, that retrives user object from database, and sets layout variable with it.
I want to access the very same object in controller. How can I pass it?
public function onBootstrap($e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$app = $e->getParam('application');
$app->getEventManager()->attach('dispatch', array($this, 'setPortfolioLayout'));
}
/**
* This event will set $user variable in layout when it's active
*
* #param MvcEvent $e
*/
public function setPortfolioLayout(MvcEvent $e)
{
$matchedRoute = $e->getRouteMatch();
if($matchedRoute->getParam('isPortfolio')) {
$em = $e->getApplication()->getServiceManager()->get('Doctrine\ORM\EntityManager'); /** #var ObjectManager $em */
$user = $em->getRepository('Application\Entity\User')->findOneBy(['login' => $matchedRoute->getParam('login')]);
if(! $user) {
$e->getResponse()->setStatusCode(404); return;
}
$viewModel = $e->getViewModel();
$viewModel->setVariable('user', $user);
$viewModel->setTemplate('layout/portfolio');
$matchedRoute->setParam('user', $user);
}
}
I've tried setting the RouteMatch parameter - not the best place to do it, and it is not visible in Controller.
There's a $e->getController() method in listener. Should I add a special method for setting the user object for every controller that expects it (adding interfaces traits etc)?
You can access the layout parameters you set in the controller pretty easily:
$user = $this->layout()->user;
Also, you mentioned you cannot access RouteMatch parameters in the controller, but you actually can:
$user = $this->getEvent()->getRouteMatch()->getParam('user');
Additionally, if the "user" is a hard-dependency of this particular controller, I see nothing wrong with setting it into the ServiceManager (before dispatch, so you'd need to increase the priority of your dispatch listener in your example) and making it a hard dependency (constructor injection) of the controller. You'd then create a factory for your controller which constructs the controller like:
return new MyController($sm->get('active-user'));
In this case, you'd want to be careful to make sure that you have adequate controls in place to make sure this controller can't be dispatched in situations where there might not be an 'active-user' service (or whatever you call it).

Modify ViewModel after controller dispatch

I'm trying to intercept the ViewModel, prior to it being rendered, and add it to another 'parent' view model - much like the way that the ZF2 layout wraps around the controllers returned content.
The simple (working) way to do this would be in each controller action.
public function dashboardAction() {
$dashboardContent = new ViewModel(array('foo' => 'bar'));
$parent = new ViewModel();
$parent->setTemplate('foo/bar/parent-template');
$parent->addChild($dashboardContent, 'content');
return $parent;
}
This works as expected and the 'child' view is correctly nested within the 'parent' in the final output.
As I have a number of controllers/actions that should all behave in the same way (resolved via their route name); I was hoping to encapsulate this in an event listener.
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, array($this, 'addUserAccountLayout'), -100);
}
public function addUserAccountLayout(EventInterface $event)
{
$routeMatch = $event->getRouteMatch();
$controller = $event->getTarget();
$result = $event->getResult();
if (! $result instanceof ViewModel || $result instanceof JsonModel) {
return;
}
if (! $routeMatch instanceof RouteMatch || 0 !== strpos($routeMatch->getMatchedRouteName(), 'zfcuser') || $result->terminate()) {
return;
}
$application = $event->getApplication();
$serviceManager = $application->getServiceManager();
$accountService = $serviceManager->get('JobboardUser\Service\AccountService');
$user = $accountService->getCurrentUser();
$parent = new ViewModel();
$parent->setVariables(compact('user'));
$parent->setTemplate('jobboard-user/widget/user-account');
$parent->addChild($result, 'content');
$event->setResult($parent);
}
This however is not working; the normal view is rendered (without the parent). My guess is because I am either not using the correct event or event priority OR $event->setResult($view) is not the correct way to assign the result.
How can I modify and then re-assign the view from within an event listener?
I can't offer an explanation as to 'why', but a priority of -10 seems to be the sweet spot to get this working with the code you already have.
I do have one suggestion though, instead of listening to every dispatch event triggered by every module controller and then having to check if it's a zfcuser route, you can instead make use of the shared events manager to listen specifically to the ZfcUser controller you're interested in ...
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$sharedEvents = $eventManager->getSharedManager();
$sharedEvents->attach(
'ZfcUser\Controller\UserController', // controller FQCN to listen to
MvcEvent::EVENT_DISPATCH,
array($this, 'addUserAccountLayout'),
-10
);
}
If you do that, you can remove the routematch check 0 !== strpos($routeMatch->getMatchedRouteName(), 'zfcuser') in your callback entirely.
It's been a little while since I asked this; however I did find the solution I was after. It was indeed related to how the new ViewModel is attached back to the MvcEvent.
Previously I had tried to set the event's result property with no luck :
$mvcEvent->setResult($myNewViewModel);
The key was to first fetch the MvcEvent's own view model and then attach my custom one to it as a child.
$mvcEvent->getViewModel()->addChild($myNewViewModel, 'content');
I've also incorporated the point that #Crisp made regarding the event managers target. Previously, I was attaching to the main application dispatch event meaning that the listener would be called on every dispatch event.
I now specifically target the controllers I want to listen to.
$eventManager->attach(array(
'JobboardUser\Controller\AccountController',
'JobboardUser\Controller\ProfileController'
),
MvcEvent::EVENT_DISPATCH,
array($this, 'addUserAccountLayout'),
-80
);
The final listener code looks like this
public function addUserAccountLayout(EventInterface $event)
{
$result = $event->getResult();
if (! $result instanceof ViewModel || $result instanceof JsonModel) {
return;
}
$application = $event->getApplication();
$serviceManager = $application->getServiceManager();
$accountService = $serviceManager->get('JobboardUser\Service\AccountService');
$currentUser = $accountService->getCurrentUser();
$layout = new ViewModel();
$layout->setVariables(array('user' => $currentUser));
$layout->setTemplate('jobboard-user/widget/user-account');
$layout->addChild($result, 'userContent');
$event->getViewModel()->addChild($layout, 'content');
}

Zend Framework 2: Auto disable layout for ajax calls

An AJAX request to one of my controller actions currently returns the full page HTML.
I only want it to return the HTML (.phtml contents) for that particular action.
The following code poorly solves the problem by manually disabling the layout for the particular action:
$viewModel = new ViewModel();
$viewModel->setTerminal(true);
return $viewModel;
How can I make my application automatically disable the layout when an AJAX request is detected? Do I need to write a custom strategy for this? Any advice on how to do this is much appreciated.
Additionally, I've tried the following code in my app Module.php - it is detecting AJAX correctly but the setTerminal() is not disabling the layout.
public function onBootstrap(EventInterface $e)
{
$application = $e->getApplication();
$application->getEventManager()->attach('route', array($this, 'setLayout'), 100);
$this->setApplication($application);
$this->initPhpSettings($e);
$this->initSession($e);
$this->initTranslator($e);
$this->initAppDi($e);
}
public function setLayout(EventInterface $e)
{
$request = $e->getRequest();
$server = $request->getServer();
if ($request->isXmlHttpRequest()) {
$view_model = $e->getViewModel();
$view_model->setTerminal(true);
}
}
Thoughts?
Indeed the best thing would be to write another Strategy. There is a JsonStrategy which can auto-detect the accept header to automatically return Json-Format, but as with Ajax-Calls for fullpages, there it's good that it doesn't automatically do things, because you MAY want to get a full page. Above mentioned solution you mentioned would be the quick way to go.
When going for full speed, you'd only have one additional line. It's a best practice to always return fully qualified ViewModels from within your controller. Like:
public function indexAction()
{
$request = $this->getRequest();
$viewModel = new ViewModel();
$viewModel->setTemplate('module/controller/action');
$viewModel->setTerminal($request->isXmlHttpRequest());
return $viewModel->setVariables(array(
//list of vars
));
}
I think the problem is that you're calling setTerminal() on the view model $e->getViewModel() that is responsible for rendering the layout, not the action. You'll have to create a new view model, call setTerminal(true), and return it. I use a dedicated ajax controller so there's no need of determining whether the action is ajax or not:
use Zend\View\Model\ViewModel;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Controller\AbstractActionController;
class AjaxController extends AbstractActionController
{
protected $viewModel;
public function onDispatch(MvcEvent $mvcEvent)
{
$this->viewModel = new ViewModel; // Don't use $mvcEvent->getViewModel()!
$this->viewModel->setTemplate('ajax/response');
$this->viewModel->setTerminal(true); // Layout won't be rendered
return parent::onDispatch($mvcEvent);
}
public function someAjaxAction()
{
$this->viewModel->setVariable('response', 'success');
return $this->viewModel;
}
}
and in ajax/response.phtml simply the following:
<?= $this->response ?>
Here's the best solution (in my humble opinion). I've spent almost two days to figure it out. No one on the Internet posted about it so far I think.
public function onBootstrap(MvcEvent $e)
{
$eventManager= $e->getApplication()->getEventManager();
// The next two lines are from the Zend Skeleton Application found on git
$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);
}
I'm still not satisfied though. I would create a plugin as a listener and configure it via configuration file instead of onBootstrap method. But I'll let this for the next time =P
I replied to this question and seems it maybe similar - Access ViewModel variables on dispatch event
Attach an event callback to the dispatch event trigger. Once this event triggers it should allow you to obtain the result of the action method by calling $e->getResult(). In the case of an action returning a ViewModel it should allow you to do the setTerminal() modification.
aimfeld solution works for me, but in case some of you experiment issues with the location of the template, try to specify the module:
$this->viewModel->setTemplate('application/ajax/response');
The best is to use JsonModel which returns nice json and disable layout&view for you.
public function ajaxCallAction()
{
return new JsonModel(
[
'success' => true
]
);
}
I had this problem before and here is a quikc trick to solved that.
First of all, create an empty layout in your layout folder module/YourModule/view/layout/empty.phtml
You should only echo the view content in this layout this way <?php echo $this->content; ?>
Now In your Module.php set the controller layout to layout/empty for ajax request
namespace YourModule;
use Zend\Mvc\MvcEvent;
class Module {
public function onBootstrap(MvcEvent $e) {
$sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
if ($e->getRequest()->isXmlHttpRequest()) {
$controller = $e->getTarget();
$controller->layout('layout/empty');
}
});
}
}
public function myAjaxAction()
{
....
// View - stuff that you returning usually in a case of non-ajax requests
View->setTerminal(true);
return View;
}

Categories