Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change Symfony 3 controllers folder

I'm working on a project with Symfony 3 and I want to make my AppBundle folder be like this:

  • AppBundle/
    • Domain/
    • Application/
    • Infrasctructure/
    • UserInterface/
      • Controller/
        • DefaultController/

I want to put the "Controller" folder inside the UserInterface layer. The problem is that the Symfony magic method to load the controllers is searching them on "AppBundle/Controller" path.

Is there any option to specify the controller path on my routing.yml file like this?

app.home_page:
  path: /test
  defaults: {_controller: AppBundle:UserInterface\Default:indexAction}
like image 797
Ijuhash Avatar asked Dec 01 '16 13:12

Ijuhash


2 Answers

You could also refer to this controller using its fully-qualified class name and method (Ref.):

app.home_page:
    path: /test
    defaults: { _controller: AppBundle\UserInterface\Controller\DefaultController::indexA‌​ction }
like image 80
yceruto Avatar answered Sep 21 '22 05:09

yceruto


@Yonel's answer is indeed the simplest solution to this sort of question. However, the question of how to adjust the controller resolver does come up every so often. Been awhile since I messed with overriding a framework service so I decided to go through the steps in Symfony 3.2.

The goal is to be able to define a route like:

// routing.yml
app.home_page:
  path: /test
  defaults: {_controller: AppBundle:UserInterface\Controller:Default:indexAction}

Notice that there are four :'s in _controller. The idea is that the second argument will be used to determine the controller directory inside the given bundle.

To implement this, we need to override the default ControllerNameParser. Searching through FrameworkBundle\Resources\config we find the service is defined in web.xml with an id of controller_name_converter. Back in the good ole days, all the framework classes had associated parameter values. We could actually modify a service by simply supplying a new parameter value for the class name. Nowadays, we need to modify the actual service definition for which we need a compiler pass. The wiring:

// AppBundle.php
namespace AppBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use AppBundle\DependencyInjection\Compiler\CustomPass;

class AppBundle extends Bundle
{
    // http://symfony.com/doc/current/service_container/compiler_passes.html
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $container->addCompilerPass(new CustomPass());
    }
}

// CustomPass.php
namespace AppBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

// http://symfony.com/doc/current/bundles/override.html#services-configuration
class CustomPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $definition = $container->getDefinition('controller_name_converter');
        $definition->setClass('AppBundle\Controller\ControllerNameParser');
        $definition->addArgument('SomeParameter');
    }
}

Note that I added an additional argument to the name parser just to show how it could be done. So you could actually use some parameter values to indicate where to search for controllers.

The actual implementation of the custom name parser is a bit messy mostly because of the nature of the task. It could certainly be refined but basically any controller strings with four colons are processed.

namespace AppBundle\Controller;

use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser as ControllerNameParserBase;

class ControllerNameParser extends ControllerNameParserBase
{
    public function __construct(KernelInterface $kernel, $someParameter)
    {
        return parent::__construct($kernel);
    }

    public function parse($controllerString)
    {
        $parts = explode(':', $controllerString);
        if (4 !== count($parts)) {
            return parent::parse($controllerString);
        }
        list($bundleName, $controllerDir, $controller, $action) = $parts;
        $controller = str_replace('/', '\\', $controller);

        $bundle = $this->kernel->getBundle($bundleName, false)[0];

        $try = $bundle->getNamespace().'\\'.$controllerDir.'\\'.$controller.'Controller';

        if (class_exists($try)) {
            return $try.'::'.$action;
        }
        return parent::parse($controllerString);
    }
}

So even though there are easier ways to answer this particular question, if you really need to adjust how the resolver works then this answer can perhaps serve as a reference.

like image 21
Cerad Avatar answered Sep 20 '22 05:09

Cerad