1

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);
        }
    }
}
FZE
  • 1,587
  • 12
  • 35

2 Answers2

2

There are a few things you should consider when debugging event related issues on Symfony2.

  1. 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).
  2. 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

  3. 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.

Community
  • 1
  • 1
Francesco Casula
  • 26,184
  • 15
  • 132
  • 131
0

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 }
FZE
  • 1,587
  • 12
  • 35