Split your Symfony views into multiple controllers with NumberNine CMS
Tags: symfony, cms, php
Published: 2021-08-13 16:43:42

When building a simple page with Symfony, we usually end up having a controller and a view.

![Simple controller](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p8y4yujvl2cfw9jifqs8.png)

Oftentimes, things get complicated and one controller is not a viable solution anymore, therefore we need to split the view like this:

![Multiple controllers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ss1gg87kgzhtr5n3ss1m.png)

Symfony offers several ways to split your views into multiple controllers. NumberNine CMS offers two more. We'll see which cons and pros each method provides.

* using Twig partials with `include`
```twig
{% include 'my_partial.html.twig' with {message: 'hello'} %}
```
* using Twig partials with `embed`
```twig
{% embed 'my_partial.html.twig' %}
    {% block message %}hello{% endblock %}
{% endembed %}
```
* using [embedded controllers](https://symfony.com/doc/current/templates.html#embedding-controllers)
```twig
{{ render(controller('MyController', {message: 'hello'})) }}
```
* using [Twig components](https://github.com/symfony/ux-twig-component)
```twig
{{ component('my_component', {message: 'hello'}) }}
```
* using NumberNine components
```twig
{{ N9_component('MyComponent', {message: 'hello'}) }}
```
* using NumberNine shortcodes
```twig
{{ N9_shortcode('[my_shortcode message="hello"]') }}
```

## Pros and cons of each technique

**Twig includes and embeds** are great and flexible but their data come from above, which makes the root controller bloated. Of course there are workarounds such as Twig extensions or global variables to access a service, but a view shouldn't retrieve data that the controller didn't send to it.

**Embedded controllers** are a good solution if they're used sporadically, as they make a new Request and will negatively impact performance.

**Twig Components** is a better approach than embedded controllers, although they have some limitations. Components are non shared services, which means a new instance is created at every call of `{{ component('my_component') }}`, erasing previously stored data. This means that if your component registers an event listener, it won't work as the instance is created on the fly.

## NumberNine components

NumberNine Components solve the problem of Twig Component's separate instances by creating a new set of template parameters everytime the component is called, but the instance itself of the component stays the same.

A NumberNine Component is a shared service, hence event listeners registered will catch events as they should. Parameters injection is done through setters.

Here's a concrete example.

```twig
# templates/page/show.html.twig

{{ N9_component('MyComponent', {example: 'string variable'}) }}
```

```php
// src/Component/MyComponent/MyComponent.php

final class MyComponent implements ComponentInterface
{
    private string $example;

    public function setExample(string $example): void
    {
        $this->example = $example;
    }

    public function getTemplateParameters(): array
    {
        return [
            'example' => $this->example,
        ];
    }
}
```

```twig
// src/Component/MyComponent/template.html.twig

<p>Displaying custom variable: {{ example }}.</p>
```

Moreover, just like every other view in NumberNine, component templates are overridable. The theme you use probably uses components, and you'll probably want to adapt the design.

[Read more](https://numberninecms.com/developers/howto/theming/create-a-component.html) about components on the documentation page.

## NumberNine shortcodes

While components are developer-oriented, shortcodes are a user-oriented way to inject templates in the view.

Also known as BBcodes, shortcodes and have been widely used in forums and CMS for years. They are represented by a string that the user can input in his editor.

They have a short syntax:
```
[my_shortcode message="hello"]
```

Or an extended syntax:
```
[my_shortcode]hello[/my_shortcode]
```

Extended syntax allows for nested shortcodes:

```
[my_shortcode]
    [my_nested_shortcode message="hello"]
[/my_shortcode]
```

NumberNine handles shortcodes as services. Each shortcode has its own class which acts like a controller.

As an example, we'll analyze this page header:

![Page header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l82bfj06mjswwjq7cal2.jpg)

Now look at how it's rendered behind the scene:

![Rendered shortcodes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/koa2ps5hqi7a6rmhl1p9.jpg)

This is not the result of a manual input in an editor, although it can. To know more about how it was rendered, see the last section of this article about the page builder.

Back to our view splitting. To put it simply, this header is composed of 9 controllers and 9 views, with no negative performance impact.

Let's take the `my_account_link` shortcode and see how it's built, as it's a very basic shortcode. Its purpose is to automatically link to the user-defined "My account" page:

```php
/**
 * @Shortcode(name="my_account_link", label="My Account Link")
 */
final class MyAccountLinkShortcode extends AbstractShortcode
{
    public function configureParameters(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'loggedOutText' => 'Login / Register',
            'loggedInText' => 'My account',
        ]);
    }

    public function processParameters(array $parameters): array
    {
        return [
            'loggedOutText' => $parameters['loggedOutText'],
            'loggedInText' => $parameters['loggedInText'],
        ];
    }
}
```

While its template looks like this:
```twig
<a href="{{ N9_path(PAGE_FOR_MY_ACCOUNT) }}">
    {%- if is_granted('IS_AUTHENTICATED_REMEMBERED') -%}
        {{ loggedInText|trans }}
    {%- else -%}
        {{ loggedOutText|trans }}
    {%- endif -%}
</a>
```

Now it can be used either by the developer with `{{ N9_shortcode('[my_account_link]') }}`, or directly by the user in a text editor with `[my_account_link]`.

That's all! Our views can be split in many ways and still keep a high performance. The main controller stays very light, and every partial controllers handle their own responsability. This makes the code highly reusable and easier to use for both developers and users.

[Read more](https://numberninecms.com/developers/howto/theming/create-a-shortcode.html) about shortcodes on the documentation page.

## Shortcodes and the page builder

NumberNine's page builder relies on shortcodes to build the view. Each shortcode can be defined as an editable shortcode, which will appear in the page builder's list of available components to the user.

This will be the topic of an other article. Meanwhile, [check out the documentation](https://numberninecms.com/developers/howto/theming/create-a-shortcode.html) to see how to build a page builder component with a shortcode.

## Read more
{% post numberninecms/symfony-on-steroids-with-numbernine-cms-4no4 %}