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
Tags: coding, development, dev, legacy, php, maintenance, silex, howto, pimple