Search code examples
symfonyinheritancefosuserbundlesymfony4

How to override bundle templates and controllers (FOSUserBundle) without using bundle inheritance


In my Symfony 2.8 based project I use FOSUserBundle and extended it with a custom MyUserBundle using bundle inheritance.

When trying to update FOSUserBundle from 2.0.2 to 2.1.2 I came across several problems.

Since Bundle inheritance is deprecated in Symfony 3.4 and completely dropped in Symfony 4.0 I am now trying to achieve the same result without using bundle inheritance.

I found many information that shows, that bundle resources can be overridden by simply placing the files in the app/Resources/<BundleName>/.... Thus the FOSUserBundle templates could be overridden by placing them in app/Resources/FOSUserBundle/views/<template.html.twig>

While this will work, it does not deliver the same result as bundle inheritance. I can use my inherited bundle in different projects and thus reuse the same code over and over again. Using the app/Resources/<BundleName>/... solution would only work for the current project.

Question 1: How to override templates (and other resources) within a custom bundle which can be used in different projects rather than within a project specific app/Resources folder?

Question 2: How to override the behavior of controllers which to not offer event to to do so?

Currently I am using bundle inheritance to override the confirmedAction to send the user to a custom setup page rather than to the default page:

// Original FOSUserBundle code
public function confirmedAction(Request $request) {
    $user = $this->getUser();
    if (!is_object($user) || !$user instanceof UserInterface) {
        throw new AccessDeniedException('This user does not have access to this section.');
    }

    return $this->render('@FOSUser/Registration/confirmed.html.twig', array(
        'user' => $user,
        'targetUrl' => $this->getTargetUrlFromSession($request->getSession()),
    ));
}  


// MyUserBundle codes which should override the default behavior
public function confirmedAction(Request $request) {
    $user = $this->getUser();
    if (!is_object($user) || !$user instanceof UserInterface) {
        throw new AccessDeniedException('This user does not have access to this section.');
    }

    // Send user to setup-page
    return $this->redirectToRoute('app_user_setup');
}

How can I control the behavior in the same way and send the user to a setup page instead of to the default confirmation page without using bundle inheritance?

As far as I know FOSUserBundle does not provide an option to specify the confirmation target. Of course I could intercept the request to the confirmed-route using some request event listener and re-route it. But this would be quite hacky. Would this be the right solution to do this or is there a cleaner way to achieve the same?


Solution

  • Overriding resources within a bundle since Symfony 4.0 without bundle inheritance

    About Controllers

    Routes! is the way to achieve it now, "the first route found wins" so make sure to define the same route before the original one, with your custom controller. e.g.:

    <route id="fos_user_registration_confirmed" path="/confirmed" methods="GET">
        <default key="_controller">MyUserBundle\Controller\RegistrationController::confirmedAction</default>
    </route>
    

    Then, your routing file MyUserBundle/Resources/config/routing/registration.xml must be imported before the FOSUserBundle routing file. This way, your custom action will be executed instead of the original fos_user.registration.controller:confirmedAction.

    About Templates

    Again, "the first template found wins" so make sure your bundle views path is defined before the one in FOSUser Twig namespace. You can achieve it by creating a compiler pass:

    // MyUserBundle/DependencyInjection/Compiler/TwigPass.php
    
    $path = dirname(__DIR__, 2).'/Resources/views';
    
    $twigFilesystemLoaderDefinition = $container->findDefinition('twig.loader.native_filesystem');
    $twigFilesystemLoaderDefinition->addMethodCall('prependPath', array($path, 'FOSUser'));
    

    Check the Twig loader paths by running bin/console debug:twig, it should look like this:

      @FOSUser               - vendor/acme/my-user-bundle/Resources/views                                               
                             - vendor/friendsofsymfony/user-bundle/Resources/views 
    

    From here, you're able to override any template of FOSUserBundle like Registration/confirmed.html.twig when @FOSUser/Registration/confirmed.html.twig is called.