We are using zend framework 2 for a new application, i would like to have the same logging system of Rails or similar, i would like have a log for each request, is possible to do this in Zend?
It depends what you want to log. If it is just an access log, you should try to use the webserver's log. The logs from Apache/nginx/IIS etc perform better than you will achieve in your ZF2 app.
If you need to log inside the ZF2 application, you have two choices. First option is at bootstrap. It's one of the earliest options you can use, so probably therefore the best. However, you can also look at route or dispatch. Those two events are called during the "run" phase of the application. With these events, you have for example a route match available and therefore you know (or not) if your request did match any controller (or in case you don't have the match, it's a 404).
Some examples. Let's assume you have a logger configured in the ServiceManager under the logger key. Then to log at bootstrap:
namespace Application;
class Module
{
public function onBootstrap($e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$logger = $sm->get('logger');
$logger->debug('Log here!');
}
}
Or for example if you wait for route, you attach a listener for the route event:
namespace Application;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Router\RouteMatch;
class Module
{
public function onBootstrap($e)
{
$app = $e->getApplication();
$em = $app->getEventManager();
$sm = $app->getServiceManager();
$logger = $sm->get('logger');
$em->attach(MvcEvent::EVENT_ROUTE, function($e) use ($logger) {
$match = $e->getRouteMatch();
// No route, this is a 404
if (!$match instanceof RouteMatch) {
return;
}
$logger->debug(sprintf(
'Route event with route %s',
$match->getMatchedRouteName()
));
});
}
}
Sounds like you could attach a listener to the dispatch event on Zend\Mvc\Application.
For reference, Rob Allen has created a handy list of ZF2 events.
Related
I have a "standard procedure" that I need in EVERY route I call in Symfony.
I basically create a short (relatively) unique number, check the permission and log that the function has been called.
Here is one example:
**
* #Route("/_ajax/_saveNewClient", name="saveNewClient")
*/
public function saveNewClientAction(Request $request)
{
/* Create Unique TrackNumber */
$unique= $this->get('log')->createUnique();
/* Check Permission */
if (!$permission = $this->get('permission')->needsLevel(2, $unique)) {
/* Log Action */
$this->get('log')->writeLog('No Permission, 2 needed', __LINE__, 4);
return new JsonResponse(array(
'result' => 'error',
'message' => 'Insufficient Permission'
)
);
}
/* Log Action */
$this->get('log')->writeLog('called '.__FUNCTION__, __LINE__, 1, $unique);
return $this->render(':admin:index.html.twig', array());
}
Is there a way to put all that in one function somewhere?
The writeLog gets called at other parts in the functions as well, so I don't want to combine it with the permisson check, although that would be possible of course.
When creating an EventListener, do I still have to call it in every function or is it possible to have it automatically called?
Any hint appreciated!
You could try to make a beforefilter.
http://symfony.com/doc/current/cookbook/event_dispatcher/before_after_filters.html
How to Set Up Before and After Filters
It is quite common in web application development to need some logic to be executed just before or just after your controller actions acting as filters or hooks.
Some web frameworks define methods like preExecute() and postExecute(), but there is no such thing in Symfony. The good news is that there is a much better way to interfere with the Request -> Response process using the EventDispatcher component.
Token Validation Example
Imagine that you need to develop an API where some controllers are public but some others are restricted to one or some clients. For these private features, you might provide a token to your clients to identify themselves.
So, before executing your controller action, you need to check if the action is restricted or not. If it is restricted, you need to validate the provided token.
Please note that for simplicity in this recipe, tokens will be defined in config and neither database setup nor authentication via the Security component will be used.
Before Filters with the kernel.controller Event
First, store some basic token configuration using config.yml and the parameters key:
YAML
# app/config/config.yml
parameters:
tokens:
client1: pass1
client2: pass2
Tag Controllers to Be Checked
A kernel.controller listener gets notified on every request, right before the controller is executed. So, first, you need some way to identify if the controller that matches the request needs token validation.
A clean and easy way is to create an empty interface and make the controllers implement it:
namespace AppBundle\Controller;
interface TokenAuthenticatedController
{
// ...
}
A controller that implements this interface simply looks like this:
namespace AppBundle\Controller;
use AppBundle\Controller\TokenAuthenticatedController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller implements TokenAuthenticatedController
{
// An action that needs authentication
public function barAction()
{
// ...
}
}
Creating an Event Listener
Next, you'll need to create an event listener, which will hold the logic that you want executed before your controllers. If you're not familiar with event listeners, you can learn more about them at How to Create Event Listeners and Subscribers:
// src/AppBundle/EventListener/TokenListener.php
namespace AppBundle\EventListener;
use AppBundle\Controller\TokenAuthenticatedController;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class TokenListener
{
private $tokens;
public function __construct($tokens)
{
$this->tokens = $tokens;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
/*
* $controller passed can be either a class or a Closure.
* This is not usual in Symfony but it may happen.
* If it is a class, it comes in array format
*/
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get('token');
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException('This action needs a valid token!');
}
}
}
}
Registering the Listener
Finally, register your listener as a service and tag it as an event listener. By listening on kernel.controller, you're telling Symfony that you want your listener to be called just before any controller is executed.
YAML
# app/config/services.yml
services:
app.tokens.action_listener:
class: AppBundle\EventListener\TokenListener
arguments: ['%tokens%']
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
With this configuration, your TokenListener onKernelController method will be executed on each request. If the controller that is about to be executed implements TokenAuthenticatedController, token authentication is applied. This lets you have a "before" filter on any controller that you want.
My app has multiple modules, among these are 2 CMS modules and a front module. I want to trigger the identity validation method only on the CMS modules on all actions. I want to do this in something like a front controller plugin (ZF1 refference) and the way I see it it should be in Module.php of the module in cause, but everything in here gets triggered across the entire app on dispatch.
Although your event handler will affect all modules, you can filter your CMS modules by their namespace. For example, if all controllers in your CMS module are named like 'CMSModule\Controller\IndexController', you can test if the namespace is CMSModule and do the identity validation, like in example below:
class Module {
public function onBootstrap(\Zend\Mvc\MvcEvent $e){
// ...
$em = $e->getApplication()->getEventManager();
$em->attach(\Zend\Mvc\MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch'));
}
public function onDispatch(\Zend\Mvc\MvcEvent $e){
$match = $e->getRouteMatch();
$controller = $match->getParam('controller');
$segments = explode('\\', $controller);
$namespace = array_shift($segments);
if(!$namespace=='CMSModule1' && !$namespace=='CMSModule2' && ...)
return;
// Check the identity here
}
}
Is it possible to setup an event listener (or do — something else?) to listen for all the events fired by a Symfony 2 AppKernel application for a particular request?
That is, I know I can browse an application with app_dev.php and use the profiler to view a list of all the listeners, but I'm interested in grabbing a list of every event that's been dispatched/fired. I know some event systems have a special global/all listener what would let me receive every event. I'm wondering if Symfony has something similar, or if there's another mechanism to get a list of all the available events on a particular page.
I also know I could add some temporary debugging code to one of the event dispatcher classes
Symfony/Component/EventDispatcher/EventDispatcher.php
Symfony/Component/HttpKernel/Debug/ContainerAwareTraceableEventDispatcher.php
Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php
but I'm looking for something that is less of a hack/less-destructive.
New to Symfony, but not new to programming. Apologies if this is a naive question, but googling about hasn't revealed what I'm after.
The clean way would be creating your own EventDispatcher which executes your logging or whatever you're trying to do if an event occurs. Have a look at the default one to get an idea of how it works.
Now first create the class
use Symfony\Component\EventDispatcher\EventDispatcher;
class MyDispatcher extends EventDispatcher
{
// sadly those properties aren't protected in EventDispatcher
private $listeners = array();
private $sorted = array();
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}
$event->setDispatcher($this);
$event->setName($eventName);
// do something with the event here ... i.e. log it
if (!isset($this->listeners[$eventName])) {
return $event;
}
$this->doDispatch($this->getListeners($eventName), $eventName, $event);
return $event;
}
... then register your MyDispatcher as symfony's default one.
( by overwriting the original event_dispatcher service )
app/config/config.yml
services:
event_dispatcher:
class: Vendor\YourBundle\MyDispatcher
arguments: [#service_container]
... or even simpler just override the class parameter being used by symfony when creating the service.
parameters:
event_dispatcher.class: Vendor\YourBundle\MyDispatcher
I have a module in my zendframework 2 application which contains two controllers.
I want to set a different layout for one of the controller's actions.
Is there a way to set it inside module config file?
P.s: I just tried to set it inside controller's __CONSTRUCT method using the following commands but it just didnt worked!
$event = $this->getEvent();
$event->getViewModel()->setTemplate('layout/MYLAYOUT');
But if i use the above commands inside each action of my controller it just works fine.
See akrabat's examples for a number of nice ways that layouts, views, etcetera can be tweaked easily.
Specifically what you're looking for can be found on his github here.
Here is a cut-paste of the controller's action method that sets/uses the alternate layout:
public function differentLayoutAction()
{
// Use a different layout
$this->layout('layout/different');
return new ViewModel();
}
Edit: It looks like akrabat has an example that says Change the layout for every action within a module, which might give the best pointers for setting the layout in the config; but I just had a look at the code, and the example is currently unfinished, it's not changing the layout.
I can just point you into the right direction, since currently i'm unable to open a sample project. Evan Coury has posted a method for Module specific layouts. See the following links:
Module Specific Layouts in Zend Framework 2
<?php
namespace MyModule;
use Zend\ModuleManager\ModuleManager;
class Module
{
public function init(ModuleManager $moduleManager)
{
$sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
// This event will only be fired when an ActionController under the MyModule namespace is dispatched.
$controller = $e->getTarget();
$controller->layout('layout/alternativelayout');
}, 100);
}
}
Now how would this help you?: Well, $controller should have both the called controller and action stored. I'm sure you can check the $controller for the called action and then assign the layout accordingly.
I'm sorry i can currently only hint you into the direction, but i'm sure this can get you started.
#Sam's answer pretty much answers the question. As stated it just needs a check on which controller is called, which can be done like this:
<?php
namespace MyModule;
use Zend\ModuleManager\ModuleManager;
class Module
{
public function init(ModuleManager $moduleManager){
$sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
$controller = $e->getTarget();
if ($controller instanceof Controller\AltLayoutController) {
$controller->layout('layout/alternativelayout');
}
}, 100);
}
I
namespace Auth;
use Zend\ModuleManager\ModuleManager;
class Module
{
public function init(ModuleManager $moduleManager)
{
$sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
echo "I am init module dispatch";
exit();
}, 100);
}
}
$moduleManager->getEventManager()->getSharedManager()->attach() is working fine in ZF2 BETA5 but it is not working in stable final release.
Has this functionality taken off in final release?
How can I make this work in ZF2 final release?
public function onBootstrap(MvcEvent $e)
{
$application = $e->getApplication();
$sharedManager = $application->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
echo "I am init module dispatch";
exit();
}, 100);
}
In Beta Series of zend framework2
Auth\src\User\Controller\UserController.php
but in final release of zf2 this does not work. Main namespace folder should match exactly same as under src folder. so above will work only like this
Auth\src\Auth\Controller\UserController.php
or
User\src\User\Controller\UserController.php
Don't forget to change your namespaces and paths in module.php and module.config.php and controller file.
There are two ways,
You can get it from Module.php init method, by passing ModuleManger object into it and then modulemanager->getEventManager.
Or from onBootstrap method again in Module.php but not from ModuleManager but by the application object as Abdul did.
Remember, init and onBoostrap methods run for every page request. Registering Event there is okay but do not put heavy stuff there. I prefer sharedEventManager, as it is available even if the service is initializes in future.
Cheers!