Dependency injection is a powerful design convention that can help separate your app routing logic from the external services it needs (like a database connection, a user management system, and so on). That separation means your app will stay lightweight and flexible, easier to modify and better able to absorb external changes.
Bullet itself is built on Pimple 1.x, a simple dependency injection container, so injection capability is automatically available in your apps.
Our blog so far uses two external services:
somehowGetBlogMapper()
, a function that returns a database mapper for the
blog content and operationsPost
object that we use to create a new blog postLet’s work on that first one. somehowGetBlogMapper()
currently takes no
arguments, which means it must be figuring out the database connection
internally. That’s not very flexible—if we wanted to change the connection
settings, we’d have to go fiddle around in the middle of the function. If there
are more functions that need a database connection, we’d have to go fiddle with
all of them!
Let’s change our blog mapper by adding the database connection as a parameter (a.k.a. injecting a dependency):
function somehowGetBlogMapper($database_connection) { // use $database_connection to get the blog mapper }
Great, much more flexible! Let’s try using it in our Bullet app:
$app = new Bullet\App(array( 'template.cfg' => array('path' => __DIR__ . '/templates') )); // 'blog' subdirectory $app->path('blog', function($request) use($app) { $blog = somehowGetBlogMapper(/* where do we get the connection? */); });
Whoops, we’ve painted ourselves into a corner. If somehowGetBlogMapper()
doesn’t know how to get the database connection itself, that means the /blog
route has to:
// 'blog' subdirectory $app->path('blog', function($request) use($app) { $database_connection = somehowGetDatabaseConnection(); $blog = somehowGetBlogMapper($database_connection); // ... more paths and handlers here ... });
Wait, now the route has to know about the connection and the mapper? That doesn’t seem more flexible!
Lucky for us, our Bullet app doubles as a dependency injection container. Let’s teach Bullet how to create a database connection:
$app['database_connection'] = $app->share(function() { return somehowGetDatabaseConnection(); });
The $app->share()
method means that our database connection will be created
only once, the first time we request it. Every subsequent request will use the
same connection.
Now that Bullet knows how to create our database connection dependency, we can
inject it into the /blog
route:
// 'blog' subdirectory $app->path('blog', function($request) use($app) { $blog = somehowGetBlogMapper($app['database_connection']); // ... more paths and handlers here ... });
That’s an improvement; now the route gets the database connection right from
$app
. Can we keep going, and teach Bullet to create the blog mapper itself?
$app['database_connection'] = $app->share(function() { return somehowGetDatabaseConnection(); }); $app['blog_mapper'] = function($app) { return somehowGetBlogMapper($app['database_connection']); }; // 'blog' subdirectory $app->path('blog', function($request) use($app) { $blog = $app['blog_mapper']; // ... more paths and handlers here ... });
Whoa, what happened there?
$app->share()
for the mapper? That means Bullet
will run somehowGetBlogMapper()
every time the blog_mapper
is requested.)/blog
route, we
asked Bullet directly for the
blog mapper, without worrying about the database connection.Using Bullet’s built-in dependency injection container, we’ve managed to not
only make our database connection available from our entire app, but also
remove any blog mapper creation logic from our /blog
route. If we need to
change that logic later on, we’ll only have to modify the injector; not the app
routing.
This page only brushes the surface of Pimple’s (and Bullet’s!) dependency injection capabilities. If you want to keep going, read the documentation!