PHP - - By Bruno Skvorc

Symfony Flex: Paving the Path to a Faster, Better Symfony

Symfony Flex is a modern replacement for the Symfony Installer, and not the name of the next Symfony version. As the intro text says:

Internally, Symfony Flex is a Composer plugin that modifies the behavior of the require and update commands. When installing or updating dependencies in a Flex-enabled application, Symfony can perform tasks before and after the execution of Composer tasks.

The new Symfony will be called just Symfony 4, and while this tutorial will deal only with the Flex tool, it will mention some Symfony 4 upgrades as well.


Still Under Development

Symfony Flex can be considered a Composer wrapper, in that it provides your Symfony project with additional options during installation and configuration. It was developed with simplicity in mind, and obviously heavily influenced by the user-friendliness of Laravel. Remember, Laravel got to its current level of popularity due to its ease of use and the low entry barrier it provides newcomers with, and Symfony wanted to emulate this.

It should be noted that both Flex and Symfony 4 are still under development, slated for release somewhere at the end of November this year (2017). As such, some of the features mentioned in this post may have changed by the time you read it, but we’ll do our best to keep it up to date.

Most notably, the use of a makefile and the make tool to build a project if Symfony/Console is unavailable is still up in the air, as it seems to not be working properly on some operating systems. Fabien recently held a survey around this, asking for the community’s suggestions to a replacement, and overwhelmingly, the community voted in favor of just making Symfony/Console required.

Survey result

What’s Different?

Most notably, Flex respects the coming Symfony 4 updates which boil down to the following major changes:

  • PHP 7+ is required
  • all folders are optional. If your project isn’t using one, it doesn’t have to be there. This makes the directory tree much simpler and more readable. Additionally, often useless files like .htaccess, LICENSE, and README have been removed as well – a project which needs those can easily add them.
  • there is no more web folder. Instead, there is the public folder, like in all other major frameworks. This consolidates user experience across ecosystems.
  • temporary files go under /var in the root of the project folder, with the /var/cache subfolder reserved for long term cache, like merged class files for deploying apps as read-only artifacts
  • source code goes under /src. No /app.
  • configuration goes into /config.
  • templates go into /templates.
  • Flex will have its own Symfony-verified list of packages that are referenced by one and one alias alone. So executing composer require cli will actually trigger Flex, which will look in its list of packages, find the one tagged as cli (in this case, Symfony Console), and install it. These “official” packages are called recipes, and can be found here. To accept user-submitted recipes, a flag exists in Flex’s configuration which needs to be set to true: composer config extra.symfony.allow-contrib true. Those recipes can be found here. By officially endorsing some packages, Symfony is in many ways becoming as opinionated as Laravel. While this is bad in some ways, it’s very good in many more ways: a consolidated, opinionated way to build Symfony apps used by most people so that everyone is on the same page.
  • bundle fragments no longer need to be custom-activated and added into a ton of files. Flex automates this, as well as their removal.
  • instead of parameters in config files, Symfony 4 will be using environment variables like Laravel

Bootstrapping

As usual, we’ll assume you’re already running a healthy VM environment like Homestead Improved so you can follow along.

Okay, let’s get our hands dirty with an example app. All Symfony apps can now be started from the bare bones super-minimal Symfony Skeleton app:

 composer create-project symfony/skeleton flexy

Notice the created directory structure.

Directory structure

In /public, we no longer have app.php and app_dev.php, only the index.php file. The type of the environment (test / dev / prod) is now dictated with environment variables, and reads the configuration from the /config folder.

Notice how the end of the installation process mentions that make cache-warmup was called, and that you can run make serve. This is where the new Symfony uses the “controversial” Makefile approach mentioned above. This might change.

Out of the box, opening this skeleton in the browser will throw an error because no routes have been defined yet. Let’s fix this.

index:
    path: /
    defaults: { _controller: 'App\Controller\DefaultController::index' }

config/routes.yaml

We’ll need to create this controller and its index action:

<?php

namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;

class DefaultController
{
    public function index()
    {
        return new Response('Hello');
    }
}

This will produce a simple Hello screen, like so:

Hello Symfony

Execution Permissions

If you try to install a binary like the Symfony/Console with composer req cli, you might run into the following problem:

~ bin/console
-bash: bin/console: Permission denied

This is a known hiccup when using virtual machines, and can be easily fixed by either:

  • running the console with php bin/console instead of running it directly, or
  • adding the “execute” permission to the file on the host machine (not from within the virtual machine), by executing: chmod +x bin/console. This will allow the direct execution of bin/console from within the VM then.

Adding Bundles

That “Hello” view we built is kind of naked. Let’s add some templates into the mix.

composer req template

We can use template, twig, templates, or templating here, as defined in the Twig recipe’s aliases.

The Symfony 4 / Flex approach will automatically activate this bundle for us and set up a folder (/templates) with a base layout view, as well as a configuration file (config/packages/twig.yaml).

We are now free to define a view for our Hello route:

{% extends '../base.html.twig' %}

{% block body %}
    {{ greeting }}
{% endblock %}

/templates/default/index.html.twig

Now we can modify the controller to return this instead of the plain text response:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller
{
    public function index()
    {
        return $this->render('default/index.html.twig', ['greeting' => 'hello']);
    }
}

Notice how we had to extend the FrameworkBundle‘s controller to get access to the render method, but that was about all the added configuration we had to do. Our hello route is now way cooler.

Screenshot of templated view

Big Bundles

Now let’s try adding in a big bundle – one that includes several others. The admin bundle for creating backends is a good option. Besides, it’s one the Symfony team decided to officially endorse, and it pulls in the orm recipe, which refers to Doctrine – another Symfony recommendation (can you see the opinionatedness in action?)

composer req admin

We’ll need to create an entity before we can use the admin bundle. For that, we need a database. Create a new database and user. This process should be fine:

mysql -u homestead -psecret
create database flexy character set utf8mb4 collate utf8mb4_unicode_ci;

Feel free to create a database specific user too if you feel it’s necessary. Then, modify the .env file to respect this:

DATABASE_URL="mysql://homestead:secret@127.0.0.1:3306/flexy?charset=utf8mb4&serverVersion=5.7"

Finally, let’s create an entity. Suppose we’re making a site that lets users make submissions to the site – like Reddit, links to submit for example. We’ll have an entity called “Submission”, like so:

<?php


namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
 * Class Submission
 * @package App\Entity
 *
 * @ORM\Entity
 * @ORM\Table(name="submission")
 */
class Submission
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    public $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    public $name;

    /**
     * @ORM\Column(type="string", length=255)
     */
    public $url;

    /**
     * @ORM\Column(type="text")
     */
    public $description;
}

The entity needs to be registered in the config/packages/easy_admin.yml file:

easy_admin:
    entities:
        - App\Entity\Submission

Now we’ll have Doctrine create this table for us.

bin/console doctrine:schema:update --force

Note that you can also have Doctrine create the database as well if it doesn’t exist. Look into doctrine:database:create for that functionality.

If we now visit the /admin URL of our app, we should see something like this:

Easy Admin Bundle Back End Screen

Adding submissions should now work like a charm:

A submission was added

Unofficial Bundles

What about bundles that aren’t officially backed by Symfony?

For that, we need to flip a Composer flag’s switch.

composer config extra.symfony.allow-contrib true

This will allow for pulling of recipes from this repository as well. Let’s say we want to make our submissions have uuid for ID instead of a simple auto-incrementing integer. We can use Ramsey’s UUID-Doctrine bundle for that. When requesting contrib recipes, they typically don’t have aliases and have to be referenced in full, like regular packages.

composer req ramsey/uuid-doctrine

Since this is a community contributed package, Symfony will throw a warning at you once it’s done downloading it.

A warning about the contrib recipe

Note: The contrib repo is a great filter of dead bundles and packages – every bundle/package developer who cares about their work will have moved it there, so you can be sure that the development on the missing ones has stagnated.

Once the package has been installed, we can use it in our project.

First, we need to tell Doctrine that it’s now available (something the recipe should do by itself, in my opinion – not automated enough just yet!):

doctrine:
    dbal:
        url: '%env(DATABASE_URL)%'
        types:
            uuid:  Ramsey\Uuid\Doctrine\UuidType
    orm:
    ...

config/packages/doctrine.yaml

Next, we change the Submission entity to use this type on its id attribute:

...
class Submission
{
    /**
     * @var \Ramsey\Uuid\Uuid
     *
     * @ORM\Id
     * @ORM\Column(type="uuid", unique=true)
     * @ORM\GeneratedValue(strategy="CUSTOM")
     * @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
     */
    public $id;
...

Now let’s update the database and clear current entities:

bin/console doctrine:schema:drop --force
bin/console doctrine:schema:update --force

Finally, let’s try re-visiting /admin and adding a new entity.

New entity added

Sure enough, our new entity has a UUID for a primary key.

Note: it is recommended to use another type when using UUID for primary keys in InnoDB type databases, but for the sake of brevity we used the default. Full instructions here.

Adding Third Party Tools

Other third party tools can be used just like before – only they won’t be auto-configurable by Flex. You’ll have to register them manually and remove them in the same way. It is therefore recommended you move any package that needs extra configuration to work smoothly with Symfony into the contrib recipes repo, so that others may benefit from the smoother Flex workflow.

Conclusion

Symfony Flex is the modern way to install and manage Symfony apps, and it’s the red carpet towards the door of Symfony 4. Needless to say, we’re very excited about Symfony’s most recent foray into modern development and the field of high-DX, and we’ll be keeping a close eye on it. We hope you found this introduction useful!

We didn’t benchmark this final result with another framework purely because it makes no sense when you consider the possible optimizations you can do, but also because we’ve got a performance month coming up (all of November) during which we’ll devour all sorts of performance related issues and go through a whole set of techniques you’ll be able to apply to your projects – no matter the framework or app type. Stay tuned!

Sponsors