How do I reduce the severity of NotFoundHttpException? - symfony

I want to be alerted when bad things happen in my Symfony2 app. Right now I just look for ERROR in the logs. Unfortunately, "HTTP 404 - file not found" (NotFoundHttpException) gets logged as an error, as does "HTTP 403 - forbidden" (AccessDeniedHttpException).
This doesn't warrant an error; at the most these should be warnings. How can I make these log at a less severe level?
Example error:
[2012-07-02 16:58:21] request.ERROR: Symfony\Component\HttpKernel\Exception\NotFoundHttpException: No route found for "GET /foo" (uncaught exception) at /home/user/Symfony2_v2.0.12/vendor/symfony/src/Symfony/Bundle/FrameworkBundle/EventListener/RouterListener.php line 83 [] []

I found something that works. The Symfony2 internals doc on the kernel.exeption event mention that a response can be set on the event, and the GetResponseForExceptionEvent docs say
The propagation of this event is stopped as soon as a
response is set.
I cobbled together a listener that appears to do just what I want:
<?php
namespace Acme\DemoBundle\Listener;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class ExceptionLoggingListener {
private $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
public function onKernelException(GetResponseForExceptionEvent $event) {
if(!$event) {
$this->logger->err("Unknown kernel.exception in ".__CLASS__);
return;
}
$notFoundException = '\Symfony\Component\HttpKernel\Exception\NotFoundHttpException';
$e = $event->getException();
$type = get_class($e);
if ($e instanceof $notFoundException) {
$this->logger->info($e->getMessage());
$response = new Response(Response::$statusTexts[404], 404);
$event->setResponse($response);
return;
}
$accessDeniedException = '\Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException';
if ($e instanceof $accessDeniedException) {
$this->logger->info($e->getMessage());
$response = new Response(Response::$statusTexts[403], 403);
$event->setResponse($response);
return;
}
$this->logger->err("kernel.exception of type $type. Message: '".$e->getMessage()."'\nFile: ".$e->getFile().", line ".$e->getLine()."\nTrace: ".$e->getTraceAsString());
}
}

Just add excluded_404s to your configuration:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_404s:
- ^/
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
See http://symfony.com/doc/current/logging/monolog_regex_based_excludes.html for a reference

Here's a way with less code :)
1. Extend Symfonys ExceptionListner class and override the logging method:
<?php
use Symfony\Component\HttpKernel\EventListener\ExceptionListener as BaseListener;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ExceptionListener extends BaseListener
{
/**
* Logs an exception.
*
* #param \Exception $exception The original \Exception instance
* #param string $message The error message to log
* #param Boolean $original False when the handling of the exception thrown another exception
*/
protected function logException(\Exception $exception, $message, $original = true)
{
$isCritical = !$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500;
if (null !== $this->logger) {
if ($isCritical) {
$this->logger->critical($message);
} else {
if ($exception instanceof NotFoundHttpException) {
$this->logger->info($message);
} else {
$this->logger->error($message);
}
}
} elseif (!$original || $isCritical) {
error_log($message);
}
}
}
2. Configure the twig.exception_listener.class parameter:
parameters:
twig.exception_listener.class: "MyBundle\EventListener\ExceptionListener"

You can also use error level activity strategy (actually Symfony's in-built 404 errors excluding is done using this, so I guess this is a proper way to do it).
config.yml
monolog:
handlers:
main:
type: fingers_crossed
handler: loggly
activation_strategy: 'mybundle.monolog.fingers_crossed.activation_strategy'
loggly:
type: loggly
token: %loggly_token%
level: error
tag: %loggly_tag%
services.yml (note that action level is set here, not in config.yml)
services:
mybundle.monolog.fingers_crossed.activation_strategy:
class: MyBundle\Handler\FingersCrossed\ErrorLevelActivationStrategy
arguments:
- '#request_stack'
- 'error'
ErrorLevelActivationStrategy.php
<?php
namespace MyBundle\Handler\FingersCrossed;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy as BaseErrorLevelActivationStrategy;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Activation strategy that ignores client errors (4xx)
*/
class ErrorLevelActivationStrategy extends BaseErrorLevelActivationStrategy
{
protected $requestStack;
public function __construct(RequestStack $requestStack, $actionLevel)
{
parent::__construct($actionLevel);
$this->requestStack = $requestStack;
}
/**
* {#inheritdoc}
*/
public function isHandlerActivated(array $record)
{
$isActivated = parent::isHandlerActivated($record);
if (
$isActivated
&& isset($record['context']['exception'])
&& $record['context']['exception'] instanceof HttpException
&& $record['context']['exception']->getStatusCode() >= 400
&& $record['context']['exception']->getStatusCode() <= 499
&& ($request = $this->requestStack->getMasterRequest())
) {
$isActivated = false;
}
return $isActivated;
}
}
https://gist.github.com/sobstel/d791d0347ee1f4e47b6e

Symfony 5 solution based on Tarjei's answer:
Create a new ErrorListener that overrides the Symfony ErrorListener.
App\EventListener\ErrorListener.php
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\HttpKernel\EventListener\ErrorListener as BaseListener;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ErrorListener extends BaseListener
{
/**
* Logs an exception.
*
* #param \Exception $exception The original \Exception instance
* #param string $message The error message to log
*/
protected function logException(\Throwable $exception, string $message): void
{
$isCritical = !$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500;
if (null !== $this->logger) {
if ($isCritical) {
$this->logger->critical($message);
} else {
if ($exception instanceof NotFoundHttpException) {
$this->logger->info($message);
} else {
$this->logger->error($message);
}
}
}
}
}
services.yaml
services:
App\EventListener\ErrorListener: '#exception_listener'
exception_listener:
autowire: false
class: App\EventListener\ErrorListener
arguments:
$controller: '%kernel.error_controller%'
$logger: '#logger'
$debug: '%kernel.debug%'

Preferred this in Symfony 3.3 instead of excluded_404s parameter.
app/config/services.yml
exception_listener:
class: AppBundle\Listener\ExceptionListener
arguments: ["#logger", "#templating"]
tags:
- { name: kernel.event_listener, event: kernel.exception}
src/AppBundle/Listener/ExceptionListener.php
<?php
namespace AppBundle\Listener;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Bridge\Monolog\Logger;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Templating\EngineInterface;
class ExceptionListener
{
private $logger;
private $templateEngine;
public function __construct(Logger $logger, EngineInterface $templateEngine)
{
$this->logger = $logger;
$this->templateEngine = $templateEngine;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException)
{
$this->logger->info($exception->getMessage());
// default twig error app/config/Resources/TwigBundle/views/Exception/error.html.twig
$response = $this->templateEngine->render('TwigBundle:Exception:error.html.twig');
$event->setResponse(new Response($response));
}
}
}

Related

Access to logger within an ExceptionListener in Symfony 4

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

Symfony2 kernel exception event not handling fatal error exception in production mode

I make a listener for exception handling. Below is my code
services.yml
kernel.listener.prod_exception_listener:
class: MyBundle\Listener\ExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
ExceptionListener.php
<?php
namespace MyBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
// no fatal exception goes here others are coming in this function
// like 403,404,500 are coming in this block
}
}
What additional work I need to do for fatal exceptions in production mode? Because in dev mode fatal errors are coming in listener.
I solved it the following way,
in my services.yml
api_exception_subscriber:
class: AppBundle\EventListener\ApiExceptionSubscriber
arguments: ['%kernel.debug%', '#api.response_factory', '#logger']
tags:
- { name: kernel.event_subscriber }
api.response_factory:
class: AppBundle\Api\ResponseFactory
my response factory look like:
<?php
namespace AppBundle\Api;
use Symfony\Component\HttpFoundation\JsonResponse;
class ResponseFactory
{
public function createResponse(ApiProblem $apiProblem)
{
$data = $apiProblem->toArray();
$response = new JsonResponse(
$data,
$apiProblem->getStatusCode()
);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
and the Api subscriper class
<?php
namespace AppBundle\EventListener;
use AppBundle\Api\ApiProblem;
use AppBundle\Api\ApiProblemException;
use AppBundle\Api\ResponseFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ApiExceptionSubscriber implements EventSubscriberInterface
{
private $debug;
private $responseFactory;
private $logger;
public function __construct($debug, ResponseFactory $responseFactory, LoggerInterface $logger)
{
$this->debug = $debug;
$this->responseFactory = $responseFactory;
$this->logger = $logger;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
// only reply to /api URLs
if (strpos($event->getRequest()->getPathInfo(), '/api') !== 0) {
return;
}
$e = $event->getException();
$statusCode = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500;
// allow 500 errors to be thrown
if ($this->debug && $statusCode >= 500) {
return;
}
$this->logException($e);
if ($e instanceof ApiProblemException) {
$apiProblem = $e->getApiProblem();
} else {
$apiProblem = new ApiProblem(
$statusCode
);
/*
* If it's an HttpException message (e.g. for 404, 403),
* we'll say as a rule that the exception message is safe
* for the client. Otherwise, it could be some sensitive
* low-level exception, which should *not* be exposed
*/
if ($e instanceof HttpExceptionInterface) {
$apiProblem->set('detail', $e->getMessage());
}
}
$response = $this->responseFactory->createResponse($apiProblem);
$event->setResponse($response);
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => 'onKernelException'
);
}
/**
* Adapted from the core Symfony exception handling in ExceptionListener
*
* #param \Exception $exception
*/
private function logException(\Exception $exception)
{
$message = sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine());
$isCritical = !$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500;
$context = array('exception' => $exception);
if ($isCritical) {
$this->logger->critical($message, $context);
} else {
$this->logger->error($message, $context);
}
}
}
Edit 2020: as of Symfony 5 this is no longer necessary
I have handled this by overriding Kernel::handle to call the ExceptionListener manually
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true): Response
{
try {
return parent::handle($request, $type, $catch);
} catch (\Exception $exception) {
throw new \Exception("There was an issue booting the framework");
} catch (\Throwable $throwable) {
$exception = new FatalThrowableError($throwable);
$event = new ExceptionEvent($this, $request, $type, $exception);
/** #var ExceptionListener $exceptionListener */
$exceptionListener = $this->container->get(ExceptionListener::class);
$exceptionListener->onKernelException($event);
return $event->getResponse();
}
}

Inject a flash on specific exception

I'm extending a custom exception for all my handled "application errors"
abstract class AbstractApplicationException extends \Exception
{
public function __construct(array $context = array())
{
$this->context = $context;
parent::__construct('exceptions.'.lcfirst(self::classname()));
}
}
And I use messages.xx.yml to describe the error to the user
exceptions:
incompatibleSettings: Vos réglages ne sont pas compatibles
I'd like to automatically inject a flash with the translated message on exception (of kind AbstractApplicationException) so I don't have to do it on all my controllers
public myControllerAction()
try {
$someService->someFunction();
} catch (AbstractApplicationException $e) {
$flashBag->add('error',
$this->get('translator')->trans(
$e->getMessage(), $e->getContext()
)
);
}
$this->render('related_template.html.twig');
}
I know how to redirect the user with a listener, but I actually want the user to land on the specific action reponse, only with a flash injected.
You can create an Exception listener:
# app/config/services.yml
services:
kernel.listener.your_listener_name:
class: AppBundle\EventListener\AcmeExceptionListener
arguments: [#router, #session]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
Then set flash message and redirect user to the action you want, which will render it:
class AcmeExceptionListener
{
/**
* #var UrlGeneratorInterface
*/
private $router;
/**
* #var SessionInterface
*/
private $session;
public function __construct(UrlGeneratorInterface $router, SessionInterface $session)
{
$this->router = $router;
$this->session = $session;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof YourExceptionType) {
$this->session->getFlashBag()->add('error', $exception->getMessage());
$event->setResponse(new RedirectResponse($this->router->generate('your_route')));
}
}
}
Be careful with this though: if during exception handling you again throw YourExceptionType, you will end up having infinite redirect loop.
I finally used a trait to be able to continue the execution of my controllers with the flash injected.
/**
* Class ControllerTrait
* #requirement The using class must be containerAware
*/
trait ControllerTrait {
public function injectExceptionFlash(AbstractApplicationException $e) {
if (!isset($this->container)) {
throw new \Exception(sprintf('You must containerAware to use %s',
__TRAIT__
));
}
$flashBag = $this->container->get('session')->getFlashBag();
$flashBag->add('error',
$this->container->get('translator')->trans(
$e->getMessage(), $e->getContext()
)
);
}
public function tryFlash($lambda) {
try {
$lambda();
} catch (AbstractApplicationException $e) {
$this->injectExceptionFlash($e);
}
}
}
Here is how I use it from my controllers
use ControllerTrait;
public myControllerAction()
$this->tryFlash(function () (use $someParam) {
$someService->someFunction($someParam);
});
$this->render('related_template.html.twig');
}
tryFlash() is a shortcut using a lambda function to do the try/catch/flash jobs
Don't hesitate to tell me if there is a bad practice somewhere

Symfony2 store user related things on every request

I want to store the last locale used by a user on every Request the user makes. I already created a Field in the Database and only need the best practice way without big modifications in every Controller.
Thank you all.
You could store your locale in session and use it with an event listener. And on user login set user locale in session.
-LocaleListener : Store the locale in session and user locale in session
-UserLocaleListener : On user login set user locale in session
<?php
namespace YourApp\YourBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest17(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
// if no explicit locale has been set on this request, use one from the session
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest17', 17)),
);
}
}
Second service:
<?php
namespace YourApp\YourBundle\EventListener;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class UserLocaleListener
{
private $session;
public function setSession(Session $session)
{
$this->session = $session;
}
/**
* kernel.request event. If a guest user doesn't have an opened session, locale is equal to
* "undefined" as configured by default in parameters.ini. If so, set as a locale the user's
* preferred language.
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function setLocaleForUnauthenticatedUser(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
if ('undefined' == $request->getLocale()) {
if ($locale = $request->getSession()->get('_locale')) {
$request->setLocale($locale);
} else {
$request->setLocale($request->getPreferredLanguage());
}
}
}
/**
* security.interactive_login event. If a user chose a language in preferences, it would be set,
* if not, a locale that was set by setLocaleForUnauthenticatedUser remains.
*
* #param \Symfony\Component\Security\Http\Event\InteractiveLoginEvent $event
*/
public function setLocaleForAuthenticatedUser(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if ($lang = $user->getLocale()) {
$event->getRequest()->setLocale($lang);
$this->session->set('_locale', $lang);
}
}
}
services.yml
services:
yourapp_your.locale_listener:
class: YourApp\YourBundle\EventListener\LocaleListener
arguments: ["%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }
yourapp_your.locale.interactive_login_listener:
class: YourApp\YourBundle\EventListener\UserLocaleListener
calls:
- [ setSession, [#session] ]
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: setLocaleForAuthenticatedUser }
This is using
https://github.com/symfony/symfony/blob/master/UPGRADE-2.1.md#simulate-old-behavior
Translations in Symfony 2.3 locale in request
Symfony2 locale detection: not considering _locale in session
symfony 2 set locale based on user preferences stored in DB
That is my implementation to save the locale on each request in the database.
It is necessary for me because i need the locale persisted for the newsletter.
LocaleListener:
namespace MyApp\MyBundle\EventListener;
use \Symfony\Component\HttpKernel\Event\GetResponseEvent;
use \Symfony\Component\HttpKernel\KernelEvents;
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
use \FOS\UserBundle\Model\UserManagerInterface;
use \Symfony\Component\Security\Core\SecurityContext;
use \MyApp\MyBundle\Entity\User;
class LocaleListener implements EventSubscriberInterface
{
private $userManager;
private $securityContext;
private $defaultLocale;
public function __construct(UserManagerInterface $userManager, SecurityContext $securityContext, $defaultLocale = 'en')
{
$this->userManager = $userManager;
$this->securityContext = $securityContext;
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest17(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
$locale = $request->attributes->get('_locale');
if ($locale !== null)
{
$request->getSession()->set('_locale', $locale);
}
else
{
// if no explicit locale has been set on this request, use one from the session
$locale = $request->getSession()->get('_locale', $this->defaultLocale);
$request->setLocale($locale);
}
// save last locale to the user if he is logged in
$user = $this->getUser();
if($user instanceof User)
{
$user->setDefaultLanguage($locale);
$this->userManager->updateUser($user);
}
}
private function getUser()
{
$token = $this->securityContext->getToken();
if($token !== null)
return $this->securityContext->getToken()->getUser();
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest17', 17)),
);
}
}
services.yml:
myApp_myBundle.locale_listener:
class: MyApp\MyBundle\EventListener\LocaleListener
arguments: [#fos_user.user_manager, #security.context, "%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }

How to get Request object inside a Twig Extension in Symfony?

How can one access the Request object inside Twig Extension?
namespace Acme\Bundle\Twig;
use Twig_SimpleFunction;
class MyClass extends \Twig_Extension
{
public function getFunctions()
{
return array(
new Twig_SimpleFunction('xyz', function($param) {
/// here
$request = $this->getRequestObject();
})
);
}
public function getName() {
return "xyz";
}
}
As requested in the comments, here's the prefered way of injecting a request into any service. It works with Symfony >= 2.4.
Injecting the request and putting our service in the request scope is no longer recommended. We should use the request stack instead.
namespace AppBundle\Twig;
use Symfony\Component\HttpFoundation\RequestStack;
class MyClass extends \Twig_Extension
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function getFunctions()
{
$requestStack = $this->requestStack;
return array(
new \Twig_SimpleFunction('xyz', function($param) use ($requestStack) {
$request = $requestStack->getCurrentRequest();
})
);
}
public function getName()
{
return "xyz";
}
}
app/config/services.yml
app.twig_extension:
class: AppBundle\Twig\MyExtension
arguments:
- '#request_stack'
tags:
- { name: twig.extension }
Docs:
the request stack API
the request stack announcement
Register your extension as a service and give it the container service:
# services.yml
services:
sybio.twig_extension:
class: %sybio.twig_extension.class%
arguments:
- #service_container
tags:
- { name: twig.extension, priority: 255 }
Then retrieve the container by your (twig extension) class constructor and then the request:
<?php
// Your class file:
// ...
class MyClass extends \Twig_Extension
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* #var Request
*/
protected $request;
/**
* Constructor
*
* #param ContainerInterface $container
*/
public function __construct($container)
{
$this->container = $container;
if ($this->container->isScopeActive('request')) {
$this->request = $this->container->get('request');
}
}
// ...
Note that testing the scope is usefull because there is no request when running console command, it avoids warnings.
That's it, you are able to use the request !
I would suggest setting 'needs_environment' => true for your Twig_SimpleFunction, which then will add \Twig_Environment as first argument of your function. Then in your function you can find the request like this:
$request = $twig->getGlobals()['app']->getRequest();
So the whole function will look like this:
...
public function getFunctions() {
return [
new \Twig_SimpleFunction('xyz', function(\Twig_Environment $env) {
$request = $twig->getGlobals()['app']->getRequest();
}, [
'needs_environment' => true,
]),
];
}
...

Resources