whateverthing.com

Portfolio and Technology Blog
by Kevin Boyd

Upgrading to Silex 2

Version 2 of the Silex PHP Microframework has been out for a while, but many sites are still using Silex version 1. Perhaps this is because of Silex 2's backwards-compatibility breaks.

The Changelog file on the Silex website helpfully touches on these changes, but it can be tough to understand how to translate the entries into meaningful code. I've found the documentation on the Silex 2 website to be spot-on, but it's not always clear when the examples are different from Silex 1 code. To that end, I've started writing a wiki page on the Silex project that should help clarify things. A portion of it is presented here (based on a blog post I started writing over a year ago and forgot to finish. Oops!).

Here are some of the changes introduced in Silex 2:

System Requirements and Dependency Changes:

  • bumped minimum version of PHP to 5.5.0
  • bumped minimum version of Symfony to 2.8
  • Updated Pimple to 3.0

New Functionality:

  • added a global Twig variable (an AppVariable instance)
  • added support for the Symfony VarDumper Component
  • added support for the Symfony HttpFoundation Twig bridge extension
  • added support for the Symfony Asset Component
  • monolog.exception.logger_filter option added to Monolog service provider
  • Updated session listeners to extends HttpKernel ones

Backwards Compatibility Breaks:

  • [BC BREAK] CSRF has been moved to a standalone provider (form.secret is not available anymore)
  • [BC BREAK] Locale management has been moved to LocaleServiceProvider which must be registered if you want Silex to manage your locale (must also be registered for the translation service provider)
  • [BC BREAK] Provider interfaces moved to SilexApi namespace, published as separate package via subtree split
  • [BC BREAK] ServiceProviderInterface split in to EventListenerProviderInterface and BootableProviderInterface
  • [BC BREAK] Service Provider support files moved under SilexProvider namespace, allowing publishing as separate package via sub-tree split
  • [BC BREAK] $app['request'] service removed, use $app['request_stack'] instead

The two biggest changes that have affected my projects already (yep, that's me, living on the bleeding edge!) have been the upgrade to Pimple 3.0 and the removal of the $app['request'] service. Another change that has had an affect is the new way of building certain types of service providers.

Pimple the Simple DIC

Pimple 3.0, Silex 2's upgraded Dependency Injection Container, has at least one significant change that will certainly impact existing codebases.

Services in Pimple 1.0 were, by default, not shared. As an example:

$app['session'] = function () use ($app) {
    return new Session($app['session_storage']);
};

This returned a new Session object each time $app['session'] was invoked.

If you wanted a single, reusable service object, Pimple 1.0 required you to "share" it:

$app['session'] = $app->share(function () use ($app) {
    return new Session($app['session_storage']);
});

In Pimple 3.0, the entire concept is flipped-turned upside down. Services are shared by default. The example definition below will always return the same Session object:

$app['session'] = function () use ($app) {
    return new Session($app['session_storage']);
};

If you still need the functionality of a new object for each invocation, you have to define it as a "factory":

$app['session'] = $app->factory(function () use ($app) {
    return new Session($app['session_storage']);
});

Overall, this is a change that I'm pretty happy with. It's rare for me to want to use the factory workflow by default, and the code updates to accommodate the change are pretty simple - as long as you've been given the heads up that changes are needed.

The Request Service

The $app['request'] service is being removed, and developers are advised to use the $app['request_stack'] service instead. At first this sounds like a minor change, but it can actually hit codebases in an unexpected spot: smack in the templates. In the past I've used "app.request" in templates as a shortcut for getting certain information I needed, a dirty habit I probably picked up from a blog post somewhere.

There are two ways to fix this. One is extracting the request information you need and injecting it into Twig or your templates prior to rendering them. This approach is usually a solid one, as it has the added effect of keeping your templates "dumb" and easier to manage.

The other approach is to reference app.request_stack in your templates - but that gets pretty long-winded.

As a quick example, app.request.baseUrl becomes app.request_stack.currentRequest.baseUrl. The horror. The horror.

You're better-off doing something like this in your bootstrap:

$app->extend('twig', function ($twig, $app) {
    $twig->addGlobal(
        'baseUrl',
        $app['request_stack']->getCurrentRequest()->getBaseUrl()
    );
});

Or something like this in your controller action:

$app->get('/', function (Request $request) use ($app) {
    return $app['twig']->render(
        'index.html.twig',
        ['baseUrl' => $request->getBaseUrl()]
    );
});

Service Providers

The change to Pimple 3.0 also impacts the way some service providers need to be defined. In Silex 1.x, service providers always extended from the same parent class. In Silex 2.x, some service providers can still extend from that class, but certain types of service providers will have to extend from a related Pimple service provider class.

In Silex 1.x, you would extend from ServiceProviderInterface and then ensure that the register() and boot() methods were implemented.

With Silex 2.x, you can still extend from ServiceProviderInterface, but now the class is located in the Pimple namespace. Additionally, this version of the interface only requires the 'register' method, and the signature on the method has changed to require 'Container' instead of 'Application':

class MyProvider extends Pimple\ServiceProviderInterface {
    public function register(Pimple\Container $app)
    {
        // ...
    }
}

The separation of Pimple and Silex service providers allows Silex 2.x to implement three custom types of provider interfaces:

BootableProviderInterface

This interface is used when constructing providers that interact with the bootstrapping of the Silex application.

class MyBootProvider implements ServiceProviderInterface,
                                BootableProviderInterface
{
    public function register(Pimple\Container $app)
    {
        // ...
    }

    public function boot(Application $app)
    {
        // ...
    }
}

ControllerProviderInterface

This interface is used when constructing providers that add routes to your Silex application.

class MyBootProvider implements ServiceProviderInterface,
                                ControllerProviderInterface
{
    public function register(Pimple\Container $app)
    {
        // ...
    }

    public function connect(Application $app)
    {
        return new ControllerCollection();
    }
}

EventListenerProviderInterface

This interface is used when constructing providers that listen to events in your Silex application's lifecycle.

class MyListenerProvider implements ServiceProviderInterface,
                                    EventListenerProviderInterface
{
    public function register(Pimple\Container $app)
    {
        // ...
    }

    public function subscribe(Application $app, EventDispatcher $dispatcher)
    {
        // ...
    }
}

Conclusion

In conclusion, always read the changelogs! Support for Silex 1.x may run out at the end of 2017, or sooner. Get those apps upgraded!

Published: January 30, 2017

Categories: coding

Tags: coding, development, dev, legacy, php, maintenance, silex, howto, pimple

comments powered by Disqus