Symfony, OroCRM, and Views

The concept of a "view" is a little tricky in Symfony, which makes it a little tricky in OroCRM/OroBAP.

As of Symfony 2.4 (the latest version) and 2.3 (the latest version used with OroCRM), there are two primary ways for a controller action method to hand off to a view. You can tell your controller to render a specific view identifier, or you can tell your controller to render the view it thinks it should render, (you can also simply return a raw Response object, but that's off topic for this post)

To tell your controller to render a specific view, just have your controller action return the results of the render function.

#File: vendor/oro/platform/src/Oro/Bundle/DashboardBundle/Controller/DashboardController.php
public function indexAction($name = null)
{    
    return $this->render(
        'OroDashboardBundle:Index:default.html.twig',
        [
            'pageTitle'     => $dashboard['label'],
            'dashboardName' => $name,
            'dashboards'    => $manager->getDashboards(),
            'widgets'       => $manager->getDashboardWidgets($name),
        ]
    );
}

Behind the scenes the render method will use the view identifier OroDashboardBundle:Index:default.html.twig and the templating service's renderResponse method to create a Response object

#File: vendor/oro/platform/src/Oro/Bundle/DashboardBundle/Controller/DashboardController.php
public function render($view, array $parameters = array(), Response $response = null)
{
    return $this->container->get('templating')
        ->renderResponse($view, $parameters, $response);
}

Pretty straight forward so far. However, you'll also see controller action methods returning plain old PHP arrays.

#File: vendor/oro/crm/src/OroCRM/Bundle/AccountBundle/Controller/AccountController.php
/**
 * @Route("/view/{id}", name="orocrm_account_view", requirements={"id"="\d+"})
 * @Template
 * @Acl(
 *      id="orocrm_account_view",
 *      type="entity",
 *      permission="VIEW",
 *      class="OroCRMAccountBundle:Account"
 * )
 */    
public function viewAction(Account $account)
{
    return ['entity'  => $account];
}

This can be confusing the first time you encounter it — the trick here is the annotation configuration format. Specifically the @Template annotation. The @Template annotation lets you skip the explicit call to render, and tells Symfony which template to use. The returned PHP array becomes the variables for the view.

When, as above, there's no template listed after @Template, Symfony will construct a template name using the bundle name, controller class name, and action method name.

In the above example, this generated view name is

OroCRMAccountBundle:Account:view.html.twig

The OroCRMAccountBundle part comes from the controller's bundle, the Account comes from the controller's class name, and the view comes from the action method name.

For people curious about the implementation details, this template name gets created in the following Symfony core file, called as a kernel.controller listener.

#File: vendor/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle/EventListener/TemplateListener.php
public function onKernelController(FilterControllerEvent $event)
{
    //...
    if (!$configuration->getTemplate()) {
        $guesser = $this->container->get('sensio_framework_extra.view.guesser');
        $configuration->setTemplate($guesser->guessTemplateName($controller, $request, $configuration->getEngine()));
    }        
    //...
}

More specifically, it's the call to the sensio_framework_extra service's guessTemplateName that does it.

Symfony's internal use of the annotation system seems to be their compromise in the "opinionated framework" wars. Symfony, by itself, is a configuration based framework. However, the annontation features are often used to implement a simplier, "opinioned" version of a feature.

Also, I thought about going on a strongly types rant about how PHP's lack of return types combined with their embrace of Java/C# style classes make this feel extra weird — but decided it fit better as a snarky post script.