Symfony - using twig trans filter on 404 page - symfony

I have a 404 error page set up through an event listener triggered by Kernel exceptions:
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($event->isMasterRequest()) {
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
$response = new Response();
$event->setResponse(
$response->setContent($this->templating->render(
'LandingPageBundle:Error:error404.html.twig',
['welcome_url' => $this->router->generate("welcome")]
))
);
}
}
}
kernel.kernel_exception_listener:
class: S\Project\LandingPageBundle\EventListener\KernelExceptionListener
arguments: [ "#router", "#logger", "#translator", #templating ]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
This works, but when I try to use the trans filter on the twig error404.html.twig template, it does nothing. My locale is being set in a cookie and read by an event listener to Kernel Requests, so I tried adding the following to onKernelException:
$request = $event->getRequest();
$locale = $request->cookies->get('_locale');
$request->setLocale($locale);
After that, including {{ app.request.locale }} in the template displayed the correct locale, however, the trans filter does not seem to be picking this up.
It seems like my problem may be related to Symfony 2.1 set locale and yet that question does not quite fit my exact problem, and I'm not sure what can be done to fix the problem. Ideally my kernel request listener could trigger before the onKernelException, so that it would properly set the locale beforehand, but currently it seems that the kernel request event is not triggered during a 404. I took a look at http://symfony.com/doc/current/components/http_kernel/introduction.html to better understand Symfony's request sequence, but I'm not really clear on the sequence that happens in a bad request, but it looks like in the case of an exception, it skips most of the request flow and goes straight to the response, and as I recall from http://symfony.com/doc/current/book/translation.html#handling-the-user-s-locale
"Setting the locale using $request->setLocale() in the controller is too late to affect the translator. Either set the locale via a listener (like above), the URL (see next) or call setLocale() directly on the translator service."
Is there a way to use the trans filter on a twig templated 404 page?

Try to inject the #translator and use its method setLocale.
['welcome_url' => $this->router->generate("welcome")]
And why did you create link in the listener? You should do it in the template using twig function called path.

Related

Throwing Exception in an EventSubscriber prevent ApiPlatform from serializing it so i only get standard symfony error page

i'm trying to do some process in an EventSubscriber listening to the request. To make it simple i want to check a header and refuse the request if i don't find it. It's almost like a Guard, but quite simpler.
In that step i might throw an Exception. And it works, except that i would like apiPlatform to manage the response and serialize it.
I have already configure ApiPlatform with packages/api_platform.yaml
api_platform:
exception_to_status:
Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException: 403
My EventSubscriber is configured like this in services.yaml
services:
app.security.api_subscriber:
class: App\Security\ApiAccessSubscriber
arguments:
- "%api.authorized.keys%"
calls:
- [setClient, ["#common-sentry-api"]]
- [setClientPerUser, ["#common-sentry-peruser"]]
My Subscriber describe itself like this:
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['onKernelRequest', EventPriorities::POST_WRITE],
];
}
What am i doing wrong ?

Symfony Tactician-bundle Typehints = Missing handler method error

I've used the thephpleague/tactician-bundle with Symfony before, but this is the first time I've used it with Symfony 4.* (specifically 4.1.4) and attempted to use a single handler Class for my Application Service.
When I execute a command in the Controller
public function postAction(Request $request, CommandBus $commandBus)
{
$form = $this->createForm(VenueType::class);
$form->submit($request->request->all(), true);
$data = $form->getData();
if($form->isValid()) {
$command = new CreateVenueCommand($data);
$commandBus->handle($command);
return $form->getData();
}
return $form;
}
... I get the following error:
"error": {
"code": 500,
"message": "Internal Server Error",
"exception": [
{
"message": "Could not invoke handler for command App\\Application\\Command\\CreateVenueCommand for reason: Method 'handle' does not exist on handler",
"class": "League\\Tactician\\Exception\\CanNotInvokeHandlerException",
"trace": [
I've seemingly followed the installation documents for the tactician-bundle and installed it using Flex. As far as I can tell everything is configured correctly, so I'm unsure what I'm missing in my implementation.
Implementation
As per the thephpleague/tactician-bundle installation guide I've installed using Flex and the bundle is registered and the config package installed:
tactician:
commandbus:
default:
middleware:
- tactician.middleware.locking
- tactician.middleware.doctrine
- tactician.middleware.command_handler
After creating the DTO Command Class 'CreateVenueCommand', I created the handler Class:
use App\Infrastructure\Domain\Model\VenueRepositoryInterface;
use App\Application\Command\CreateVenueCommand;
use App\Domain\Entity\Venue;
class VenueApplicationService
{
private $venueRepository;
public function __construct(VenueRepositoryInterface $venueRepository)
{
$this->venueRepository = $venueRepository;
}
/**
* #param CreateVenueCommand $aCommand
* #throws \Exception
*/
public function createVenue(CreateVenueCommand $aCommand)
{
$aVenue = new Venue($aCommand->getData())
if ($aVenue === null) {
throw new \LogicException('Venue not created');
}
$this->venueRepository->add($aVenue);
}
Then I registered the handler Class as a Service taking advantage of Symfony's autowiring and Tacticians typehints:
App\Application\VenueApplicationService:
arguments:
- '#App\Infrastructure\Persistence\Doctrine\DoctrineVenueRepository'
tags:
- { name: tactician.handler, typehints: true }
So according to the installation documents, typehints work if:
The method must be public.
The method must accept only one parameter.
The parameter must be typehinted with a class name.
Also, and this is specific to my use case:
If you have multiple commands going into a single handler, they will all be detected, provided they follow the rules above. The actual name of the method is NOT important.
So when I invoke the commandbus in the Controller Class, I'm unsure why I'm getting the error above.
If I change the Command Handler method to:
public function handle(CreateVenueCommand $aCommand)
{
... then it works fine. This would seem to suggest that the typehints aren't working as documented.
It seems in this case that the actual name of the method IS important. ... or I've made some form of error in my implementation ... or I'm misunderstanding the multiple commands going into a single handler use case??
Any assistance would be greatly appreciated.
Solution
With a big thanks to kunicmarko20 for pointing me in the right direction.
Specifically for my use case I simply needed to use one of Tacticians MethodNameInflector classes, configured in Symfony thus:
tactician:
commandbus:
default:
middleware:
- tactician.middleware.locking
- tactician.middleware.doctrine
- tactician.middleware.command_handler
method_inflector: tactician.handler.method_name_inflector.handle_class_name
... then it was simply a matter of naming each Handler method in my Application Service class 'handle{whateverYouLike}Command
Here under 1. is explained how the naming works, if you want to use a different name than in this table you can implement MethodNameInflector Interface and provide a name of the method.

Symfony Routing "_locale" parameter not accepted

I have a problem with the symfony routing.
For an multilanguage projekt I render a Twig template via a job queue for mailing. In this template is a link to a route that requires the "_locale" parameter with "de" or "en" for example. I use the function "{{ url('route', {'_locale': 'de'}) }}" to generate the url.
By rendering the template, I got the following error message:
[Twig_Error_Runtime]
An exception has been thrown during the rendering of a template ("Some mandatory parameters are missing ("_locale") to generate a URL for route "Route".") in "TemplatePath" at line 5.
Whats my mistake?
Thanks for help
When you are generating URL route, via CLI, the Symfony kernel is missing a HTTP Request, and a Routing->RequestContext.
Thats why, the URL generator cannot find _locale parameter.
To fix it, you must manually create a RequestContext, so, in your command:
$this->getContainer()->get('router')
->setContext(
(new RequestContext())
->setParameter('_locale', 'fr')
);
class BaseCommand extends ContainerAwareCommand
{
protected function getLocale()
{
return $this->getContainer()->get('translator')->getLocale();
}
protected function render($view, $data)
{
return $this->getContainer()->get('templating')->render($view, $data);
}
}
in command
class SomeCommand extends BaseCommand
{
...
$this->render($view, array_merge($data, ['_locale' => $this->getLocale()])
}
in view
{{url('any', {param: 'foo'}|merge(_locale is defined ? {'_locale': _locale } : {}))}}
It's necro time :)
I'm just guessing here, but there's a couple things you can try.
Can you set the locale in your command?
Set the locale for the translator bundle:
$this->getContainer()->get('translator')->setLocale('de');
Set the locale for this session:
$this->getContainer()->get('session')->setLocale('de);
Internationalized routing for Symfony 4.1 and higher
If this applies to you, try this:
url('route.de', {'_locale': 'de'}) }}"
Does your route have 'de' and 'en' set as a requirement?
/**
* Matches /route
* #Route(
* "/route/{_locale}",
* requirements={
* '_locale': 'en|de'
* },
* name="route"
* )
*/
It's an old question but this might help someone out.

Override a symfony service tag with a compiler pass

I'm trying to override a tag in a symfony service definition with a compiler pass. The service as an example would be data_collector.translation.
The goal is to deactivate the data collector service to disable the element in the symfony web developer toolbar. To do this, I have to set the priority of the data_collector tag to 0.
I could also override it in my own service definition:
services:
data_collector.translation:
class: 'Symfony\Component\Translation\DataCollector\TranslationDataCollector'
tags:
- {name: 'data_collector', priority: '0'}
arguments: [#translator.data_collector]
But as I want to do this for a few of the data collectors, I would need to know the mandatory arguments for the data collector definition. The priority works the same for all collectors and therefore I would only need the name of the collector to disable it.
So I wrote the following compiler pass:
class DataCollectorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('data_collector.translation')) {
return;
}
$definition = $container->getDefinition('data_collector.translation');
$tags = $definition->getTags();
$tags['data_collector'][0]['priority'] = 0;
$definition->setTags($tags);
$container->setDefinition('data_collector.translation', $definition);
}
}
To make things more wired: When I run this command:
$ php app/console container:debug --show-private --tag='data_collector'
I get the following output:
data_collector.translation #WebProfiler/Collector/translation.html.twig translation 0 Symfony\Component\Translation\DataCollector\TranslationDataCollector
So the priority even in the debugger is set to 0.
But for which reason ever the element is still shown in the toolbar.
What did I do wrong here? Is there another mechanism for overwriting a tag within a compiler pass?
The compiler pass does run (tested it with printing out stuff)
I'm using Symfony 2.7.1
Turns out the code does work, the only problem is, that the CompilerPass is run after the ProfilerPass which is part of the FrameworkBundle. Putting my bundle with the CompilerPass before the FrameworkBundle in the AppKernel solves the problem (more information here). For not even initiating the data collectors it's better to remove all tags instead of just setting the priority to 0.
That's what the final solution looks like:
class DataCollectorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$collectorsToRemove = [
'data_collector.form',
'data_collector.translation',
'data_collector.logger',
'data_collector.ajax',
'data_collector.twig'
];
foreach($collectorsToRemove as $dataCollector) {
if (!$container->hasDefinition($dataCollector)) {
continue;
}
$definition = $container->getDefinition($dataCollector);
$definition->clearTags();
}
}
}
Can you try this?
if (!$container->hasDefinition('data_collector.form')) {
return;
}
$definition = $container->getDefinition('data_collector.form');
$definition->clearTags();
$container->setDefinition('data_collector.form', $definition);
Why not use your compiler pass to manipulate directly the service Definition of the service holding all these collectors ?
If I look at the compiler pass responsible for loading the data collector, it seems that they are all injected using a method call injection.
You could use your compiler pass to rewrite the method call array using methods like setMethodCalls, removeMethodCall, ... of the Definition entity.
The method call manipulation documentation : link

Symfony2; domain/host based info in controller and base template

I'm building an symfony2 app that is configurable up to some point based on what domain is used to access the site.
For ease of this question, lets say there is an "Domain" entity in the database containing the hostname and further configuration.
Think about minor template differences, some differences in header/footer. A difference in products being offered.
The routes available would not be different.
There are 2 places where I need this Domain object.
* in a Controller::action
* in a base template (even if the controller didn't need it)
I would not need it somewhere else, if I did, I could simply pass it from the controller.
What would be the best way to get this object without creating too much overhead and not fetching it when we don't actually need it.
Some thoughts I got so far:
* I could override the ControllerResolver and determine the Domain object based on the Request object. Although I don't seem to have access to the ServiceContainer there.
* I could add some method to a BaseController that can retrieve the domain for me when I'm in a Controller:Action.
* For usage in the template I could create a TwigExtension that adds a global variable. But it would need access to the Request object or RequestStack. Also, this would only help me in the template, I might be doing the same thing twice.
Any suggestions what might be a good approach here?
Don't know if this is the best solution, but worked well for me so far.
Since the domain information depends on the request it is NOT a service, so don't try to inject it in services or you'll get a bad headache. The most natural place to set information about the domain is in the request, and allow the controllers to read this information to interact with the services.
So, you can setup a Kernel event listeners which read the information from the database and set a domain Request attribute, like this:
<?php
namespace Acme\SiteBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Doctrine\ORM\EntityRepository;
class DomainSubscriber implements EventSubscriberInterface
{
protected $domainRepository;
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => 'onKernelRequest'
);
}
public function __construct(EntityRepository $domainRepository)
{
$this->domainRepository = $domainRepository;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
// Console/CLI commands don't have Domain info
if ($request === null)
return;
$domain = $this->domainRepository->find($request->getHost());
if ($domain === null)
throw new \RuntimeException(sprintf("Cannot find domain %s", $request->getHost()));
$request->attributes->set('domain', $domain);
}
}
Which must be registered in services.yml (or XML) with:
acme_site.manager:
class: Doctrine\ORM\EntityManager
factory_service: doctrine
factory_method: getManager
acme_site.domain_repository:
class: Doctrine\ORM\EntityRepository
factory_service: acme_site.manager
factory_method: getRepository
arguments:
- 'AcmeSiteBundle:Domain'
acme_site.domain_subscriber:
class: Acme\SiteBundle\EventListener\DomainSubscriber
arguments:
- "#acme_site.domain_repository"
tags:
- { name: kernel.event_subscriber }
In your Controller you can now access the data by simply doing this:
public function someAction(Request $request) {
$domain = $request->attributes->get('domain');
$domain->getWhatever();
}
And in Twig you can always access the request with this:
{% set domain = app.request.attributes.get('domain') %}
whatever: {{ domain.whatever }}
Hope this help!
DISCLAIMER: the code is copy-pasted and then edited, so it may contain some minor error.
NOTE: If you really need to inject the request in services, then I suggest you to read the docs about the RequestStack (Symfony 2.4+), or use a setRequest method and take care of container scopes.

Resources