PHP
Article

Single-File Symfony Apps? Yes, with MicroKernelTrait!

By Deji Akala

Working with PHP 7.1? Download our FREE PHP 7.1 Cheat Sheet!

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!

  • Joeri Sebrechts

    composer require symfony/symfony pulls in 27 MB of code in 6159 files. So, wouldn’t it be more accurate to say this is an app in only 6160 files? Just because it comes from composer doesn’t mean it’s not part of your app.

    It annoys me that there are no light-weight PHP frameworks. Even something like slim-skeleton is 6 MB of code across 1781 files. There is no hope of comprehending that sort of complexity. You just have to put your faith in the framework authors and hope they did a good job. But if you want to be in control of your codebase, you have no alternative except to write it yourself from scratch, which is even worse.

    • martinczerwi

      I see your point, and it’s valid indeed. But most modern Frameworks are highly tested, and you can dig through tests if you don’t trust the code.
      You can use several components and piece your stuff together. Although that’s pretty complicated, you can start of with Aura Router, and skip Containers and ORM altogether, bootstrap your app manually, but you’ll pretty much loose the comfort frameworks provide you with.

    • Călin Pristavu

      That’s when the standalone components come to the rescue.

      If you build a terminal app for instance, just require symfony/console
      If you migrate from a legacy vanilla project, add components one by one
      And so on.

      The point is, there are options, and it’s up to you if you only fetch what you need, or build on top of the full stack.

      The thing is, although your concern is valid in some regards, I find it a bit hindering for a project’s outcome.
      Relying on fully tested code is by far better than re-implementing and maintaining it.

      But in the end, it’s just a matter of taste :)

      • Joeri Sebrechts

        I agree that writing everything yourself is a bad idea, but i wish i had a better option than pulling in massive amounts of code, or hand-rolling everything. IMHO, pulling in dependencies piece by piece doesn’t really work that well in practice to slim down a codebase because by the time you have something approaching a framework, you might as well have pulled in a framework.

        Besides, a well-designed framework is more than the sum of its parts. It creates a consistent development experience that results in a maintainable codebase. It is desirable to pull in a whole framework for that reason alone, but it is unfortunate that all of them are so big. I want something i can grok at a source code level in less than a day that gets rid of the boilerplate and papers over the rough parts of PHP.

        • Ahmed Abderraham

          When you “get” the concept of service containers (Dep. Injection), understanding Symfony’s code is a walk in the park. Start from the front controller (generally app.php) and follow the rabbit.

    • Deji Akala

      I agree that frameworks may offer a lot more than required for different types of projects. However, one needs to weigh the pros and cons of writing a couple of custom components against selecting a “bloated” framework with tried and tested high-quality code that simply works. Agreed things might not be that simple but it usually boils than to using the best tools for a job at hand.

  • I am wondering how would you implement the security component?..or a simple login form

Recommended
Sponsors
Get the latest in PHP, once a week, for free.