I activated user confirmation for FOSUserBundle. But I don't want to take the response from the original listener
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
I want to chose another route. I tried to extend the EventListener
namespace Acme\MyBundle\EventListener;
use FOS\UserBundle\EventListener\EmailConfirmationListener as BaseListener;
// ...
class EmailConfirmationListener extends BaseListener
{
public function onRegistrationSuccess(FormEvent $event)
{
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
}
}
Unfortunately, EventListeners don't seem to be extendable, just as Controllers or Forms are. (Just in case you wonder: of course my bundle is a child of the FOSUserBundle.)
So I want to avoid editing those two lines directly in the vendor folder (as it would be very bad practice to do so!). So what are my ways out of this calamity?
Just override the service fos_user.listener.email_confirmation by creating a service with the same name in your config.yml ...
# app/config/config.yml
services:
fos_user.listener.email_confirmation:
class: "Acme\MyBundle\EventListener\EmailConfirmationListener"
arguments: ["#fos_user.mailer", "#fos_user.util.token_generator", "#router", "#session"]
tags:
- { name: kernel.event_subscriber }
... or even cleaner - create a parameter that's being used by your service:
parameters:
my.funky_parameter.class: "Acme\MyBundle\EventListener\EmailConfirmationListener"
services:
fos_user.listener.email_confirmation:
class: "%my.funky_parameter.class%"
# ...
... or inside your bundle's xml/yml/php configuration file loaded by the bundle's extension. Make sure your bundle is being registered after FOSUserBundle in AppKernel.php when choosing this way.
... or the best method:
change the original service's class name in a compiler pass as the documentation chapter How to Override any Part of a Bundle suggests.
Maybe take a dive into the chapter How to work with Compiler Passes before choosing this option.
Related
I need, for each action in my controller, check if these actions are called by an ajax request or not.
If yes, nothing append, if no, i need to redirect to the home page.
I have just find if($this->getRequest()->isXmlHttpRequest()), but i need to add this verification on each action..
Do you know a better way ?
It's very easy!
Just add $request variable to your method as use. (For each controller)
<?php
namespace YOUR\Bundle\Namespace
use Symfony\Component\HttpFoundation\Request;
class SliderController extends Controller
{
public function someAction(Request $request)
{
if($request->isXmlHttpRequest()) {
// Do something...
} else {
return $this->redirect($this->generateUrl('your_route'));
}
}
}
If you want to do that automatically, you have to define a kernel request listener.
For a reusable technique, I use the following from the base template
{# app/Resources/views/layout.html.twig #}
{% extends app.request.xmlHttpRequest
? '::ajax-layout.html.twig'
: '::full-layout.html.twig' %}
So all your templates extending layout.html.twig can automatically be stripped of all your standard markup when originated from Ajax.
Source
First of all, note that getRequest() is deprecated, so get the request through an argument in your action methods.
If you dont want to polute your controller class with the additional code, a solution is to write an event listener which is a service.
You can define it like this:
services:
acme.request.listener:
class: Acme\Bundle\NewBundle\EventListener\RequestListener
arguments: [#request_stack]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onRequestAction }
Then in the RequestListener class, make a onRequestAction() method and inject request stack through the constrcutor. Inside onRequestAction(), you can get controller name like this:
$this->requestStack->getCurrentRequest()->get('_controller');
It will return the controller name and action (I think they are separated by :). Parse the string and check if it is the right controller. And if it is, also check it is XmlHttpRequest like this:
$this->requestStack->getCurrentRequest()->isXmlHttpRequest();
If it is not, you can redirect/forward.
Also note, that this will be checked upon every single request. If you check those things directly in one of your controllers, you will have a more light-weight solution.
Our project allows the users to create custom theme files. Because of the way the project is structured, we have to render these files and save the output. The project uses Symfony2.
In order to render this Twig template and save the output to another file, I use a service which takes #templating as its argument.
The services.yml defines the service like this:
theme_renderer:
class: ApiBundle\Service\ThemeRenderer
arguments: [ #templating ]
This gives me a TwigEngine object that I can use like this:
public function __construct(TwigEngine $templateEngine) {
$this->templateEngine = $templateEngine;
}
public function renderTheme($filePath, $themeSettings) {
...
$renderedFileContents = $this->templateEngine->render($sourceFilePath, $themeSettings);
...
}
This works fine.
However, if the template contains an {{ asset('images', 'some_image') }} tag, the rendering fails. The exception is:
An exception has been thrown during the rendering of a template ("There is no "some_image" asset package.") in "<template_file_path_here>" at line 2
I need to be able to give a custom (CDN actually) URL for the rendering of the asset() tag. How would I go about doing this?
I think exception message There is no "some_image" asset package. is clear. Second argument of asset function is package name. Packages for templating component are defined under framework.templating key in Symfony configuration (etc. config.yml).
http://symfony.com/doc/current/reference/configuration/framework.html#packages
# app/config/config.yml
framework:
# ...
templating:
packages:
avatars:
base_urls: 'http://static_cdn.example.com/avatars'
Then you can use asset('my_image.jpg', 'avatars'). So, I suppose you have not defined package some_image or configured it improperly.
I need, for each action in my controller, check if these actions are called by an ajax request or not.
If yes, nothing append, if no, i need to redirect to the home page.
I have just find if($this->getRequest()->isXmlHttpRequest()), but i need to add this verification on each action..
Do you know a better way ?
It's very easy!
Just add $request variable to your method as use. (For each controller)
<?php
namespace YOUR\Bundle\Namespace
use Symfony\Component\HttpFoundation\Request;
class SliderController extends Controller
{
public function someAction(Request $request)
{
if($request->isXmlHttpRequest()) {
// Do something...
} else {
return $this->redirect($this->generateUrl('your_route'));
}
}
}
If you want to do that automatically, you have to define a kernel request listener.
For a reusable technique, I use the following from the base template
{# app/Resources/views/layout.html.twig #}
{% extends app.request.xmlHttpRequest
? '::ajax-layout.html.twig'
: '::full-layout.html.twig' %}
So all your templates extending layout.html.twig can automatically be stripped of all your standard markup when originated from Ajax.
Source
First of all, note that getRequest() is deprecated, so get the request through an argument in your action methods.
If you dont want to polute your controller class with the additional code, a solution is to write an event listener which is a service.
You can define it like this:
services:
acme.request.listener:
class: Acme\Bundle\NewBundle\EventListener\RequestListener
arguments: [#request_stack]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onRequestAction }
Then in the RequestListener class, make a onRequestAction() method and inject request stack through the constrcutor. Inside onRequestAction(), you can get controller name like this:
$this->requestStack->getCurrentRequest()->get('_controller');
It will return the controller name and action (I think they are separated by :). Parse the string and check if it is the right controller. And if it is, also check it is XmlHttpRequest like this:
$this->requestStack->getCurrentRequest()->isXmlHttpRequest();
If it is not, you can redirect/forward.
Also note, that this will be checked upon every single request. If you check those things directly in one of your controllers, you will have a more light-weight solution.
I would like to render different views in different context in my Symfony2 project.
I'm using multiple routes for the same actions and I would like to render a different page (view) but with the same controller.
For example I have:
#Route("/articles/show", name="articles_show")
#Route("/mobile/articles/show", name="mobile_articles_show")
Both routes are using the same action : ArticlesController:showAction(), but should render 2 differents templates (for mobile users and regulars ones).
show.html.twig
mobile.show.html.twig
I do not want to use a if statement or whatever in my controller, so I created a listener (similar to a preExecute function)
Here is a part or my config.yml that defines my listener
services:
controller.pre_execute_listener:
class: MyProject\MyBundle\Listener\ControllerListener
arguments: ["#security.context", "#doctrine", "#router", "#session"]
tags:- { name: kernel.event_listener, event: kernel.controller, method: preExecute }
I was thinking about doing something like that in the listener preExecute function:
if(substr($route,0,7) == 'mobile_'){
$view = 'mobile.'.$view;
}
Unfortunately I cannot find a way to get $view or update the view "on the fly", just before it's rendered.
I hope my question is clear enough, thanks in advance, any idea is welcome :)
J.
Here is the solution:
First I have to listen to kernel.view, not kernel.controller.
Then I use the "#templating" service (Thanks Marko Jovanovic for the hint)
So here is my new config.yml:
services:
controller.pre_execute_listener:
class: MyProject\MyBundle\Listener\ControllerListener
arguments: ["#templating"]
tags:
- { name: kernel.event_listener, event: kernel.view, method: preExecute }
Finally here is my listener preExecute function
public function preExecute(\Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event){
//result returned by the controller
$data = $event->getControllerResult();
/* #var $request \Symfony\Component\HttpFoundation\Request */
$request = $event->getRequest();
$template = $request->get('_template');
$route = $request->get('_route');
if(substr($route,0,7) == 'mobile_'){
$newTemplate = str_replace('html.twig','mobile.html.twig',$template);
//Overwrite original template with the mobile one
$response = $this->templating->renderResponse($newTemplate, $data);
$event->setResponse($response);
}
}
Hope this helps!
J.
Worth noting: The accepted solution doesn't actually work if you directly return a Response-object (e.g. when you call $this->render()), because the kernel.view event is not fired in that case:
If the controller doesn't return a Response object, then the kernel dispatches another event - kernel.view.
— see Symfony's HTTP Kernel Docs
I couldn't work out a way around this, but found another interesting solution for the same problem:
You could simply extend twig's render engine like the ZenstruckMobileBundle does or write your own file locator like the LiipThemeBundle.
[edit:] Alternatively you could also override the TemplateNameParser.
You can add "#templating" service as argument for the controller.pre_execute_listener.
It seems your device detection is done before you come to your route, so I bet you expect that mobile user will use the mobile routes thanks to some detection before the request, this seems to be painful to deal with in every templates and url generation.
May be better to detect device either before or later (thanks to Categorizr or some nice apache configuration) but not relying on used route for the mobile detection.
An integration of Categorizr with that way of calling templates rendering might be nice.
Then using a nice bundle for using the right templates/themes or using one which provides some more generic functions
I would like to have a method available to all Views in my app.
I would like to be able to make calls like this:
<span>${ getDynamicText() }</span>
The most obvious ways (to me) to implement this are:
Call the method in the controller and and pass it to the View.
Make the method static on some Util class and call it from the code ${ UtilClass.getDynamicText() }
Using meta programming to somehow make the method available to all Views.
The benefit of #3 is that the change would only have to be made in one place. #1 would have to be made in each controller action; and #2 would need an import on every View page which wants to use the method.
So is there a way to add a method to be available to all views in my app?
I have to admit I don't know in a lot of detail how .gsp files are processed behind-the-scenes so maybe they don't have a corresponding class and therefore can't be manipulated in this way. Links to good articles/docs will get extra good karma.
GSPs are compiled into classes that extend org.codehaus.groovy.grails.web.pages.GroovyPage, so you can add methods to that metaclass and they'll be available to all GSPs. The best place to do this is in BootStrap.groovy (or in a plugin's doWithDynamicMethods closure):
import org.codehaus.groovy.grails.web.pages.GroovyPage
class BootStrap {
def init = { servletContext ->
GroovyPage.metaClass.getDynamicText = { ... }
}
}
The recommended way to reuse functionality across GSPs is to define it as a tag, e.g.
class MyTagLib {
static namespace = 'my'
def dynamicText = {attrs ->
out << 'not very dynamic'
}
}
You can then call this tag in a GSP using:
<my:dynamicText/>
4th way: make a class/service that have method '.getDynamicText' and put it's intance into request at before filter ( request.setAttribute('x', myDynamicTextGeneratorObject) )
Now you can use x.dynamicText in any GSP
This is how I would do it:
Add a new class to your controllers folder containing your method
Do a grails install-templates
Navigate to the templates: \src\templatesscaffolding
Add the extends part to the controller template: class ${className}Controller extends NewController
re-generate your controllers
You can now use the method in every class and gsp.