Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request package for Slim Framwork #135

Open
adjenks opened this issue Jan 12, 2018 · 14 comments
Open

Request package for Slim Framwork #135

adjenks opened this issue Jan 12, 2018 · 14 comments

Comments

@adjenks
Copy link

adjenks commented Jan 12, 2018

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.

@christiaangoossens
Copy link

christiaangoossens commented Jan 13, 2018

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:

  • The static set function
  • The Slim CallableResolver and ControllerInvoker. (Using a modified PHP-DI Invoker is the easiest)
  • Substitutions on the container (Dice) for the Invoker, CallableResolver, ControllerInvoker, Request, Response etc if you want to use it in a sensible way with PSR-7.

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.

@christiaangoossens
Copy link

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.

@TRPB
Copy link
Member

TRPB commented Jan 13, 2018

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]]);

@christiaangoossens
Copy link

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.

@christiaangoossens
Copy link

Slim should have just allowed you to enter a config array and a PSR container seperately.

@TRPB
Copy link
Member

TRPB commented Jan 13, 2018

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.

@christiaangoossens
Copy link

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.

@TRPB
Copy link
Member

TRPB commented Jan 13, 2018

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)

@christiaangoossens
Copy link

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.

@christiaangoossens
Copy link

So for final recommendations:

  • Define your settings array with the method above
  • Define the others with the normal way, returning instances of the given classes (maybe as shared instances)
  • You can use the PHP-DI Invoker for the callableResolver as I did

@TRPB
Copy link
Member

TRPB commented Jan 13, 2018

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]]);

@christiaangoossens
Copy link

Agreed. Don't write your own stuff like Slim (using a $container->get('settings')), and use the normal method Tom uses above.

@adjenks
Copy link
Author

adjenks commented Jan 28, 2018

Thanks for all the great feedback guys. I'll try it all out and let you know how it goes.

@garrettw
Copy link

garrettw commented Aug 2, 2018

@adjenks Did anything ever come of your testing of Dice with Slim?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants