Open adjenks opened 6 years ago
I wrote this myself as part of the framework I work with. It's a combination of Slim, Dice, the PHP-DI Invoker, and Event (league/event) with a core focus on modularization (thus: everything except the container and the kernel is a module). It's core idea is being as pretty as Laravel (without the evil static stuff, instead using Dice), with the modularization of bundles, without the config hassle of Symfony. Sadly, this code is not yet available (will update if it is).
However, Dice is not directly compatible here, because it has no static set function such as Pimple does. Slim uses this extensively for settings, request and response and all handlers. Therefore I extended Dice with a static set function, having Dice return a static key instead of a created one, if one was already set in the call stack.
I also made some more modifications, for instance adding this syntax $this->container->when(PDO::class)->call({normal Dice config array for call}) and $this->container->substitute(SomeInterface::class)->for(SomeImplementer::class)
, also enabling this to be done via config if preferred.
Mainly, you need to implement the following things to make it work with Slim:
In the end i have enabled myself to inject the Slim\App and then $this->app->get('/test', TestController::class . '::testGet')
or $this->app->get('/test', [TestController:class, 'testGet'])
which will create the TestController via Dice (thus also providing dependencies in the controller) and will inject Request, Response and $args into the testGet
method.
Sadly, because I kinda created a new container, extending Dice, for the missing static set and some slightly different way of handling Interfaces, I don't think this bridge concept is very easy to implement.
Dice doesn't have a ->set()
method because I wanted to keep it purely a DI container, not a registry.
If your container is using set
and get
methods for instances, you don't need a DI container, you need an array. If you're using a container, the container's job is to construct the object and inject the object. So rather than:
$obj = new Obj('foo', 'bar');
$container->set('obj', $obj);
You can use Dice like so:
$dice->addRule('Obj', ['constructParams' => ['foo', 'bar']);
If you really need to set an existing instance, you can do it like so:
class NeedsObj {
public function __construct(Obj $obj) {}
}
$obj = new Obj('foo', 'bar');
$callback = function() use ($obj) {
return $obj;
};
$dice->addRule('NeedsObj', ['constructParams' => ['instance' => $callback]]);
Yes, to add to Tom here (as I agree), DON'T USE THIS SET FUNCTION ANYWHERE YOURSELF. It's only necessary because Pimple has it, and Slim uses it by saying that you have to provide for instance the settings
key on your container. It's not part of the PSR standard, and thus you should always avoid it.
For all normal purposes a container should not have a set
method.
Slim should have just allowed you to enter a config array and a PSR container seperately.
FYI, there is a Dice-Interop package: https://packagist.org/packages/tombzombie/dice-interop
This is a wrapper for dice that implements Cointainer-Interop. If Slim can use any container that has this interface, then you can use this as a wrapper for Dice in the framework.
Sadly, Slim requires setting static values first, and then using an PSR compatible container, as there is no defined set method for it to use. Dice does by design not support this.
https://www.slimframework.com/docs/concepts/di.html
See:
Required services
Your container MUST implement these required services.
If you use Slim’s built-in container, these are provided for you.
If you choose a third-party container, you must define these required services on your own.
settings
Associative array of application settings, including keys:
- httpVersion
- responseChunkSize
- outputBuffering
- determineRouteBeforeAppMiddleware.
- displayErrorDetails.
- addContentLengthHeader.
- routerCacheFile.
environment
Instance of \Slim\Interfaces\Http\EnvironmentInterface.
request
Instance of \Psr\Http\Message\ServerRequestInterface.
response
Instance of \Psr\Http\Message\ResponseInterface.
router
Instance of \Slim\Interfaces\RouterInterface.
foundHandler
Instance of \Slim\Interfaces\InvocationStrategyInterface.
phpErrorHandler
Callable invoked if a PHP 7 Error is thrown. The callable MUST return an instance of
- \Psr\Http\Message\ResponseInterface and accept three arguments:
- \Psr\Http\Message\ServerRequestInterface
- \Psr\Http\Message\ResponseInterface
- \Error
errorHandler
Callable invoked if an Exception is thrown. The callable MUST return an instance of
- \Psr\Http\Message\ResponseInterface and accept three arguments:
- \Psr\Http\Message\ServerRequestInterface
- \Psr\Http\Message\ResponseInterface
- \Exception
notFoundHandler
Callable invoked if the current HTTP request URI does not match an application route. The callable MUST return an instance of \Psr\Http\Message\ResponseInterface and accept two arguments:
- \Psr\Http\Message\ServerRequestInterface
- \Psr\Http\Message\ResponseInterface
notAllowedHandler
Callable invoked if an application route matches the current HTTP request path but not its method. The callable MUST return an instance of \Psr\Http\Message\ResponseInterface and accept three arguments:
- \Psr\Http\Message\ServerRequestInterface
- \Psr\Http\Message\ResponseInterface
- Array of allowed HTTP methods
callableResolver
Instance of \Slim\Interfaces\CallableResolverInterface.
If you choose a third-party container, you must define these required services on your own.
Shouldn't it then be up to the person who wants to use a different container to configure these services in whatever way the third party container requires? As long as $container->get('phpErrorHandler');
returns a relevant instance, why does it matter how the container was configured to do so?
For Dice, I'm guessing a lot of these classes will just work without any configuration anyway (though some might want to be marked as shared instances)
True, that's how it's designed, but how can you define the settings
array in Dice?
Answering this myself with your post from above:
$callback = function() use ($obj) {
return $obj;
};
$dice->addRule('NeedsObj', ['constructParams' => ['instance' => $callback]]);
You could return an array in the callback function maybe.
So for final recommendations:
Yes, that would work. However, with Inversion of Control, you'd usually do it the other way around. Rather than having the container act as a registry for things to pull the settings from, you'd have the container pass the settings array to any class which needed it:
$dice->addRule('SomeClassThatNeedsSettings', ['constructParams' => [$settings]]);
$dice->addRule('SomeOtherClassThatNeedsSettings', ['constructParams' => [$settings]]);
Agreed. Don't write your own stuff like Slim (using a $container->get('settings')
), and use the normal method Tom uses above.
Thanks for all the great feedback guys. I'll try it all out and let you know how it goes.
@adjenks Did anything ever come of your testing of Dice with Slim?
I would love to see this released as a plugin for the Slim Framwork: https://www.slimframework.com/docs/concepts/di.html
PHP-DI has php-di/slim-bridge, which I am using now, but I'd rather use Dice. http://php-di.org/doc/frameworks/slim.html
I'm sure I could write my own adapter, but I would much sooner transition to Dice if I could just
composer require level-2/dice-slim-bridge
or something of the sort.