Vich UploadedFile, FOSUserBundle and Events - symfony

I added a VichImageType field on edit profile twig template...so im trying to check image dimensions using vich_uploader.pre_upload as Event.
In my class i got the image properties and if their dimensions arent bigger enough i tried to stop propagation and flashed a message to the twig template but, i dont know why, the event keeps propagating and redirects to fos_user_profile_show showing the image setup. Also, i tried to redirect again to fos_user_profile_edit but i cant use $event because "Vich\UploaderBundle\Event\Event" doesnt implement setController(). How can achieve this?
This is the method of the Listener class:
namespace BackendBundle\EventListener;
use Vich\UploaderBundle\Event\Event;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
class ComprobarDimensionesDeImagen
{
private $requestStack;
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router)
{
$this->requestStack = $requestStack;
$this->router = $router;
}
public function onVichUploaderPreUpload(Event $event)
{
$object = $event->getObject();
$mapping = $event->getMapping();
$imagen = getimagesize($object->getImageFile());
if (250 > $imagen[0] || 250 > imagen[1]) {
$request = $this->requestStack->getCurrentRequest();
$session = $request->getSession();
$event->stopPropagation();
$session->getFlashBag()->add('error', "Minimum dimensions: 250x250 \n");
$url = $this->router->generate('fos_user_profile_edit');
/*
* Testing different methods of redirect
*
* $response = new RedirectResponse($url);
* $event->setResponse($response);
*/
$event->setController(function() use ($request) {
return new RedirectResponse($url);
});
}
}
}
When i edit the profile again, I can see the flash message and the image setup in VichImageType field (i didnt expect that stopping propagation). Any help will be very welcome.
SOLVED: Just using #Assert\Image in my Entity class did the validation. No service neither listener needed

The argument for ->setController must be a callable . In your case, the function you pass as an argument returns an object of type Response. In order to be callable, the method should have the suffix Action. See also this post.

Related

Trying to use notifier in an event Subscriber

I'm learnig the event Subscriber on Symfony, my idea is when I create a new blog post, I want to send a notification. I'm already able to get the last entity created.
I've added to code to get the Symfony notifier and be able to send a notification on Disocrd. I currently have the following code :
class EasyAdminSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
AfterEntityPersistedEvent::class => ['setBlogPostSlug'],
];
}
/**
* #param AfterEntityPersistedEvent $event
* #param ChatterInterface $chatter
* #return void
* #throws \Symfony\Component\Notifier\Exception\TransportExceptionInterface
*/
public function setBlogPostSlug(AfterEntityPersistedEvent $event)
{
$chatter = new Chatter();
$entity = $event->getEntityInstance();
if (!($entity instanceof Blog)) {
return;
}
$message = (new ChatMessage('You got a new invoice for 15 EUR.'));
// if not set explicitly, the message is send to the
// default transport (the first one configured)
$chatter->send($message);
}
}
But when I run the code I have the followinf error :
App\EventSubscriber\EasyAdminSubscriber::setBlogPostSlug(): Argument #2 ($chatter) must be of type Symfony\Component\Notifier\ChatterInterface, string given
I don't understand what's wrong. Do you any idea why ? And how to make things work ?

cannot binding a kernel.view EventListener to FOSUserBundle

I separated mobile and web requests with the help of kernel.view Event Listener.
The logic works like this:
if request is coming from mobile, then load xxx.mobile.twig
if request is coming from web, then load xxx.html.twig
This is working with my CustomBundle without any problem. In addition to it I'm using FOSUserBundle and HWIOAuthBundle with some of their routes. I checked var/logs/dev.log and I can't see kernel.view events regarding these bundles routes and eventually my listener cannot work with these bundles.
Could you give me an idea how could I bind to the kernel.view event for those bundles?
/**
* #param GetResponseForControllerResultEvent $event
* #return bool
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
if (!$this->isMobileRequest($event->getRequest()->headers->get('user-agent'))) {
return false;
}
$template = $event->getRequest()->attributes->get('_template');
if (!$template) {
return false;
}
$templateReference = $this->templateNameParser->parse($template);
if ($templateReference->get('format') == 'html' && $templateReference->get('bundle') == 'CustomBundle') {
$mobileTemplate = sprintf(
'%s:%s:%s.mobile.twig',
$templateReference->get('bundle'),
$templateReference->get('controller'),
$templateReference->get('name')
);
if ($this->templating->exists($mobileTemplate)) {
$templateReference->set('format', 'mobile');
$event->getRequest()->attributes->set('_template', $templateReference);
}
}
}
There are a few things you should consider when debugging event related issues on Symfony2.
Events propagation can be stopped
it could be that another listener is listening for the very same event and is stopping the event propagation by calling $event->stopPropagation(). In that case make sure your listener is executed first (see point 2 below).
Event listeners have priorities
When defining a listener you can set its priority like shown below:
view_response_listener:
class: AppBundle\EventListener\ViewResponseListener
tags:
# The highest the priority, the earlier a listener is executed
# #see http://symfony.com/doc/2.7/cookbook/event_dispatcher/event_listener.html#creating-an-event-listener
- { name: kernel.event_listener, event: kernel.view, method: onKernelView, priority: 101 }
The other optional tag attribute is called priority, which defaults to 0 and it controls the order in which listeners are executed (the highest the priority, the earlier a listener is executed). This is useful when you need to guarantee that one listener is executed before another. The priorities of the internal Symfony listeners usually range from -255 to 255 but your own listeners can use any positive or negative integer.
Source: http://symfony.com/doc/2.7/cookbook/event_dispatcher/event_listener.html#creating-an-event-listener
Usually the dispatch of those events is done in the bootstrap file
Make sure regenerate your bootstrap file (and clear the cache now that you're at it!), especially if you're playing with priorities and/or your configuration.
composer run-script post-update-cmd
php app/console cache:clear --env=dev
The composer post-update-cmd will regenerate your bootstrap file but it will also do other things like reinstalling your assets which is probably something that you don't need. To just regenerate the bootstrap file check my answer here.
I find the solution, it is however a bit workaround, working properly now.
I put following function to my MobileTemplateListener.php file.
More details are here -> http://www.99bugs.com/handling-mobile-template-switching-in-symfony2/
/**
* #param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($this->isMobileRequest($request->headers->get('user-agent')))
{
//ONLY AFFECT HTML REQUESTS
//THIS ENSURES THAT YOUR JSON REQUESTS TO E.G. REST API, DO NOT GET SERVED TEXT/HTML CONTENT-TYPE
if ($request->getRequestFormat() == "html")
{
$request->setRequestFormat('mobile');
}
}
}
/**
* Returns true if request is from mobile device, otherwise false
* #return boolean mobileUA
*/
private function isMobileRequest($userAgent)
{
if (preg_match('/(android|blackberry|iphone|ipad|phone|playbook|mobile)/i', $userAgent)) {
return true;
}
return false;
}
as a result when kernel.request event listener starts to handling, it is setting the format with value mobile
I was using FOSUserBundle through a child bundle to manipulate for my needs. You may find more details here -> https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/overriding_controllers.md
for instance : we assume SecurityController.php
I created a file named SecurityController.php under my UserBundle It looks like following.
<?php
namespace Acme\UserBundle\Controller;
use Symfony\Component\HttpFoundation\RedirectResponse;
use FOS\UserBundle\Controller\SecurityController as BaseController;
use Symfony\Component\HttpFoundation\Request;
use Acme\UserBundle\Overrides\ControllerOverrideRenderTrait;
class SecurityController extends BaseController
{
use ControllerOverrideRenderTrait;
public function loginAction(Request $request)
{
$securityContext = $this->container->get('security.context');
$router = $this->container->get('router');
if ($securityContext->isGranted('IS_AUTHENTICATED_FULLY')) {
return new RedirectResponse($router->generate('my_profile_dashboard'), 307);
}
return parent::loginAction($request);
}
}
for all other FOS controllers I have to override render function. Which is provided by smyfony Symfony\Bundle\FrameworkBundle\Controller
But already my child bundle extends the FOSUserBundle's controllers, the only way to override this without duplicates of code is to use traits.
and I created one trait as following.
<?php
namespace Acme\UserBundle\Overrides;
use Symfony\Component\HttpFoundation\Response;
trait ControllerOverrideRenderTrait {
/**
* This overrides vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php
* Renders a view.
*
* #param string $view The view name
* #param array $parameters An array of parameters to pass to the view
* #param Response $response A response instance
*
* #return Response A Response instance
*/
public function render($view, array $parameters = array(), Response $response = null)
{
$format = $this->getRequest()->getRequestFormat();
$view = str_replace('.html.', '.' . $format . '.', $view);
return $this->container->get('templating')->renderResponse($view, $parameters, $response);
}
}
The Original function provided by symfony is the following.
/**
* Renders a view.
*
* #param string $view The view name
* #param array $parameters An array of parameters to pass to the view
* #param Response $response A response instance
*
* #return Response A Response instance
*/
public function render($view, array $parameters = array(), Response $response = null)
{
return $this->container->get('templating')->renderResponse($view, $parameters, $response);
}
Basically my change replaces '.html.' part in template name by providing $format which setted via onKernelRequest EventListener.
don't forget to add your service definition in services.yml
services:
acme.frontend.listener.mobile_template_listener:
class: Acme\FrontendBundle\EventListener\MobileTemplateListener
arguments: ['#templating', '#templating.name_parser']
tags:
- { name: kernel.event_listener, event: kernel.view, method: onKernelView }
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

Symfony2 and FOSUserBundle: performing other operations when updating/creating a user

I'm using FOSUserBundle on Symfony2.
I extended the User class to have additional fields, therefore I also added the new fields in the twigs.
One of those fields is a licence code. When a user fills in that field I want to perform a connection to DB to look if that license is valid. If not returns an error, if yes creates an event in the "licenceEvents" table assigning the current user to that license.
[EDIT] As suggested I created a custom validator (which works like a charm), and I'm now struggling with the persisting something on DB once the user is created or updated.
I created an event listener as follows:
<?php
// src/AppBundle/EventListener/UpdateOrCreateProfileSuccessListener.php
namespace AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Doctrine\ORM\EntityManager; //added
class UpdateOrCreateProfileSuccessListener implements EventSubscriberInterface
{
private $router;
public function __construct(UrlGeneratorInterface $router, EntityManager $em)
{
$this->router = $router;
$this->em = $em; //added
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => array('onUserCreatedorUpdated',-10),
FOSUserEvents::PROFILE_EDIT_COMPLETED => array('onUserCreatedorUpdated',-10),
);
}
public function onUserCreatedorUpdated(FilterUserResponseEvent $event)
{
$user = $event->getUser();
$code = $user->getLicense();
$em = $this->em;
$lastEvent = $em->getRepository('AppBundle:LicenseEvent')->getLastEvent($code);
$licenseEvent = new LicenseEvent();
// here I set all the fields accordingly, persist and flush
$url = $this->router->generate('fos_user_profile_show');
$event->setResponse(new RedirectResponse($url));
}
}
My service is like follows:
my_user.UpdateOrCreateProfileSuccess_Listener:
class: AppBundle\EventListener\UpdateOrCreateProfileSuccessListener
arguments: [#router, #doctrine.orm.entity_manager]
tags:
- { name: kernel.event_subscriber }
The listener is properly triggered, manages to create the connection to DB as expected, but gives me the following error
Catchable Fatal Error: Argument 1 passed to AppBundle\EventListener\UpdateOrCreateProfileSuccessListener::onUserCreatedorUpdated()
must be an instance of AppBundle\EventListener\FilterUserResponseEvent,
instance of FOS\UserBundle\Event\FilterUserResponseEvent given
I must be missing something very stupid...
Another question is: I don't want to change the redirect page, so that if the original page was the "email sent" (after a new user is created) let's go there, otherwise if it's a profile update show the profile page.

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);
}
}

Listener on Bundle call

My point is to call a generic function in all my controllers in some Bundle (let's say AdminBundle). I got a login listener in whitch I set a session that contain true or false and I need to check this session before every method of my AdminBundle.
So I tried to make a __construct() function in my AdminBundle controllers, but it appears that I can't access to a service from this method (because the container is not yet loaded so I can't access $this).
The best practice should be to use a listener to call this service before very method of those controllers but I can't figure out whitch listener I need to use (cannot find a clue on google...).
Hope in clear enough, don't hesitate to ask questions if you don't understand my point !
Thanks in advance for any solution/idea (if you think that I'm not using the correct way to do it please explain my your point of view !)
After the afternoon on this issue i finally get a solution thanks to mahok.
For those whitch have the same issue here's my controller listener :
<?php
namespace Site\MyBundle\Listener;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ControllerListener
{
protected $container;
protected $router;
public function __construct(ContainerInterface $container, Router $router)
{
$this->router = $router;
$this->container = $container;
}
public function onKernelController(FilterControllerEvent $event)
{
if (HttpKernel::MASTER_REQUEST == $event->getRequestType())
{
$controller = $event->getController();
$controller = $controller[0];
$new = new \ReflectionObject($controller);
if($new->getNamespaceName() == 'Site\MyBundle\Controller')
{
$test = $this->container->get('myservice')->test();
if(empty($test) || !$test)
{
$index = $this->router->generate('index');
$event->setController(function() use($index) {
return new RedirectResponse($index);
});
}
}
}
}
}
So basically it compare the namespace of your current controller's action with another and if true i check some variable to know if the user can be here or not.
Thanks again mahok you show me the way !

Resources