Single-File Symfony Apps? Yes, with MicroKernelTrait!

Deji Akala
Deji Akala
Share

This article was peer reviewed by Younes Rafie, Claudio Ribeiro, and Haydar KÜLEKCİ. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


A Single Page Application (SPA) offers a desktop experience to users of a web application by loading a single HTML page, and dynamically updating it as required without reloading. However, a Symfony application may have hundreds of classes, and in a basic application we end up with lots of files we don’t really need.

Illustration of a programmer holding up an elephant

The latest versions of Symfony (2.8 and 3.0) introduce us to the concept of a Single File Application (SFA) – a super-slim application or micro-framework implemented in one file.

To follow along, you need to have a running web server and have your way of running web applications locally. See Laravel Valet article for a quick way of setting up a local development environment that doesn’t require configuring a web server, virtual hosts and mucking about with a hosts file. Another option is our trusty Homestead Improved for a ready-to-go experience.

Step 1: Install Barebones Symfony

We are going to install Symfony with Composer as it allows us install only the main package. Create a folder where you usually have your web applications and let’s call it sfa. I’ve got mine under ~/Sites/sfa. In it, we install Symfony:

composer require symfony/symfony

Now, create 2 folders inside sfa and name them app and web.

Step 2: The Front Controller

Inside sfa/web we will house our front controller – a file that receives all requests to the application, passes it to the right place for processing and returns the response to the client that made the request.

You can call this file anything, but you need to make sure your web server has been configured to find it in the correct place. Laravel has public/index.php, Drupal 8 has index.php, and Symfony has web/app_dev.php (during development) and web/app.php (during production). Since this is a Symfony application, let’s call ours app_dev.php:

<?php

use Symfony\Component\HttpFoundation\Request;

require __DIR__.'/../vendor/autoload.php';
require __DIR__ . '/../app/SfaKernel.php';

$kernel = new SfaKernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

Two small differences between this file and a vanilla Symfony 3 installation.

Firstly, our kernel class is going to be in app/SfaKernel.php. Nothing stops us from calling it Kernel.php, but we want something different. Secondly, we opt not to call the loadClassCache() method. Our application is a slim one, without a good number of classes from a standard installation, so we can leave that method out for now.

Even though we’re talking about a single file app, you’ll notice it’s not really a single file – we do have a front controller and a mini-kernel which does all the heavy lifting. That’s in addition to all the other classes loaded from vendor. However, for all intents and purposes, starting and running a Symfony app from a single Kernel file can be regarded as a single file application.

Step 3: The Kernel Class

Create app/SfaKernel.php and add this:

<?php

use Symfony\Component\HttpKernel\Kernel;

class SfaKernel extends Kernel
{
}

Our class should inherit from the Kernel class from Symfony core.

Since the Kernel class is an abstract class, our concrete class must implement the registerContainerConfiguration() method. By the way, if you look in the Symfony\Component\HttpKernel\Kernel.php file, you won’t find a registerContainerConfiguration() method – it’s in Symfony\Component\HttpKernel\KernelInterface.php which Kernel itself implements.

Here, we are interested in a new feature of Symfony 3 (also available since 2.8), that lets us create a micro-framework and has been aptly named MicroKernelTrait. Inside the class, use this trait:

<?php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;

class SfaKernel extends Kernel
{
     use MicroKernelTrait;
}

Now we need to implement three methods in this class – configureRoutes(), configureContainer() and registerBundles(). The first 2 are abstract methods from the trait while registerBundles() is in the Symfony\Component\HttpKernel\KernelInterface which Kernel implements and we, in turn, extend in our micro-kernel. If we take a close look at those methods, we can learn a lot from the comments.

  1. registerBundles()

    A bundle in Symfony is a set of files that implement a feature. Other applications or frameworks talk about plugins or modules. The only thing we need at the moment is the Symfony framework itself. The comment says, “Returns an array of bundles to register.”, so our method should look like this:

    public function registerBundles()
    {
        return [
            new FrameworkBundle()
        ];
    }
    
  2. configureRoutes()

    This is where we add or import routes for our application. Routing is looking at the path in the request and determining where to direct it to eventually get a response back. The comment is helpful as usual and it tells us about two ways:

    $routes->import('config/routing.yml');
    

    You add a configuration file where you define your routes and import it here.

    $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard');
    

    You specify a path (/admin), add a controller class with a method (AppBundle:Admin:dashboard), and optionally give it a name or alias (admin_dashboard).

    However, there’s a third way, rather a second way of specifying your controller. For example, 'kernel:home' refers to a method home() in the current kernel class. In effect, this SfaKernel class is doubling as a controller. How nice! Let’s add 2 routes.

    $routes->add('/', 'kernel:home');
    

    When we go to our home page, the request will be routed to the home() method in this class.

    $routes->add('/greet/{who}', 'kernel:greet');
    

    Similarly, this route will match all requests to /greet/{who} with a route parameter represented by {who} and pass them to a method called greet, with a $who parameter.

    Let’s go ahead and implement the methods. Once again, make sure you have use Symfony\Component\HttpFoundation\Response; at the top of the class.

    public function home() {
        return new Response(
            '<p>Home, sweet home</p>'
        );
    }
    
    public function greet($who)
    {
        return new Response(
            "<h1>Greeting</h1><p>Hello $who</p>"
        );
    }
    

    Bear in mind that you need to return a Response object from the methods.

  3. configureContainer()

    The container holds a wide variety of classes and they often need different parameters or any other configuration passed to them. This is where you register any extensions, services or parameters, e.g.

    $c->loadFromExtension('framework', array(
           'secret' => '%secret%'
    ));
    
    $c->register('halloween', 'FooBundle\HalloweenProvider');
    
    $c->setParameter('halloween', 'lot of fun');
    

    The only extension we’ve got is the FrameworkBundle which has a number of configuration options, but only one – secret – is required. They are provided as an associative array keyed by the options. The value of our secret should be unique, so our method should look like this:

    protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
    {
        $c->loadFromExtension('framework', [
            'secret' => 'micr0',
        ]);
    }
    

Provided you’ve configured your webserver, go to the home page and you should see, Home, sweet home. Append /greet/Symfony to the URL. Hello Symfony should be displayed in the browser.

You can register as many routes as you wish in registerRoutes() and return a response from this same class. You’ve got a working Symfony single file application.

Before continuing, let’s go back to the beginning when we first extended the Kernel class. We needed to implement only one method – registerContainerConfiguration(). When we added the MicroKernelTrait, we needed 3 methods.

If you open Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait.php, you’ll notice that the registerContainerConfiguration() method has been implemented for us, in order to make it more flexible and configurable. Let’s go through it.

public function registerContainerConfiguration(LoaderInterface $loader)
{
    $loader->load(function (ContainerBuilder $container) use ($loader) {
        $container->loadFromExtension('framework', array(
            'router' => array(
                'resource' => 'kernel:loadRoutes',
                'type' => 'service',
            ),
        ));

        $this->configureContainer($container, $loader);

        $container->addObjectResource($this);
    });
}

Inside the closure, look at the resource array key for router and you’ll see kernel:loadRoutes. We’ve come across something like this when we looked at specifying a controller class for our route. It’s the same concept here. kernel refers to the class using this trait when extending the Kernel class, and :loadRoutes will look for a method in this trait.

public function loadRoutes(LoaderInterface $loader)
{
    $routes = new RouteCollectionBuilder($loader);
    $this->configureRoutes($routes);

    return $routes->build();
}

It’s from this method our configureRoutes() gets called. To make sure it’s implemented in any micro-framework using this trait, it’s been defined as an abstract method.

Use cases

Implementing a real-life application in a single file wasn’t the primary goal of the MicroKernelTrait. It’s to give developers more flexibility in how they structure applications, what bundles they add, and when. Instead of a huge package they only use a fraction of, they are able to start off from a very slim installation and progressively build more functionality. Our example has no templating, for example, but it can be easily added.

Others are suggesting building microservices from the MicrokernelTrait based on the full-stack Symfony framework. Another possibility would be the separation of GET requests to be handled by a much leaner application of the MicrokernelTrait while other requests for more resource-intensive processes are managed by a more traditional Symfony application.

Conclusion

Prior to Symfony 2.8 and 3.0, micro-frameworks such as Silex and Lumen were the available options for projects that had reservations about implementing the full framework. However, the single file application concept has offered another middle ground.

This is an exciting thing for Symfony and in the days ahead, one can expect developers to squeeze out ingenious use cases from this new feature. Personally, I hope the vendor folder receives further scrutiny. Should a barebones installation with composer require symfony/symfony really pull in all those dependencies?

It’s still early days but the potential is there, and the direction in which developers are going to take this new feature remains to be seen. Are you using it yet? Let us know!

Frequently Asked Questions about Single-File Symfony Apps with MicroKernelTrait

What is the MicroKernelTrait in Symfony?

The MicroKernelTrait is a feature in Symfony that allows you to create a fully-functional Symfony application in a single file. It’s a trait that you can use in your kernel to define routes and services in a concise way. This is particularly useful for small applications or microservices where a full-fledged Symfony setup might be overkill. It provides a simpler and more streamlined approach to building Symfony applications.

How do I use the MicroKernelTrait in my Symfony application?

To use the MicroKernelTrait in your Symfony application, you need to include it in your kernel class. This trait provides two abstract methods: configureRoutes and configureContainer. You can define your routes and services within these methods. Here’s a basic example:

class MyKernel extends Kernel
{
use MicroKernelTrait;

protected function configureRoutes(RouteCollectionBuilder $routes)
{
$routes->add('/', 'App\Controller\DefaultController::index');
}

protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
$container->loadFromExtension('framework', [
'secret' => '%env(APP_SECRET)%',
]);
}
}

What are the benefits of using MicroKernelTrait?

The main benefit of using MicroKernelTrait is that it allows you to create a fully-functional Symfony application in a single file. This can make your code easier to manage, especially for smaller applications or microservices. It also simplifies the configuration process, as you can define your routes and services directly in your kernel class.

Can I use MicroKernelTrait for larger applications?

While it’s possible to use MicroKernelTrait for larger applications, it’s generally not recommended. The trait is designed for simplicity and ease of use, which can come at the expense of flexibility and scalability. For larger applications with more complex needs, a full-fledged Symfony setup might be more appropriate.

Are there any limitations or drawbacks to using MicroKernelTrait?

One potential drawback of using MicroKernelTrait is that it can make your code more difficult to organize and maintain as your application grows in complexity. Because everything is defined in a single file, it can become cluttered and hard to navigate. Additionally, because the trait simplifies the configuration process, it may not provide the level of flexibility and control that some developers need for more complex applications.

How does MicroKernelTrait compare to a full Symfony setup?

MicroKernelTrait provides a simpler and more streamlined approach to building Symfony applications compared to a full Symfony setup. It allows you to define your routes and services directly in your kernel class, eliminating the need for separate configuration files. However, a full Symfony setup provides more flexibility and control, and is generally better suited for larger, more complex applications.

Can I use MicroKernelTrait with other Symfony components?

Yes, you can use MicroKernelTrait with other Symfony components. The trait is fully compatible with the Symfony ecosystem, so you can use it in conjunction with any other Symfony component or bundle.

How do I install and set up MicroKernelTrait?

To use MicroKernelTrait, you need to include it in your kernel class. You can do this by adding the use MicroKernelTrait; statement at the top of your class. Then, you need to implement the configureRoutes and configureContainer methods provided by the trait. These methods allow you to define your routes and services.

Can I use MicroKernelTrait in a Symfony Flex application?

Yes, you can use MicroKernelTrait in a Symfony Flex application. The trait is fully compatible with Symfony Flex, so you can use it to simplify your application setup and configuration.

Where can I find more information about MicroKernelTrait?

For more information about MicroKernelTrait, you can refer to the official Symfony documentation. You can also find useful examples and tutorials on various Symfony community sites and forums.