The class Silex\Application
extends \Pimple
, a very lean dependency injection container, which itself implements \ArrayAccess
. This allows you to define parameters and services and retrieve them from the Application
like keys in an array. The first time I defined a service in a Silex application, in my case the Buzz Browser, I did it "inline", i.e. inside my app.php
file. This is how I did it:
use Buzz\Browser;
$app = new Silex\Application();
$app['autoloader']->registerNamespace('Buzz', __DIR__.'/vendor/buzz/lib');
$app['buzz.client_class'] = 'Buzz\\Client\\Curl';
$app['browser'] = $app->share(function() use ($app) {
$clientClass = $app['buzz.client_class'];
return new Browser(new $clientClass);
});
// $app['browser'] is ready for use
But this is not very reusable; every Silex application that needs Buzz\Browser
as a service, needs to take care of the autoloading, configure and define the service. The Browser service is not such a very complex service, but think about everything that needs to be done to define and configure a service like the Doctrine DBAL (of course, Silex has a DoctrineServiceProvider for that already...).
For reusability, Silex allows you to register a service provider. A service provider receives the Silex\Application
as it's only argument, so the provider can attach services to it. Then, in your app.php
file, you only have to register the service provider, and then the services that are defined by the service provider are available in your application:
$app->register(new SomeServiceProvider(), array('some_service.some_argument' => 'some-value'));
Silex\Application
adds the array with arguments as parameters to itself, so inside the service provider you can use them to configure your service:
use Silex\ServiceProviderInterface;
use Silex\Application;
use Matthias\SomeService;
class SomeServiceProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
if (!isset($app['some_service.some_argument'])) {
$argument = 'default-value;
}
else {
$argument = $app['some_service.some_argument'];
}
$app['some_service'] = $app->share(function() use ($app) {
return new SomeService($argument);
});
}
}
In the case of the Buzz Browser, it's constructor requires a client which implements Buzz\Client\ClientInterface
. Buzz for example provides clients for cURL
or file_get_contents
. I want to make the client configurable through service parameters. Furthermore, I also want to be able to define a custom class (e.g. for my own client, which caches responses, or does not make real requests at all). This is how I did it:
namespace Matthias\ServiceProvider;
use Silex\ServiceProviderInterface;
use Silex\Application;
use Buzz\Browser;
use Buzz\Client\ClientInterface;
class BuzzServiceProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
// add the path to the autoloader
if (isset($app['buzz.path'])) {
$app['autoloader']->registerNamespace('Buzz', $app['buzz.path']);
}
// provides shortcut for choosing a client: can be "curl" or "filegetcontents"
if (isset($app['buzz.client'])) {
$client = $app['buzz.client'];
$clientClass = '\\Buzz\\Client\\'.$client;
if (!class_exists($clientClass)) {
throw new \InvalidArgumentException(sprintf('Buzz client "%s" not found', $client));
}
$app['buzz.client_class'] = $clientClass;
}
// otherwise, use a custom class, if provided
if (!isset($app['buzz.client_class'])) {
throw new \InvalidArgumentException('Provide a client class for Buzz');
}
$clientClass = $app['buzz.client_class'];
$client = new $clientClass();
// check if the client is valid
if (!($client instanceof \Buzz\Client\ClientInterface)) {
throw new \InvalidArgumentException('The client should implement Buzz\\Client\\ClientInterface');
}
// add the Browser as a shared service
$app['browser'] = $app->share(function() use ($app, $client) {
return new Browser($client);
});
}
}
Add this to app.php
to register the BuzzServiceProvider
(make sure the Buzz files can indeed be found in the specified directory).
$app->register(new BuzzServiceProvider(), array(
'buzz.path' => __DIR__.'/vendor/buzz/lib',
'buzz.client' => 'filegetcontents',
));
Finally, this is how you can use the browser:
use Matthias\ServiceProvider\BuzzServiceProvider;
use Symfony\Component\HttpFoundation\Response;
// ...
$app->get('/', function() use ($app) {
$result = $app['browser']->get('https://matthiasnoback.nl/');
return new Response($result->getReasonPhrase(), $result->getStatusCode());
});
you need to add also this inside the class BuzzServiceProvider
public function boot(Application $app) {
}
Already done here : https://github.com/marcw/si... ...
Hi Greg,
Thanks! I did not mean to give Silex users a BuzzServiceProvider, but I wanted to show how you can create a ServiceProvider, I will edit the article, to better reflect my purpose. I trust other readers will notice the link you put in your comment and download the BuzzServiceProvider from that location.