I’ve promised this post to a few guys on the slim framework irc channel (#slimframework on irc.freenode.net), so here we go.
As this post is not an introduction to slim framework 3 itself, I might be skipping over some information that, if you don’t know the framework, might be important to you. I recommend that if something is unclear – just have a look at the code of the example app and/or the documentation of slim framework 3
This post is basically in 3 pieces:
- the slim3-skeleton as an example – a very quick introduction in the relevant parts of slim3, and how it is “normaly” done
- replacing pimple with king23/di
- ok? whats the benefit
the slim3 skeleton as an example
to start this off, lets take akrabats slim3-skeleton as an quick example, as it basically does all the ground work a simple app would do.
you can get the slim3-skeleton from github (note that i linked a specific commit, the skeleton might have been improved / advanced after this post, so if you want to use it, look up the changes and use the latest version ;)
Now if you look at line 4 of the app/routes.php file, you will find there something like
$app->get('/', 'App\Action\HomeAction:dispatch') which is defining a route for get requests to / (first parameter), which will be routed to ‘\App\Action\HomeAction:dispatch’. Which means an instance of \App\Action\HomeAction will have its dispatch() method be called.
If you have a look at the \App\Action\HomeAction class, you will notice that the constructor actually requires a \Slim\Views\Twig and a \Psr\Log\LoggerInterface instance to be instanciated,
so lets have a quick look where this instance is created, which is in the app/dependencies.php file, on line 44
the app/dependencies.php file is used in the skeleton to configure all dependencies that the dependency injection (pimple) is taking care of.
So what happens when a request to / is routed is, it will break up the ‘App\Action\HomeAction:dispatch’ string, will then ask the di container to provide an instance of App\Action\HomeAction, and then call the dispatch method on the resulting object.
replacing pimple with king23/di
If you read the documentation of slim framework 3, you will notice that they made the wise choice of allowing the DI container to be replaced, all that the new DI container must do is implement container-interop/container-interop, and a few basic dependencies that every slim3 application has.
When I wrote my post about Slim Framework3 + King23/Castle23 + React a few weeks ago, i basically already wrote the stuff necessary for this, however i decided to improve the interop part a bit (cover exceptions as well), and release an king23/di-interop package, which conveniantly makes king23/di available to all frameworks supporting the interop interface.
So, whats left todo?
- add king23/di-interop to composer
- edit public/index.php so it will use king23/di-interop as a container
- edit the app/dependency.php, so it uses king23/di syntax for configuring the container
Hint: this is mostly to explain how the container is switched, if you want to use this, you don’t have to follow this step by step, you can simply use ppetermann/slim3-skeleton
add king23/di-interopt to composer
Now this once is simple enough, we are adding
king23/di-interop: "~1.0" to the require block of the composer.json, then bump the requirement for the twig view up to ~2.0, as older versions are dependend on pimple specific stuff and in theory are done.
Now what I like to do here, is to add an additional “replace” block, where I claim that the package replaces pimple, but be careful with following that example, as it is a bit of an abuse of the replace that composer offers.
The idea is: i have king23/di-interop in my app, so i don’t need pimple anymore, so why have those files litter my vendor directory?
“replace” allows you to define that a package is replacing another package in composer, the thing is, its meant to have the package you are writing the composer.json for say “i replace package x”, so in this case our skeleton app is replacing pimple with itself. Now as it is requiring king23/di-interop instead, and using it, this is not causing any problems, but I think its important to keep in your mind that it was meant to be used slightly different.
(The way the documentation is written it seems to be meant to be added to packages that are drop in replacements for others – say if king23/di was a fork of pimple, which could replace it while being 100% compatible on the public interface – but thats a bit off the reality of replaceable components. A better solution would be what i described a while ago in my post about Composer & virtual packages, but at the moment thats not supported by all packages involved.
edit public/index.php so it will use king23/di-interop as a container
When you want to replace the container used by slim framework 3, you do that by handing it the container as parameter in the constructor. This parameter defaults to an empty array, and in the skeleton is actually taking the contents of app/settings.php.
So, to make this work, we remove the line that is loading the settings (we will do that elsewhere), and we actually inject the return value of a new file called app/container.php, where we instanciate the king23/di-interop container (King23\Di\Interop\InteropContainer), and add the minimum required dependencies for slim.
Then we rewrite the app/dependencies.php file to use king23-di syntax for configuring it, and delete the ‘Action Factories’ section of it (yes, thats right)
ok, what’s the benefit?
Well, first of all – running it with pimple is totally fine, so don’t take this as “pimple sucks” or something in that direction.
What I don’t like is that “action factories” configuration which i removed in the end of the last step. Having actually the DI container use the classes (idealy interfaces like with the logger) that are typehinted in the constructor to pick up what it should inject to me makes it feel a lot more “rounded”, and i can spend my time writing actual logic rather than keep doing the same boilerplate code for intialization of Actions (and other classes using the DI) over and over again.
a bit of salt
There is two downsides to using this:
- all the main examples in the slim documentation or in skeletons out there assume you are using pimple, while that in most cases shouldn’t be an issue you should know that not all of this has been written with the container-interop interface in mind – quite often you find code thats accessing the container through ArrayAccess, something thats not available in container-interop (and not in king23/di either). I’m sure this will get better with time, but in the end you will every now and then run into code that you have to refactor if you want to use it.
- if king23/di does not know a key, but it can load a class by the name of the key it will, and it will then use reflection to determine what to inject into the constructor – thats how it can create the Actions in this post without being configured for each Action, however that means that each time an object is created this way, there will be some reflection overhead, that won’t be there with the pimple solution. In the end it shouldn’t be much, but you should know this.