Create Single Page Application with Symfony

Create Single Page Application with Symfony

Posted on Wed, 09/28/2016 - 00:45 by zhilevan

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.

 

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.

More from this author

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!

(+)