I am using Symfony and i have created custom ExceptionListener to handle error.
class ExceptionListener
{
protected $templating;
protected $kernel;
public function __construct(EngineInterface $templating, $kernel)
{
$this->templating = $templating;
$this->kernel = $kernel;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
// exception object
$exception = $event->getException();
// new Response object
$response = new Response();
$response->setContent(
// create you custom template AcmeFooBundle:Exception:exception.html.twig
$this->templating->render(
'Exception/exception.html.twig',
array('exception' => $exception)
)
);
// HttpExceptionInterface is a special type of exception
// that holds status code and header details
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$this->container->get('monolog.logger.db')->info('something happened 34', [
'foo' => 'bar'
]);
$response->setStatusCode(500);
}
if($exception instanceof FatalThrowableError){
return $this->templating->render(
'Exception/exception.html.twig'
);
}
// set the new $response object to the $event
$event->setResponse($response);
}
}
and in service
kernel.listener.acme_foo_exception_listener:
class: AppBundle\Listener\ExceptionListener
arguments: [#templating, #kernel]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
My aim is to when symfony throws exception i need to log error in database so i have created Logger event as per below link and it works fine when i called in controller but this event doesn't work when i called inside ExceptionListener.
I got following error
Notice: Undefined property:
AppBundle\Listener\ExceptionListener::$container in
can any one help me how i can pass container inside Listener
As said by geoforce your service doesn't know about the container. Quick fix for this by changing the service arguments:
arguments: [#templating, #container]
While changing the listener constructor to:
public function __construct(EngineInterface $templating, ContainerInterface $container)
{
$this->container = $container;
// ...
This should work, but injecting the entire container is quite an overkill and should definitely be done differently. Inject just what you need:
arguments: [#templating, '#monolog.logger.db']
And your constructor:
public function __construct(EngineInterface $templating,
LoggerInterface $logger)
{
$this->logger = $logger;
// ...
Log with $this->logger->info(...).
Since you've said that you're new to Symfony, I'd heavily recommend reading the DI component (http://symfony.com/doc/current/components/dependency_injection.html) docs. Understanding what DI does and how it works is mandatory to work with MVC frameworks like Symfony.
Like the error says, you are trying to access a property that does not exist:
$this->container->get('monolog.logger.db')->info('something happened 34', [
'foo' => 'bar'
]);
The container property is never declared nor assigned. If you want to access your logging service inject it in your service definition, like you did with the templating and kernel services.
Updated service definition:
kernel.listener.acme_foo_exception_listener:
class: AppBundle\Listener\ExceptionListener
arguments: [#templating, #kernel, #monolog.logger.db]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
And update your class constructor to accept the log service as the third argument.
Related
I'm trying to log some informations with the logger service within an ExceptionListener class but I don't understand how to access / create a logger object...
Here is my piece of code :
class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
...
$exception = $event->getException();
if ($exception instanceof HttpExceptionInterface) {
// HTTP Exception (400, 401, 404, ...)
$response = new JsonResponse(...)
}
...
$event->setResponse($response);
}
}
The listener works perfectly but in this case nothing is logged by default into the dev.log file (of course logging is enabled and functional).
I tried to had an LoggerInterface parameter to the onKernelException function (autowiring ?) but without success.
Should I had some additional configuration in the service.yaml file ?
App\EventListener\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
Autowiring being by default on SF4, you should be able to inject the LoggerInterface into your listener without further configuration, like so (then call $this->logger when desired) :
use Psr\Log\LoggerInterface;
class ExceptionListener
{
protected $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
...
$exception = $event->getException();
if ($exception instanceof HttpExceptionInterface) {
// HTTP Exception (400, 401, 404, ...)
$response = new JsonResponse(...)
}
...
$event->setResponse($response);
}
}
Here is my code for my class listener :
<?php
namespace AppBundle\EventSubscriber;
use Lolautruche\PaylineBundle\Event\PaylineEvents;
use Lolautruche\PaylineBundle\Event\ResultEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class PaymentListener implements EventSubscriberInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public static function getSubscribedEvents()
{
return [
PaylineEvents::WEB_TRANSACTION_VERIFY => 'onTransactionVerify',
];
}
public function onTransactionVerify(ResultEvent $event)
{ break;
// You can access to the result object from the transaction verification.
/** #var \Lolautruche\PaylineBundle\Payline\PaylineResult $paylineResult */
$paylineResult = $event->getResult();
$transactionId = $paylineResult->getItem('[transaction][id]');
if (!$paylineResult->isSuccessful()) {
break;
if ($paylineResult->isCanceled()){
$this->logger->info("Transaction #$transactionId was canceled by user", ['paylineResult' => $paylineResult->getResultHash()]);
}
elseif ($paylineResult->isDuplicate()){
$this->logger->warning("Transaction #$transactionId is a duplicate", ['paylineResult' => $paylineResult->getResultHash()]);
}
else {
$this->logger->error("Transaction #$transactionId was refused by bank.", ['paylineResult' => $paylineResult->getResultHash()]);
}
return;
}
break;
// Transaction was validated, do whatever you need to update your order
// ...
// Assuming you have set a private data with "internal_id" key when initiating the transaction.
$internalId = $paylineResult->getPrivateData('idCommande');
$repoCommande = $this->getDoctrine()->getManager()->getRepository('CommandeBundle:Commande');
$commande = $repoCommande->find($id);
$commande->setValide(1);
$em = $this->getDoctrine()->getManager();
$em->persist($commande);
$em->flush();
$this->logger->info("Transaction #$transactionId is valid. Internal ID is $internalId");
}
}
then I declared it as a service
services:
app.payment_listener:
class: AppBundle\EventSubscriber\PaymentListener
arguments: ["#LoggerInterface"]
tags:
- { name: kernel.event_subscriber }
But the arguments is not good. The constructor asks a loggerInterface argument and it returns me the following error :
ServiceNotFoundException in CheckExceptionOnInvalidReferenceBehaviorPass.php line 58: The service "app.payment_listener" has a dependency on a non-existent service "loggerinterface".
I explain what I would like to do, in fact I want use the payline bundle but I am stuck here.
Please, help me.
When you're passing an argument to constructor, as _construct(LoggerInterface $logger) you're telling that $logger argument can be any object whose class is the child of the LoggerInterface. So, in your service definition you can pass any logger service (#logger service, for example), not the interface itself. The answer to your question is, pass #logger service from Monolog bridge (or any other service name, which extends the LoggerInterface).
You can find more information here.
I'm trying to generate a custom error page in symfony2:
I was following this tuto.
However it shows me this:
InvalidArgumentException in YamlFileLoader.php line 145: A service definition must be an array or a string starting with "#" but NULL found for service "kernel.listener.allotaxi_exception_listener" in services.yml. Check your YAML syntax.
my services.yml :
services:
kernel.listener.allotaxi_exception_listener:
class: AlloTaxi\AlloTaxiBundle\Listener\ExceptionListener
arguments: [#templating, #kernel]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
ExceptionListener.php :
namespace AlloTaxi\AlloTaxiBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
class ExceptionListener {
protected $templating;
protected $kernel;
public function __construct(EngineInterface $templating, $kernel)
{
$this->templating = $templating;
$this->kernel = $kernel;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
// provide the better way to display a enhanced error page only in prod environment, if you want
if ('prod' == $this->kernel->getEnvironment()) {
// exception object
$exception = $event->getException();
// new Response object
$response = new Response();
// set response content
$response->setContent(
// create you custom template AcmeFooBundle:Exception:exception.html.twig
$this->templating->render(
'AlloTaxiBundle:Exception:error.html.twig',
array('exception' => $exception)
)
);
// HttpExceptionInterface is a special type of exception
// that holds status code and header details
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$response->setStatusCode(500);
}
// set the new $response object to the $event
$event->setResponse($response);
}
}
}
Any idea what could it be? I followed exactly when he did there.
services:
kernel.listener.allotaxi_exception_listener:
class: AlloTaxi\AlloTaxiBundle\Listener\ExceptionListener
arguments: [#templating, #kernel]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
By looking at the services.yml file posted in your question, you are missing the indentation level for "class", "arguments" & "tags". They appear to be at the same level as that of your service name "kernel.listener.allotaxi_exception_listener"
Have a look at above service definition, notice the indentation between service name and class, arguments & tags.
When dealing with yml files one needs to be careful about proper hierarchy
I need to log all my users actions with monolog. But only if the actions persist data with doctrine, insert, update or delete.
What should I do ? Could I define a generic method like "afterPersist" to log every action ?
Thx !
EDIT :
The Listener :
use Doctrine\ODM\MongoDB\Event\OnFlushEventArgs;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\SecurityContextInterface;
class DatabaseLogger
{
protected $logger;
protected $security_context;
protected $request;
public function __construct(LoggerInterface $logger, ContainerInterface $service_container)
{
$this->logger = $logger;
$this->setSecurityContext($service_container->get('security.context'));
}
public function setRequest(RequestStack $request_stack)
{
$this->request = $request_stack->getCurrentRequest();
}
public function setSecurityContext(SecurityContextInterface $security_context)
{
$this->security_context = $security_context;
}
public function onFlush(OnFlushEventArgs $args)
{
// configure this however you want
}
}
and in service.yml
cc.listener.database_logger:
class: Cc\HitoBundle\Listener\DatabaseLogger
tags:
- { name: doctrine_mongodb.odm.event_listener, event: onFlush }
- { name: monolog.logger, channel: database_access }
calls:
- [ setRequest, [#request_stack] ]
arguments: [ #logger, #service_container ]
I got an error when I add the security context :
ServiceCircularReferenceException: Circular reference detected for service "doctrine_mongodb.odm.default_document_manager", path: "doctrine_mongodb.odm.default_document_manager -> doctrine_mongodb.odm.default_connection -> doctrine_mongodb.odm.event_manager -> cc.listener.post_persist -> security.context -> security.authentication.manager -> security.user.provider.concrete.user_db".
Register a listener with something like:
Build a listener:
namespace Acme\MyBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
class PersistLogger
{
public $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
public function postPersist(LifecycleEventArgs $args)
{
// configure this however you want
$this->logger->addDebug('whatever');
}
}
Register the listener in config.yml
acme_mybundle.eventlistener.persistlogger:
class: Acme\MyBundle\EventListener\PersistLogger
tags:
- { name: doctrine.event_listener, event: postPersist }
argument: [ #logger ]
EDIT:
Injecting the security context into a doctrine listener causes a circular reference exception if you are storing your users in the database (e.g. with FOSUserBundle). This is because the security context needs to inject the entity manager so it can get users from the database, but because of the listener, the entity manager depends on the security context.
The workaround is to inject the whole service container (one of the only times doing this is justified), and get the security context from there:
namespace Acme\MyBundle\EventListener;
use Psr\Log\LoggerInterface,
Symfony\Component\DependencyInjection\ContainerInterface,
Symfony\Component\Security\Core\SecurityContextInterface;
protected $service_container;
protected $logger;
public function __construct(LoggerInterface $logger, ContainerInterface $service_container)
{
$this->service_container = $service_container;
$this->logger = $logger;
}
public function getSecurityContext()
{
return $this->service_container->get('security.context');
}
and
acme_mybundle.eventlistener.persistlogger:
class: Acme\MyBundle\EventListener\PersistLogger
tags:
- { name: doctrine.event_listener, event: postPersist }
argument: [ #logger, #service_container ]
I think that you may have a look to the cookbook, there is a very nice entry that talk about Doctrine's events.
In addition, you may have a look to the method to create custom monolog chanels.
I have a Doctrine filter in Symfony2 project. I am trying to set filter's parameter to some value (taken from session) on every request.
The problem is that filter object is created after Symfony's onKernelRequest event, so I can't set it from there. If I try to set it in Doctrine's postConnect event circular dependency is detected:
ServiceCircularReferenceException: Circular reference detected for service "doctrine.orm.private_entity_manager", path: "routing.loader -> assetic.asset_manager -> twig -> translator.default -> doctrine.orm.private_entity_manager -> doctrine.dbal.private_connection -> year_visibility.parameter_setter".
The question is, where (or rather how) should I set filter's parameter?
You can try to define filters manually and pass required parameters at the same time.
services:
app.filter_manager:
class: App\Bundle\AppBundle\Filter\FilterManager
arguments: [#doctrine.orm.entity_manager, #session]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
And in the filter manager class:
// ...
public function __construct(EntityManager $em, SessionInterface $session)
{
$this->em = $em;
$this->session = $session;
}
// ...
public function onKernelRequest()
{
$this->em->getConfiguration()->addFilter('filter_name', 'Filter/Class/Name/With/Ns');
$filter = $this->em->getFilters()->enable('filter_name');
$filter->setParameter('param_name', $this->session->get('param_name'));
}
As seen here: https://stackoverflow.com/a/14650403/244058 ,
you can have an instance of your Filter class at kernel boot.
So, your instance would be available very early.
<?php
class MyBundle extends Bundle
{
public function boot()
{
$em = $this->container->get('doctrine.orm.default_entity_manager');
$conf = $em->getConfiguration();
$conf->addFilter(
'filter_name',
'Doctrine\Filter\TestFilter'
);
// either enable it here, or later in the event listener
$em->getFilters()->enable('filter_name');
}
}
After that, just add a kernel.event_listener that listens on kernel.request and set a filter parameter (something like this):
<?php
class DoctrineSqlFilterConfigurator
{
private $em; // inject the entity manager somehow (ctor is a good idea)
public function onKernelRequest(GetResponseEvent $event)
{
$filter = $this->em->getFilters()->enable('filter_name');
$filter->setParameter('param_name', $event->getRequest()->getSession()->get('param_name'));
}
}