When building a simple page with Symfony, we usually end up having a controller and a view.
Oftentimes, things get complicated and one controller is not a viable solution anymore, therefore we need to split the view like this:
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
{% include 'my_partial.html.twig' with {message: 'hello'} %}
- using Twig partials with
embed
{% embed 'my_partial.html.twig' %}
{% block message %}hello{% endblock %}
{% endembed %}
- using embedded controllers
{{ render(controller('MyController', {message: 'hello'})) }}
- using Twig components
{{ component('my_component', {message: 'hello'}) }}
- using NumberNine components
{{ N9_component('MyComponent', {message: 'hello'}) }}
- using NumberNine shortcodes
{{ 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.
# templates/page/show.html.twig
{{ N9_component('MyComponent', {example: 'string variable'}) }}
// 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,
];
}
}
// 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 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:
Now look at how it's rendered behind the scene:
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:
/**
* @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:
<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 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 to see how to build a page builder component with a shortcode.
Top comments (0)