DEV Community

Cover image for Symfony can help you fall in love with your front-end team!
Matheo Daninos for SensioLabs

Posted on • Edited on

Symfony can help you fall in love with your front-end team!

A little context

I'll place myself in the context of a Symfony application. We are a team of 8 developers: 6 dedicated backend Symfony developers and 2 frontend developers, including one who is a junior.
This shows that our team has a strong inclination towards backend development. The first thing not to do is to underestimate the frontend. Don't think that frontend development is easy; on the contrary, it requires expertise - an expertise that our backend developers lack.
Our goal, therefore, will be to take as much weight off the shoulders of our frontend developers as possible, without forcing backend developers to step out of their comfort zone.

The application I'll use for this example is as follows:

application homepage

We have a set of radios, multiple pages with different radios, and if you click on any of them, then you can listen to a live stream of that radio.

Let's start working!

So, we start by making the first page and everything goes well. But as we proceed to the next one, we encounter our first problem. Our frontend developers simply reused the code from the first page on the next. They immediately encounter this error:

error page

This is an error we're well acquainted with. We forgot to pass a variable to our Controller. Solving it is simple, but this error illustrates a deeper problem. Our frontend developer is dependent on our backend developers. Each time they make a new page, they have to ask the other developers to prepare a controller with the variables they need. We want to make our frontend independent. We aim to advance the project with as little friction between our backend and frontend devs as possible.

Component Architecture

For that, I propose that we cater to the frontend developers by using the component architecture. I won't go back over what component architecture is, but I invite you to read my article on this topic if you haven't already (https://dev.to/webmamba/how-to-integrate-component-architecture-into-symfony-4bjb).

So, we start by breaking down our page into many small components. Our list of radios will be a RadioList component composed of several Radio components. These Radio components are themselves made up of smaller components. To do all this, the frontend developer does not need us at all. They write anonymous component, component only made off a template:

RadioCard

<div
        class="
        transition ease-in-out delay-150 hover:-translate-y-1
        hover:scale-110 duration-300 aspect-square relative overflow-hidden
        rounded-lg bg-white/50 shadow-lg transition duration-300
        ease-in-out hover:shadow-2xl p-4"
>
    {% block content %}
    {% endblock %}
</div>
Enter fullscreen mode Exit fullscreen mode

RadioCardImage

{% props logo, name %}

<div class="relative overflow-hidden border-slate-500/50 border rounded aspect-square">
    <div>
        <img
                class="w-full h-full object-cover self-center"
                src="{{ logo }}" alt="{{ name }}"
        />
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Radio

{% props radio %}

<twig:RadioCard>
        <twig:RadioCardBackground logo="{{ radio.logo }}" radio="{{ radio.name }}" color="{{ radio.color }}"/>
        <twig:RadioCardImage logo="{{ radio.logo }}" name="{{ radio.name }}" />
        <div class="absolute bottom-2 right-2">
                <twig:PlayerButton/>
        </div>
</twig:RadioCard>
Enter fullscreen mode Exit fullscreen mode

I repeat, to make this list of components, the frontend did not need to write any PHP. They do all this work with complete autonomy.

Backend dev it's your time!

Once all these components are made to integrate our list of radios, the backend developers now only have to make a RadioList component, which has the following class:

<?php

namespace App\Twig\Components;

use App\Repository\RadioRepository;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class RadioList
{
    public ?string $filter = null;

    public function __construct(
        private readonly RadioRepository $radioRepository
    ) {}

    public function getRadios(): array
    {
        if ($this->filter === null) {
            return $this->radioRepository->findAll();
        }

        return $this->radioRepository->findByGenre($this->filter);
    }
}
Enter fullscreen mode Exit fullscreen mode

But where it becomes really interesting is for the template, which will now only be this. All the work will have been pre-done by our frontend developers.

{% for radio in this.getRadios %}
    <twig:Radio :radio="radio"/>
{% endfor %}
Enter fullscreen mode Exit fullscreen mode

This is great, we now have a working component able to communicate with our database, that anyone can reuse on every page. And all that while sticking to what we are good at, PHP code.

CVA

But we need to go a bit further. For example, for this site, we will need to set up an alert system. The informational alerts will be at the top left in blue, errors at the bottom in bold red, etc. We don't want a component for each variation; we want modular components.

homepage avec alertes

For this in Symfony, we can use CVAs (Component Variant Attributes). Here's how it works:

{% props color, size, position %}

{% set alert = cva({
    base: 'min-w-96 absolute rounded-lg shadow-md bg-slate-200 text-white z-50',
    variants: {
        color: {
            'blue': 'bg-blue-800 shadow-blue-800',
            'red': 'bg-red-800 shadow-red-800',
            'green': 'bg-green-800 shadow-green-800',
        },
        size: {
            'sm': 'p-2 mb-2 text-sm',
            'lg': 'p-4 mb-4text-lg',
        },
        position: {
            'top-left': 'top-4 left-4',
            'top-right': 'top-4 right-4',
            'bottom-left': 'bottom-4 left-4',
            'bottom-right': 'bottom-4 right-4',
        }
    }
}) %}

<div class="{{ alert.apply({color, size, position})|tailwind_merge }}" role="alert">
    {% block content %}
    {% endblock %}
</div>
Enter fullscreen mode Exit fullscreen mode

We create our Alert component. At the start of this file, we'll use the cva function. To this function, we'll pass a key array, the first key of which will be 'base' and will take as its value all the CSS classes that will be present in all variations of our components. Next, we'll define our variants. So here, variants on color, size, or position. Then, we'll apply our cva to the div of our alert.

And just like that, we've created a component with a multitude of possible variations.

So, quite naturally, we've been able to create a set of components of different sizes, with different variations, from which our developers can draw to implement new features. We have the beginnings of a design system. Working in this way allows our front-end and back-end developers to work without stepping on each other's toes, all while staying on a common codebase. Implementing a new feature is now picking between few components from our design system, we are dangerous!

Thank you for reading, see you soon for an article on how to document this design system!

Top comments (0)