Constraint relative to Locale parameter in Symfony2 - php

I need to validate a form field against bad words dictionary (Array for example). So to do this I have to create a new Constraint + ConstraintValidator. It works great, the only problem I have is that I want to have different dictionaries for different locales.
Example:
namespace MyNameSpace\Category\MyFormBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ContainsNoBadWordsValidator extends ConstraintValidator
{
protected $badWordsEN = array('blabla');
protected $badWordsFR = array('otherblabla');
public function validate($value, Constraint $constraint)
{
if (in_array(strtolower($value), array_map('strtolower', $this->getBadWords()))) {
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
}
}
protected function getBadWords($locale = 'EN')
{
switch ($locale) {
case 'FR':
return $this->badWordsFR;
break;
default:
return $this->badWordsEN;
break;
}
}
}
So how do I pass the locale to Constraint? Or should I implement it differently?

The locale parameter is a member of the Request object.
However, the request object is not created all the time (eg. in a CLI application)
This solution allows you to decouple your validation from the request object, and let your validation to be easily unit-tested.
The LocaleHolder is a request-listener which will hold upon creation the %locale% parameter and then, switch to the Request locale when the event is triggered.
Note: The %locale% parameter is the default parameter defined in config.yml
Your validator must then get this LocaleHolder as a constructor parameter, in order to be aware of the current locale.
services.yml
Here, declare the two services you will need, the LocaleHolder and your validator.
services:
acme.locale_holder:
class: Acme\FooBundle\LocaleHolder
arguments:
- "%locale%"
tags:
-
name: kernel.event_listener
event: kernel.request
method: onKernelRequest
acme.validator.no_badwords:
class: Acme\FooBundle\Constraints\NoBadwordsValidator
arguments:
- #acme.locale_holder
tags:
-
name: validator.constraint_validator
alias: no_badwords
Acme\FooBundle\LocaleHolder
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class LocaleHolder
{
protected $locale;
public function __construct($default = 'EN')
{
$this->setLocale($default);
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$this->setLocale($request->getLocale());
}
public function getLocale()
{
return $this->locale;
}
public function setLocale($locale)
{
$this->locale = $locale;
}
}
Acme\FooBundle\Constraints
use Acme\FooBundle\LocaleHolder;
class ContainsNoBadwordsValidator extends ConstraintValidator
{
protected $holder;
public function __construct(LocaleHolder $holder)
{
$this->holder = $holder;
}
protected function getBadwords($locale = null)
{
$locale = $locale ?: $this->holder->getLocale();
// ...
}
}

Related

Symfony6 changing the controller manually using the "kernel.controller" event. How to inject the service container?

The application that I am building is not going to work in a traditional way. All the routes ar going to be stored in the database. And based on the route provided I need to get the correct controller and action to be executed.
As I understand this can be achieved using the "kernel.controller" event listener: https://symfony.com/doc/current/reference/events.html#kernel-controller
I am trying to use the docs provided, but the example here does not exacly show how to set up a new callable controller to be passed. And I have a problem here, because I dont know how to inject the service container to my newly called controller.
At first the setup:
services.yaml
parameters:
db_i18n.entity: App\Entity\Translation
developer: '%env(DEVELOPER)%'
category_directory: '%kernel.project_dir%/public/uploads/category'
temp_directory: '%kernel.project_dir%/public/uploads/temp'
product_directory: '%kernel.project_dir%/public/uploads/product'
app.supported_locales: 'lt|en|ru'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
App\Translation\DbLoader:
tags:
- { name: translation.loader, alias: db }
App\Extension\TwigExtension:
arguments:
- '#service_container'
tags:
- { name: twig.extension }
App\EventListener\RequestListener:
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onControllerRequest }
The listener:
RequestListener.php
<?php
namespace App\EventListener;
use App\Controller\Shop\HomepageController;
use App\Entity\SeoUrl;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
class RequestListener
{
public ManagerRegistry $doctrine;
public RequestStack $requestStack;
public function __construct(ManagerRegistry $doctrine, RequestStack $requestStack)
{
$this->doctrine = $doctrine;
$this->requestStack = $requestStack;
}
/**
* #throws Exception
*/
public function onControllerRequest(ControllerEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
if(str_contains($this->requestStack->getMainRequest()->getPathInfo(), '/admin')) {
return;
}
$em = $this->doctrine->getManager();
$pathInfo = $this->requestStack->getMainRequest()->getPathInfo();
;
$route = $em->getRepository(SeoUrl::class)->findOneBy(['keyword' => $pathInfo]);
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = new HomepageController();
$event->setController([$controller, $route->getAction()]);
break;
default:
break;
}
} else {
throw new Exception('Route not found');
}
}
}
So this is the most basic example. I get the route from the database, if it a "homepage" route, I create the new HomepageController and set the action. However I am missing the container interface that I dont know how to inject. I get this error:
Call to a member function has() on null
on line: vendor\symfony\framework-bundle\Controller\AbstractController.php:216
which is:
/**
* Returns a rendered view.
*/
protected function renderView(string $view, array $parameters = []): string
{
if (!$this->container->has('twig')) { // here
throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
}
return $this->container->get('twig')->render($view, $parameters);
}
The controller is as basic as it gets:
HomepageController.php
<?php
namespace App\Controller\Shop;
use App\Repository\CategoryRepository;
use App\Repository\Shop\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomepageController extends AbstractController
{
#[Route('/', name: 'index', methods: ['GET'])]
public function index(): Response
{
return $this->render('shop/index.html.twig', [
]);
}
}
So basically the container is not set. If I dump the $event->getController() I get this:
RequestListener.php on line 58:
array:2 [▼
0 => App\Controller\Shop\HomepageController {#417 ▼
#container: null
}
1 => "index"
]
I need to set the container by doing $controller->setContainer(), but what do I pass?
Do not inject the container, controllers are services too and manually instanciating them is preventing you from using constructor dependency injection. Use a service locator which contains only the controllers:
Declared in config/services.yaml:
# config/services.yaml
services:
App\EventListener\RequestListener:
arguments:
$serviceLocator: !tagged_locator { tag: 'controller.service_arguments' }
Then in the event listener, add the service locator argument and fetch the fully configured controllers from it:
# ...
use App\Controller\Shop\HomepageController;
use Symfony\Component\DependencyInjection\ServiceLocator;
class RequestListener
{
# ...
private ServiceLocator $serviceLocator;
public function __construct(
# ...
ServiceLocator $serviceLocator
) {
# ...
$this->serviceLocator = $serviceLocator;
}
public function onControllerRequest(ControllerEvent $event)
{
# ...
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = $this->serviceLocator->get(HomepageController::class);
# ...
break;
default:
break;
}
}
# ...
}
}
If you dump any controller you will see that the container is set. Same will go for additionnal service that you autowire from the constructor.

How to control access to actions without override checkAccess and hasAccess methods

From version 3.102.0 of SonataAdminBundle a lot of methods in AbstractAdmin are marked as final.
The most important (in my opinion) "checkAccess" and "hasAccess" methods are also marked as "final" and cannot be overwritten in Admin classes any more to handle access to actions on my own.
How to handle cases when I want restrict access to some actions based on state of object?
For example I have "Task" entity:
<?php
class Task
{
private ?int $id = null;
private ?string $name = null;
private bool $closed = false;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function isClosed(): bool
{
return $this->closed;
}
public function setClosed(bool $closed): self
{
$this->closed = $closed;
return $this;
}
}
I want to denied access to edit action if Task object is closed.
Before version 3.102, doing this was simple:
<?php
class TaskAdmin extends AbstractAdmin
{
protected function checkAccess($action, $object = null)
{
if ('edit' === $action && $object && $object->isClosed()) {
throw new AccessDenied('Access Denied to action edit because task is closed.');
}
parent::checkAccess($action, $object);
}
protected function hasAccess($action, $object = null)
{
if ('edit' === $action && $object && $object->isClosed()) {
return false;
}
return parent::hasAccess($action, $object);
}
}
Of course now I can't override these methods.
I thought about Voters but in this case is not perfect, because Sonata checks first if user have "Super admin role/roles". If not, then next is checked specific role (for example ROLE_ADMIN_TASK_TASK_EDIT in my case). So, user with super admin role will still be able to edit Task object even though it is closed.
Another idea was create Controller for this TaskAdmin and override "preEdit" method and check there if object is closed or not and denied access. This solution is also not perfect, because in many places in templates is fired "hasAccess" method to checks if some parts of UI should be visible or not (for example edit button), so the user will still see the edit button but will not be able to enter the edit action (prevents on controller level).
It would be perfect if there were methods for example "preCheckAccess" and "preHasAccess" that could be overwritten in Admin classes (if "checkAccess" and "hasAccess" methods must remain marked as final).
Any other ideas? Thanks for yours help.
The solution is to create and use custom SecurityHandler service for specific Admin class.
To solve my case, follow these steps:
Create custom SecurityHandler class:
// src/Security/Handler/TaskSecurityHandler.php
<?php
namespace App\Security\Handler;
use App\Entity\Task;
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
class TaskSecurityHandler extends SecurityHandlerInterface
{
private SecurityHandlerInterface $defaultSecurityHandler;
public function __construct(SecurityHandlerInterface $defaultSecurityHandler)
{
$this->defaultSecurityHandler = $defaultSecurityHandler;
}
public function isGranted(AdminInterface $admin, $attributes, ?object $object = null): bool
{
// Handle custom access logic
if (is_string($attributes) && 'EDIT' === $attributes && $object instanceof Task && $object->isClosed()) {
return false;
}
// Leave default access logic
return $this->defaultSecurityHandler->isGranted($admin, $attributes, $object);
}
public function getBaseRole(AdminInterface $admin): string
{
return '';
}
public function buildSecurityInformation(AdminInterface $admin): array
{
return [];
}
public function createObjectSecurity(AdminInterface $admin, object $object): void
{
}
public function deleteObjectSecurity(AdminInterface $admin, object $object): void
{
}
}
Register custom SecurityHandler class in services.yaml and inject default SecurityHandler service:
# config/services.yaml
services:
App\Security\Handler\TaskSecurityHandler:
arguments:
- '#sonata.admin.security.handler' #default SecurityHandler service configured in global configuration of SonataAdminBundle
Use security_handler tag to point to your custom SecurityHandler service
for specific Admin class:
# config/services.yaml
services:
# ...
app.admin.task:
class: App\Admin\TaskAdmin
arguments: [~, App\Entity\Task, ~]
tags:
- { name: sonata.admin, manager_type: orm, label: Task, security_handler: App\Security\Handler\TaskSecurityHandler }

Redirect to Another Symfony Route from FilterControllerEvent Listener

I am trying to set up a kernal.controller listener to redirect to another route when a function returns true. I have the route available to me but no way to set the controller from this route using $event->setController().
I'm getting the following error:
FatalThrowableError in FilterControllerEvent.php line 59:
Type error: Argument 1 passed to Symfony\Component\HttpKernel\Event\FilterControllerEvent::setController() must be callable, string given
Does anyone have suggestions on how I can complete this?
class BlockListener
{
public function onKernelController(FilterControllerEvent $event)
{
$block = $this->blockService->checkForBlock($user->getId());
if ($block instanceof Block) {
// $block-getRoute() is a standard Symfony route string. It doesn't work!
$event->setController($block->getRoute());
}
}
}
We were able to get it working by using a Lambda function. Thanks for the help!
if ($block instanceof Block) {
$redirectUrl = $this->router->generate($block->getRoute());
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
};
You can modify options below as you wish.
OPTION 1
Full details
LISTENER
namespace Application\BackendBundle\EventListener;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class KernelExceptionListener
{
private $router;
private $redirectRouter = 'application_frontend_default_index';
public function __construct(Router $router)
{
$this->router = $router;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
if ($event->getRequest()->get('_route') == $this->redirectRouter) {
return;
}
$url = $this->router->generate($this->redirectRouter);
$response = new RedirectResponse($url);
$event->setResponse($response);
}
}
}
SERVICE DEFINITION
services:
application_backend.event_listener.kernel_exception:
class: Application\BackendBundle\EventListener\KernelExceptionListener
arguments: [#router]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
OPTION 2
Full details
LISTENER
namespace Application\FrontendBundle\Listener;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class PlayerListener
{
public function onKernelController(FilterControllerEvent $event)
{
$message = 'Bye inanzzz';
$event->setController(
function() use ($message) {
return new Response($message, 400);
}
);
}
}
SERVICE DEFINITION
services:
application_frontend.listener.player:
class: Application\FrontendBundle\Listener\PlayerListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Try this instead:
$event->setController( $event->getController() );
I think it should work, but no guarantees.
This passes in the controller instead of a string, which is what your error indicates.

How to send var to view from event listener in symfony2?

I am trying to send var to view from event listener in symfony2 but I am stacked.
1) Is this possible?
2) What kernel event to use (kernel.view or kernel.request)?
3) Finally how to send some var to view?
My best guess is that I have to extend return from controller and then to let controller do his job.
I need to send some array of objects (entities).
I see several ways to handle this.
Adding a global variable from a kernel.request listener
The idea is to add a global variable straight after the kernel.request event.
services.yml
services:
class: Acme\FooBundle\Listener\MyListener
arguments:
- #twig
tags:
-
name: kernel.event_listener
event: kernel.request
method: onKernelRequest
MyListener
class MyListener
{
protected $twig;
public function __construct(\Twig_Environment $twig)
{
$this->twig = $twig;
}
public function onKernelRequest(GetResponseEvent $event)
{
$myVar = 'foo'; // Process data
$this->twig->addGlobal('myvar', $myVar);
}
}
You can now use it at any time by doing
{{ myvar }}
From a kernel.view listener
First, you need to understand when kernel.view is called. It's only called when the return of the controller is not an instance of Response object.
That said, doing
// Acme/FooBundle/FooController#fooAction
return $this->render();
returns a Response object, so kernel.view is not called.
Defining controllers
The idea is to make all controller returns an array of data, just like #Template requirements.
// Acme/FooBundle/FooController#fooAction
return array(
'template' => 'AcmeFooBundle:Foo:foo.html.twig',
'data' => array(
'entity' => $entity
)
);
Defining the service
Since you already have your service definition, you just need to add some requirements in your service declaration.
You need the #templating service to render the data.
You need to set itself as a kernel.view listener
// Acme/FooBundle/Resources/config/services.yml
services:
acme_foo.my_listener:
class: Acme\FooBundle\Listener\MyListener
arguments:
- #templating
tags:
-
name: kernel.event_listener
event: kernel.request
method: onKernelRequest
-
name: kernel.event_listener
event: kernel.view
method: onKernelView
Creating the service
// Acme/FooBundle/Listener/MyListener.php
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
class MyListener
{
protected $templating;
protected $myVar;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function getMyVar()
{
return $this->myVar;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->myVar = ""; // Process MyVar data
}
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$result = $event->getControllerResult();
if (null === $this->myVar || !isset($result['template']) || !isset($result['data'])) {
return;
}
$data = array_merge($result['data'], array('myvar' => $this->myVar));
$rendered = $this->templating->render($result['template'], $data);
$event->setResponse(new Response($rendered));
}
}
And there you are. The listener is creating a new response, adding your custom definition of myvar to any template rendered by him.
From a TWIG extension
An alternative is to create a TWIG extension. In the following example, I'm assuming the MyListener definition is the same as above.
Defining services
As per the documentation given above, you just have to create a simple extension class.
// Acme/FooBundle/Resources/config/services.yml
services:
acme_foo.my_listener:
class: Acme\FooBundle\Listener\MyListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
acme_foo.my_extension:
class: Acme\FooBundle\Extension\MyExtension
arguments:
- #acme_foo.my_listener
tags:
- { name: twig.extension }
Defining the service
Just like in documentation, we'll create a simple function.
// Acme/FooBundle/Extension/MyExtension.php
use Acme\FooBundle\Listener\MyListener;
class MyExtension extends \Twig_Extension
{
protected $listener;
public function __construct(MyListener $listener)
{
$this->listener = $listener;
}
public function getName()
{
return 'my_extension';
}
public function getFunctions()
{
return array(
'myvar' => new \Twig_Function_Method($this, 'getMyVar')
);
}
public function getMyVar()
{
return $this->listener->getMyVar();
}
}
Usage
Then you can use it in any view by doing
{{ myvar() }}
From a common controller
I don't like this idea, but this is an alternative. You just have to create a BaseController which will override the default render method.
// Acme/FooBundle/Controller/BaseController.php
abstract class BaseController extends Controller
{
public function render($view, array $parameters = array(), Response $response = null)
{
$parameters = array_merge(
$parameters,
array(
'myvar' => $this->get('my_listener')->getMyVar()
)
);
return parent::render($view, $parameters, $response);
}
}
There's an alternative method here that I've had to do. I wanted to get some data, run it through json_encode(), then add that as a JavaScript variable to the response. Here's what I ended up doing.
I'm subscribing to kernel.response:
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => 'onKernelResponse'
];
}
public function onKernelResponse(FilterResponseEvent $event)
{
/** -- SNIP -- Cutting out how I get my serialised data **/
$serialized = json_encode($data);
/** Shove it into the response as some JS at the bottom **/
$dom = new \DOMDocument;
libxml_use_internal_errors(true);
$dom->loadHTML($event->getResponse()->getContent());
libxml_use_internal_errors(false);
$node = $dom->createElement('script', "var data = $serialized;");
$dom->getElementsByTagName('body')->item(0)->appendChild($node);
$event->getResponse()->setContent($dom->saveHTML());
}
This is one way of doing it. Honestly, I don't like any of the methods described on this page. There should be a better way, but there isn't. This is what I'm using, though, and it works well. Just make sure you don't call your variable "data"; use something that won't be taken up elsewhere and preferably shove it in it's own (function() { } JS namespace.
I don't know how to pass variables directly to view, but you can change response object, with kernel.response event listener. Take a look how symfony debug toolbar injects bottom bar, you could use similar technique.
https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php#L106

change locale symfony 2.3

I just began with symfony
I'm trying to build a multilang website but I have a problem to change the locale
I read some posts and I read the documentation about this but the locale don't change, I try:
public function indexAction()
{
$this->get('session')->set('_locale', 'fr');
$request = $this->getRequest();
$locale = $request->getLocale();
return $this->render('PhoneMainBundle:Default:index.html.twig',array('locale'=>$locale));
}
but the value in $locale is always 'en' (my default locale)
I also try
public function indexAction()
{
$this->get('session')->set('_locale', 'fr');
$request = $this->getRequest();
$request->setLocale('fr');
$locale = $request->getLocale();
return $this->render('PhoneMainBundle:Default:index.html.twig',array('locale'=>$locale));
}
In this case $locale is fr but the translations are always from messages.en.yml
I'd like in a first time to detect the user locale using $_SERVER['HTTP_ACCEPT_LANGUAGE'], maybe using a listner on each page actualisation ?
and after I will create a route to change the locale
But I 'd like to find a way to change the locale.
Thanks for your help
Based on this and this answers.
LanguageListener.php:
<?php
namespace Acme\UserBundle\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 LanguageListener
{
private $session;
public function setSession(Session $session)
{
$this->session = $session;
}
public function setLocale(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
$request->setLocale($request->getPreferredLanguage(array('en', 'de')));
}
}
services.yml:
acme.language.kernel_request_listener:
class: Acme\UserBundle\EventListener\LanguageListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: setLocale }
About wrong locale detection in twig, there could be a lot of different causes. Search through the SO, you'll definitely find the answer. Make sure that your '_local' var is defined right, make sure that you put your languages files in the right place, etc. FInally, read again the last version of the documentation: http://symfony.com/doc/current/book/translation.html
I however added this to make it more dynamic
services.yml
services:
acme.language.kernel_request_listener:
class: Acme\UserBundle\EventListener\LanguageListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: setLocale }
arguments: [ #router, #service_container ]
LanguageListener.php:
<?php
namespace Acme\UserBundle\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 LanguageListener
{
private $session;
private $container;
private $router;
public function __construct($router, $container)
{
// ...
$this->router= $router;
$this->container = $container;
}
public function setSession(Session $session)
{
$this->session = $session;
}
public function setLocale(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
$request->setLocale($request->getPreferredLanguage($this->container->parameters['jms_i18n_routing.locales']));
}
}
Just to be able to get the parameters and values from config.yml.
Regards,
Wick
If for example your default locale is french, except for one controller you want to have the default locale set to english can do that:
routing.yml
desktop_comingsoonpage:
resource: "#RemmelComparabusBundle/Controller/ComingsoonpageController.php"
defaults: { _locale: en }
type: annotation
more info : Symfony doc

Categories