Payum - Dispatched event on capture? - symfony

I'd like to know if Payum (or PayumBundle) dispatch an event when the payment is captured?
The aim is to send a confirmation email to the customer.
I found nothing is the doc, and I found only three events in the code source :
payum.gateway.pre_execute
payum.gateway.execute
payum.gateway.post_execute
But I guess it has no connection with the payment itself. Btw, it seems never dispatched with a Stripe payment.
Thanks.

Payum does not dispatch any events by default. It is designed to be extended by an extension. For example if you want to do some stuff on an instance push notification (Paypal IPN) you can add an extension like one described here: http://payum.org/doc/1.0/Core/instant-payment-notification
There is also an extension which dispatch events. You have to manually create it and add to payum.

I encounter the same issue : dispatching a custom event on some payment actions. In my case I want to deal with Authorize and Capture actions.
I noticed that Payum requests may be processed many times for 1 payment action, that's why I need to mark my payment as processed in order to dispatch only once my custom event (purpose of Payment::$customPaymentEventDispatched property).
Here is my Payment entity :
<?php
namespace MyApp\PaymentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Payum\Core\Model\Payment as BasePayment;
class Payment extends BasePayment
{
// ...
// Not persisted
protected $payable;
protected $customPaymentEventDispatched = false;
// ...
public function setPayable($payable)
{
$this->payable = $payable;
return $this;
}
public function getPayable()
{
return $this->payable;
}
public function markAsCustomPaymentEventDispatched()
{
$this->customPaymentEventDispatched = true;
return $this;
}
public function isCustomPaymentEventDispatched()
{
return $this->customPaymentEventDispatched;
}
}
Then register the Payum eventDispatcherExtension and my custom listener
services:
payum.extension.event_dispatcher:
class: Payum\Core\Bridge\Symfony\Extension\EventDispatcherExtension
arguments: ["#event_dispatcher"]
tags:
- { name: payum.extension, all: true, prepend: false }
my_app_payment.payum.listener.payment_listener:
class: MyApp\PaymentBundle\EventListener\PaymentListener
arguments:
- "#monolog.logger.payment"
tags:
- { name: kernel.event_listener, event: payum.gateway.post_execute }
And the code of the listener :
<?php
namespace MyApp\PaymentBundle\EventListener;
use Payum\Core\Bridge\Symfony\Event\ExecuteEvent;
use Payum\Core\Request\Authorize;
use Payum\Core\Request\BaseGetStatus;
use Payum\Core\Request\Capture;
use Payum\Core\Model\PaymentInterface;
use Payum\Core\Request\GetHumanStatus;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use MyApp\PaymentBundle\Entity\Payment;
use MyApp\PaymentBundle\Event\PayablePaymentStatusChangeEvent;
use MyApp\PaymentBundle\PaymentEvents;
class PaymentListener
{
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function onPayumGatewayPostexecute(ExecuteEvent $event, $eventName, EventDispatcherInterface $eventDispatcher)
{
$request = $event->getContext()->getRequest();
// Exclude Payum GetStatus request
// => protect from infinite loop as following code execute one GetStatus request
if ($request instanceof BaseGetStatus) {
return;
}
// Request types requiring dispatching an event
if (!($request instanceof Authorize || $request instanceof Capture)) {
return;
}
$payment = $request->getFirstModel();
if (!$payment instanceof PaymentInterface) {
return;
}
$event->getContext()->getGateway()->execute($status = new GetHumanStatus($payment));
if (!$payment->isCustomPaymentEventDispatched()) {
if ($request instanceof Capture && $status->isCaptured())
{
$payable = $payment->getPayable();
$this->logger->info('Dispatching PAYMENT_SUCCESS for '.$payable->__toString());
$tmEvent = new PayablePaymentStatusChangeEvent($payment->getPayable());
$eventDispatcher->dispatch(PaymentEvents::PAYMENT_PENDING, $tmEvent);
$payment->markAsCustomPaymentEventDispatched();
}
elseif ($request instanceof Authorize && $status->isAuthorized())
{
$payable = $payment->getPayable();
$this->logger->info('Dispatching PAYMENT_PENDING for '.$payable->__toString());
$tmEvent = new PayablePaymentStatusChangeEvent($payment->getPayable());
$eventDispatcher->dispatch(PaymentEvents::PAYMENT_PENDING, $tmEvent);
$payment->markAsCustomPaymentEventDispatched();
}
}
}
}
I'm not fully satisfied of this implementation but I did not find out a better way to achieve that. I'll appreciate any feedback about this !

That's how you can check whether payment is captured or not:
use Payum\Core\Action\CapturePaymentAction;
use Payum\Core\Bridge\Symfony\Event\ExecuteEvent;
use Payum\Core\Request\Capture;
use Payum\Core\Request\GetHumanStatus;
class SuccessPaymentListener
{
public function onPostExecute(ExecuteEvent $event) {
$context = $event->getContext();
$action = $context->getAction();
$request = $context->getRequest();
if ($action instanceof CapturePaymentAction && $request instanceof Capture) {
$status = new GetHumanStatus($request->getToken());
$gateway = $context->getGateway();
$gateway->execute($status);
if ($status->isCaptured()) {
$payment = $status->getFirstModel();
//here is your stuff
}
}
}
}
services.yml:
app.payum.extension.event_dispatcher:
class: Payum\Core\Bridge\Symfony\Extension\EventDispatcherExtension
arguments: ["#event_dispatcher"]
tags:
- { name: payum.extension, all: true, prepend: false }
app.success_payment_listener:
class: AppBundle\Listener\SuccessPaymentListener
tags:
- { name: kernel.event_listener, event: payum.gateway.post_execute, method: onPostExecute }

Related

Symfony seems to register, but not trigger my doctrine event

sry if something is not so accurate, but im less experienced with Symfony
I have the following orm mapping:
src/app/ExampleBundle/Resources/config/doctrine/Base.orm.yml
app\ExampleBundle\Entity\Base:
type: mappedSuperclass
fields:
createdAt:
type: datetime
nullable: true
options:
default: null
updatedAt:
type: datetime
nullable: true
options:
default: null
This creates a entity Base which i modified to be abstract
src/app/ExampleBundle/Entity/Base.php
abstract class Base {
...
}
I have some other entities they extend this abstract class e.g.
src/app/ExampleBundle/Entity/Category.php
class Category extends Base
{
...
}
Now i tried to add a listener that sets the createdAt/updatedAt datetime on every persist for every entity that extends the Base Entity
src/app/ExampleBundle/EventListener/BaseListener.php
namespace app\ExampleBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\User\UserInterface;
use app\ExampleBundle\Entity\Base;
class BaseListener
{
protected $tokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(Base $base, LifecycleEventArgs $event)
{
$user = $this->tokenStorage->getToken()->getUser();
if (!$user instanceof UserInterface) {
$user = null;
}
if ($base->getCreatedAt() === null) {
$base->setCreated($user, new \DateTime());
} else {
$base->setUpdated($user, new \DateTime());
}
}
}
And added it to the bundles services.yml
src/app/ExampleBundle/Resources/config
services:
app\ExampleBundle\EventListener\BaseListener:
arguments: ['#security.token_storage']
tags:
- { name: doctrine.orm.entity_listener, entity: app\ExampleBundle\Entity\Base, event: prePersist }
Symfony throws no Exception, but the defined event seems also not triggered.
I tried to change the entity param in services to the "real" entity Category, but still no error, nor the event triggered.
I think, i did everything as it is decribed in the documentation. But it still not working.
The command
debug:event-dispatcher
does also not show the event
So, the question is: What did i wrong?
Here the documentation I follow https://symfony.com/doc/3.4/doctrine/event_listeners_subscribers.html
The prePersist method is called for all the entities so you must exclude non instance of app\ExampleBundle\Entity\Base. The first argument is LifecycleEventArgs.
public function prePersist(LifecycleEventArgs $event)
{
$base = $event->getObject();
if (!$base instanceof Base) {
return;
}
$user = $this->tokenStorage->getToken()->getUser();
if (!$user instanceof UserInterface) {
$user = null;
}
if ($base->getCreatedAt() === null) {
$base->setCreated($user, new \DateTime());
} else {
$base->setUpdated($user, new \DateTime());
}
}
I can recommend you StofDoctrineExtensionsBundle (Timestampable) that does exactly what you want. It based on DoctrineExtensions.
There is even a trait that works like a charm.
After some research, many more tests, diving into the EntityManager and the UnitOfWork. Nothing seems to work fine. I get it so far to work on doctrine:fixtures:load, but for any reason they still not working if i use the entity manager in the Controllers. So, i decided to try another way with a subscriber.
tags:
- { name: doctrine.event_subscriber }
class ... implements EventSubscriber
So i still dont know why the Listener did not work as expected, but with the subscribers i found a solution that does.
Thanks to all of you for support :)
For Symfony 5 and anybody who will struggle with issue when Events::loadClassMetadata is not fired in subscriber.
Class should implement "EventSubscriberInterface"
class DiscriminatorSubscriber implements EventSubscriberInterface
{
public function getSubscribedEvents()
{
return [Events::loadClassMetadata];
}
public function loadClassMetadata(LoadClassMetadataEventArgs $event): void
{
}
Than you dont have to do anythng more if you have autoconfigure and autowire on TRUE in your services.yml
Doctrine will handle your subscriber registration (check DoctrineExtension.php)
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
->addTag('doctrine.event_subscriber');

Symfony route access check

I have a website made with Symfony 3.4 and within my actions I must check if the current user can edit the target product, something like this:
/**
* #Route("/products/{id}/edit")
*/
public function editAction(Request $request, Product $product)
{
// security
$user = $this->getUser();
if ($user != $product->getUser()) {
throw $this->createAccessDeniedException();
}
// ...
}
How can I avoid making the same check on every action (bonus points if using annotations and expressions)?
I am already using security.yml with access_control to deny access based on roles.
You can use Voters for this exact purpose. No magic involved. After creating and registering the Voter authentication will be done automatically in the security layer.
You just have to create the Voter class and then register it as a service. But if you're using the default services.yaml configuration, registering it as a service is done automatically for you!
Here is an example you can use. You may have to change a few items but this is basically it.
To read more visit: https://symfony.com/doc/current/security/voters.html
<?php
namespace AppBundle\Security;
use AppBundle\Entity\Product;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use AppBundle\Entity\User;
class ProductVoter extends Voter
{
const EDIT = 'EDIT_USER_PRODUCT';
protected function supports($attribute, $subject)
{
if($attribute !== self::EDIT) {
return false;
}
if(!$subject instanceof Product) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
/** #var Product $product */
$product= $subject;
$user = $token->getUser();
if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
}
return $this->belongsToUser($product, $user);
}
private function belongsToUser(Product $product, User $user)
{
return $user->getId() === $product->getUser()->getId();
}
}
You could try with a listener:
Check the action name,for example, if it is "edit_product", them continue.
Get the current logged User.
Get the user of the product entity.
Check if current user is different to Product user, if it is true, throw CreateAccessDeniedException.
services.yml
app.user.listener:
class: AppBundle\EventListener\ValidateUserListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
arguments: ["#service_container", "#doctrine.orm.entity_manager"]
Edit Action:
Added name "edit_product" to the action.
/**
*
* #Route("/products/{id}/edit",name="edit_product")
*/
public function editAction()
{
...
src\AppBundle\EventListener\ValidateUserListener.php
<?php
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class ValidateUserListener
{
private $container;
private $entityManager;
public function __construct($container, $entityManager)
{
$this->container = $container;
$this->entityManager = $entityManager;
}
public function onKernelRequest(GetResponseEvent $event)
{
$currentRoute = $event->getRequest()->attributes->get('_route');
if($currentRoute=='edit_product' || $currentRoute=='edit_item' )
{
$array_user = $this->getCurrentUser();
if($array_user['is_auth'])
{
$current_user = $array_user['current_user'];
$product = $this->entityManager->getRepository('AppBundle:User')->findOneByUsername($current_user);
$product_user = $product->getUsername();
if ($current_user !==$product_user)
{
throw $this->createAccessDeniedException();
}
}
}
}
private function getCurrentUser()
{
//Get the current logged User
$user = $this->container->get('security.token_storage')->getToken()->getUser();
if(null!=$user)
{
//If user is authenticated
$isauth = $this->container->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY');
return array('is_auth'=>$isauth, 'current_user'=>$user);
}
return array('is_auth'=>false, 'current_user'=>$user);
}
}
Tested in Symfony 3.3

Creating an entity while updating another

I have one question I don't seem to find an answer to.
I have my User entity with a "Status" field.
What I want to do is store in another table "StatusEvent" a new line each time the status of a user is changed to keep track of the history of statuses of my users.
I tried to work with the PreUpdate method but it doesn't allow the creation of new Entities in this step.
I was maybe thinking that it might be possible with other events (onFlush maybe?) but these do not have the methods of the LifecycleEventArgs from PreUpdate (which allows to know if a field has been changed).
Anyone has already came across a same pattern or has an idea on how I could implement it?
Thanks by advance,
This is a nice case to use a custom event and listener.
Create a class UserEvents to hold a constant with the event name like
class UserEvents
{
const STATUS_CHANGED = 'user.status.changed';
}
Create a UserStatusChangedEvent that extends Event and takes the user as a parameter.
class UserChangedEvent extends Event
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function getUser(): User
{
return $this->user;
}
}
And then create and register a listener to capture/handle that event and create the entry that you need using the data from the user object that was passed in the event when it was dispatched.
class UserListener
{
public function onStatusChanged(UserChangedEvent $event)
{
$user = $event->getUser();
//TODO: Create your new status change entry. If you need the entity manager, just inject it in the constructor, like with any other service
}
}
You then need to register you listener as a service and tag it
AppBundle\Event\Listener\UserListener:
tags:
- { name: kernel.event_listener, event: user.status.changed, method: onStatusChanged }
And now all you have to do is dispatch a new instance of the event every time the status changes, passing it the user that you just persisted.
$eventDispatcher->dispatch(
UserEvents::STATUS_CHANGED,
$user
);
Edit: To defend the manual dispatching of the custom event VS the automated dispatch of onFlush, the custom event code is far easier to read even from a newbie that has no knowledge of how/when doctrine lifecycle events are triggered or how the entity manager works internally. The cherry at the top is that the dispatching works as a nice reminder that you have a listener there, which will be useful when you revisit your code in a few months.
The solution by #Dimitris would work, but requires you to dispatch the event manually.
I would use the onFlush method like you mentioned. (If you are writing a library, you are better off with the custom event)
You can use UnitOfWork to get the change sets.
public function onFlush(OnFlushEventArgs $event)
{
$em = $event->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
$this->newEntities[] = $entity;
if ($entity instanceof User) {
$changeSet = $uow->getEntityChangeSet($entity);
// if the $changeSet contains the status, log the change
$log = new Log();
$em->persist($log);
$uow->computeChangeSet($em->getClassMetadata(Log::class), $log);
}
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
// same here, create a private method to avoid duplication
}
}
The trade off of this listener is it will only log things on flush.
If your entity changes state multiple times before flush, only the last state will be logged. Eg state1 -> 2 -> 3 will only be logged as state1 -> 3
If you plan on creating a complex status field with many states and transitions have a look at the workflow component and use the listeners from there. It is a bit more work, but well worth it.
So what I did following the advice of both Dimitris and Padam67.
Define a DoctrineListener that listens on the onFlush event and register it
Dispatch a custom event in the DoctrineListener
Define an EventSubscriber listening on my custom event
Define a handler to manage the logic
Call the handler from the EventSubscriber
I know it makes a lot of files, but I like to separate everything as much as possible for a cleaner and simpler code to read :)
Define a DoctrineListener that listens on the onFlush event:
config/services.yaml
App\EventListener\Doctrine\DoctrineListener:
tags:
- { name: doctrine.event_listener, event: onFlush }
App\EventListener\Doctrine\DoctrineListener.php
<?php
namespace App\EventListener\Doctrine;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\UnitOfWork;
use App\Event\TalentStatusChangedEvent;
use App\Entity\Talent;
use App\Event\Constants\TalentEvents;
class DoctrineListener
{
private $logger;
private $dispatcher;
public function __construct(
LoggerInterface $logger,
EventDispatcherInterface $dispatcher
) {
$this->logger = $logger;
$this->dispatcher = $dispatcher;
}
public function onFlush(OnFlushEventArgs $event)
{
$entityManager = $event->getEntityManager();
$uow = $entityManager->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof Talent) {
$this->createTalentStatusChangedEvent($entity, $uow);
}
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof Talent) {
$this->createTalentStatusChangedEvent($entity, $uow);
}
}
}
private function createTalentStatusChangedEvent(Talent $entity, UnitOfWork $uow)
{
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - start');
$changeSet = $uow->getEntityChangeSet($entity);
if (array_key_exists('status', $changeSet)) {
$talentStatusChangedEvent = new TalentStatusChangedEvent($entity, new \DateTime());
$this->dispatcher->dispatch(TalentEvents::STATUS_CHANGED, $talentStatusChangedEvent);
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - success');
} else {
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - fail');
}
}
}
Define a TalentStatusChangedEvent
App\Event\TalentStatusChangedEvent.php
<?php
namespace App\Event;
use App\Entity\Talent;
use Symfony\Component\EventDispatcher\Event;
class TalentStatusChangedEvent extends Event
{
private $talent;
private $statusChangedDate;
public function __construct(Talent $talent, \DateTime $date)
{
$this->talent = $talent;
$this->statusChangedDate = $date;
}
public function getTalent()
{
return $this->talent;
}
public function getStatus()
{
return $this->talent->getStatus();
}
public function getStatusChangedDate()
{
return $this->statusChangedDate;
}
}
 Define an EventSubscriber for my event (defined a separate file containing all my events per type)
App\EventListener\Admin\User\TalentSubscriber.php
<?php
namespace App\EventListener\Admin\User;
use App\Domain\User\StatusChanged\StatusChangedHandler;
use App\Entity\Talent;
use App\Event\Constants\TalentEvents;
use App\Event\TalentStatusChangedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class TalentSubscriber implements EventSubscriberInterface
{
private $statusChangedHandler;
public function __construct(
StatusChangedHandler $statusChangedHandler
) {
$this->statusChangedHandler = $statusChangedHandler;
}
public static function getSubscribedEvents()
{
return array(
TalentEvents::STATUS_CHANGED => 'statusChanged',
);
}
public function statusChanged(TalentStatusChangedEvent $event) {
$this->statusChangedHandler->handle($event);
}
}
 Define a handler to actually manage the creation of the linked entity
App\Domain\User\StatusChanged.php
<?php
namespace App\Domain\User\StatusChanged;
use App\Entity\Talent;
use App\Entity\TalentStatusEvent;
use App\Event\TalentStatusChangedEvent;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
class StatusChangedHandler
{
private $entityManager;
private $logger;
public function __construct(
EntityManagerInterface $entityManager,
LoggerInterface $logger
) {
$this->entityManager = $entityManager;
$this->logger = $logger;
}
public function handle(TalentStatusChangedEvent $event)
{
$this->logger->info(self::class . ' - Talent ' . $event->getTalent() . ' - start');
$talentStatusEvent = new TalentStatusEvent();
$talentStatusEvent->setTalent($event->getTalent());
$talentStatusEvent->setStatus($event->getStatus());
$this->entityManager->persist($talentStatusEvent);
// Calling ComputeChangeSet and not flush because we are during the onFlush cycle
$this->entityManager->getUnitOfWork()->computeChangeSet(
$this->entityManager->getClassMetadata(TalentStatusEvent::class),
$talentStatusEvent
);
$this->logger->info(self::class . ' - Talent ' . $event->getTalent() . ' - success');
}
}

Symfony2 custom Voter: cannot have access to getDoctrine from inside the Voter

I'm trying to implement a custom Voter.
From the controller I call it this way:
$prj = $this->getDoctrine()->getRepository('AppBundle:Project')->findOneById($id);
if (false === $this->get('security.authorization_checker')->isGranted('responsible', $prj)) {
throw new AccessDeniedException('Unauthorised access!');
}
The first line properly retrieves the Project object (I checked with a dump).
The problem occurs inside the voter
<?php
namespace AppBundle\Security\Authorization\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class ProjectVoter implements VoterInterface
{
const RESPONSIBLE = 'responsible';
const ACCOUNTABLE = 'accountable';
const SUPPORT = 'support';
const CONSULTED = 'consulted';
const INFORMED = 'informed';
public function supportsAttribute($attribute)
{
return in_array($attribute, array(
self::RESPONSIBLE,
self::ACCOUNTABLE,
self::SUPPORT,
self::CONSULTED,
self::INFORMED,
));
}
public function supportsClass($class)
{
$supportedClass = 'AppBundle\Entity\Project';
return $supportedClass === $class || is_subclass_of($class, $supportedClass);
}
/**
* #var \AppBundle\Entity\Project $project
*/
public function vote(TokenInterface $token, $project, array $attributes)
{
// check if class of this object is supported by this voter
if (!$this->supportsClass(get_class($project))) {
return VoterInterface::ACCESS_ABSTAIN;
}
// check if the voter is used correct, only allow one attribute
// this isn't a requirement, it's just one easy way for you to
// design your voter
if (1 !== count($attributes)) {
throw new \InvalidArgumentException(
'Only one attribute is allowed'
); //in origin it was 'for VIEW or EDIT, which were the supported attributes
}
// set the attribute to check against
$attribute = $attributes[0];
// check if the given attribute is covered by this voter
if (!$this->supportsAttribute($attribute)) {
return VoterInterface::ACCESS_ABSTAIN;
}
// get current logged in user
$user = $token->getUser();
// make sure there is a user object (i.e. that the user is logged in)
if (!$user instanceof UserInterface) {
return VoterInterface::ACCESS_DENIED;
}
$em = $this->getDoctrine()->getManager();
$projects = $em->getRepository('AppBundle:Project')->findPrjByUserAndRole($user, $attribute);
foreach ($projects as $key => $prj) {
if ($prj['id'] === $project['id'])
{
$granted = true;
$index = $key; // save the index of the last time a specifif project changed status
}
}
if($projects[$index]['is_active']===true) //if the last status is active
return VoterInterface::ACCESS_GRANTED;
else
return VoterInterface::ACCESS_DENIED;
}
}
I get the following error
Attempted to call method "getDoctrine" on class
"AppBundle\Security\Authorization\Voter\ProjectVoter".
I understand that the controller extends Controller, that is why I can use "getDoctrine" there. How can I have access to my DB from inside the Voter?
I solved it. This is pretty curious: I spend hours or days on a problem, then post a question here, and I solve it myself within an hour :/
I needed to add the following in my voter class:
public function __construct(EntityManager $em)
{
$this->em = $em;
}
I needed to add the following on top:
use Doctrine\ORM\EntityManager;
I also needed to add the arguments in the service.yml
security.access.project_voter:
class: AppBundle\Security\Authorization\Voter\ProjectVoter
arguments: [ #doctrine.orm.entity_manager ]
public: false
tags:
- { name: security.voter }

Symfony2 Custom Error Exception Listener - Rendering templates or passing to a controller

I'm trying to work out the best way of handling custom error pages within Symfony2.This includes 500 and 404's etc.
I can create my own custom templates (error404.html.twig etc) and render these out fine, the issue is , the app requires a few variables be passed into the base template for the page to remain consistent. Using the built in exception handler results in required variables not being available.
I have successfully setup a custom Exception Event Listener, and registered it as a service:
namespace MyCo\MyBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class MyErrorExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
// We get the exception object from the received event
$exception = $event->getException();
if($exception->getStatusCode() == 404)
{
//$engine = $this->container->get('templating');
//$content = $engine->render('MyBundle:Default:error404.html.twig');
//return $response = new Response($content);
/* Also Tried */
//$templating = $this->container->get('templating');
//return $this->render('MyBundle:Default:index.html.twig');
$response = new Response($templating->render('MyBundle:Exception:error404.html.twig', array(
'exception' => $exception
)));
$event->setResponse($response);
}
}
}
This doesn't work , as :$container is not available , meaning I cannot render my custom page.
So two questions really , is this the correct way to handle custom error pages, or should I pass the response off to a controller? If so , whats the best way of doing that?
If this is correct , how can I make the templating engine available within my Listener ?
You should add into yours Listener
/**
*
* #var ContainerInterface
*/
private $container;
function __construct($container) {
$this->container = $container;
}
How do you register your Listener?
You should register Listener like Service
Like that
core.exceptlistener:
class: %core.exceptlistener.class%
arguments: [#service_container]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: 200 }
The best way is don't use service_container.
The best way is register only necessary services.
Like that
/**
*
* #var Twig_Environment
*/
private $twig;
function __construct($twig) {
$this->twig = $twig;
}
The way I did to solve similar issue is: I dont know if it can help you out.
const GENERIC_CODE = 550;
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof RedirectableException) {
$request = $event->getRequest();
$url = $exception->getUrl($this->_authentication['base']['url']);
$response = new Response();
$response->setStatusCode(self::GENERIC_CODE, AuthenticationException::GENERIC_MESSAGE);
$response->setContent($url);
$event->setResponse($response);
}
}

Resources