5

I've already commented on this thread but it seems to be dead so I'm opening a new one: Dependency Injection Slim Framework 3

The post above explains how pass Slims Container to a class you've written yourself.

However, the OP has asked if it's possible to get Slim to Dependency Inject ALL their classes.

I'm also interested in knowing if there's a way to do this since it seems to be anything but DRY if you have to pass the container to every class that you want to use it.

As an example, if I want to use one of Slim's functions (such as doing a redirect, in one of my own classes) I cannot use this as per the documentation:

$res->withStatus(302)->withHeader('Location', 'your-new-uri');

Because $res (the response object) is not within the scope of my class, unless I inject/pass it.

The problem with this is, if I have say 100 classes, do I have to pass (or inject) the container 100 times? That seems really, really tedious.

In frameworks like CakePHP you can use the 'AppController' to globally do stuff like this, i.e. define things once, and make it available in ALL your classes. Does Slim not provide this functionality? If not, that's a serious drawback, IMO.


Edit - I'm adding this from one of the comments I've made to try and explain the issue further:

If you look at the First Application Tutorial - http://slimframework.com/docs/tutorial/first-app.html - they are adding a PDO database connection to the container.

Let's say I have 100 separate classes in a sub-directory (the example has a ../classes/ directory) and autoload them in index.php using spl_autoload_register(). The container is NOT available in any of those classes.

If I had to pass something 100 separate times, each time I use one of my classes, just to get a PDO connection (and that's just one example) then that makes the code very repetetive, i.e. not DRY.

Community
  • 1
  • 1
Andy
  • 5,142
  • 11
  • 58
  • 131
  • Since Slim is using Pimple as DIC by default, maybe you should look into that? http://pimple.sensiolabs.org/ – M. Eriksson Oct 21 '16 at 12:01
  • AFAIK, Pimple doesn't have any way to automatically resolve constructor dependencies. So you need to register all classes to the container. However, I don't see why this would go against DRY, though? When you register your classes to the container, you still inject dependencies from the container, so all classes are only registered and resolved once... Check Pimples site for examples. – M. Eriksson Oct 21 '16 at 12:03
  • @MagnusEriksson to give an example - if you look at the First Application Tutorial - http://www.slimframework.com/docs/tutorial/first-app.html - they are adding a PDO database connection to the container. Now.. let's say I have 100 separate classes in a sub-directory (the example has a ../classes/ directory) and autoload them in index.php using spl_autoload_register(). The container is NOT available in any of those classes. If I have to write something 100 times to get my PDO connection in each class, that's not DRY because I'm repeating what is essentially the same code 100 times. – Andy Oct 21 '16 at 12:33
  • You would still only create one PDO connection and pass that connection to all classes. Sure, you need to inject that same instance to all classes, but that's not against DRY (you're not repeating the same code, you're just reusing the same class instance). You still only create each class once (including PDO), and injecting their dependencies. However, this is why I don't really like Pimple and similar containers. I prefer containers like Illuminate\Container, which uses reflections to automatically resolve dependency injections. – M. Eriksson Oct 21 '16 at 12:44
  • One could also argue that if you're building an app with 100-1000 classes, you might want to use some bigger framework like Laravel, which does all that you want, out of the box. – M. Eriksson Oct 21 '16 at 12:45
  • @MagnusEriksson thanks for the info. It's the bit where you've put *pass that connection to all classes.* - how is that done? It sounds like you have to do it each time a class is instantiated somewhere in the application? I was only using 100 classes as an example to illustrate the point that if one has *a lot* of classes, this becomes a bit repetitive. The app I'm actually working on only has about 10, and the reason I wanted to use Slim is because I thought it would be simpler and more lightweight than CakePHP, especially v3 which is very heavyweight IMO. – Andy Oct 21 '16 at 12:49
  • May be that one: [container resolution](http://www.slimframework.com/docs/objects/router.html#container-resolution). If you build your routes with invokable objects (they have `__invoke` function), Slim `will call it’s constructor with the container as the first argument`. – Zimmi Oct 21 '16 at 12:55
  • Yes, you need to pass it to all classes, but you shouldn't instantiate classes in different places in your application, you should register them all to the container and just fetch the one you need, when you need it. They will be lazy loaded and only instantiated at first use. Create one file where you register all the classes, before you do start the app. – M. Eriksson Oct 21 '16 at 12:55
  • @Zimmi - That way you will just use the container as a service locator and you have created the same hard dependency in all your classes. It will also be hard to write tests if you want to mock one class in only some of the classes that uses it. – M. Eriksson Oct 21 '16 at 12:58
  • @MagnusEriksson apologies but I do not follow what you mean by "Create one file where you register all the classes". I'm using spl_autoload_register() to autoload all classes in my 'classes/' directory, but beyond this I'm not sure what to do. Are there any coded examples I can look at for this? Many thanks for your help. – Andy Oct 21 '16 at 12:59
  • 1
    Did you check the first link I posted? Slims container is based in Pimple, so you can look at that page to get a more detailed idea on how to use the container. Look at the part about _Defining services_, for example. Defining services to the container and autoloading classes are two different things... – M. Eriksson Oct 21 '16 at 13:04

2 Answers2

10

Slim comes with Pimple by default. Some developers argue (and I tend to agree with them) that Pimple is not a dependency injection container, but a service locator, since it doesn't resolve dependencies on its own, you need to register them.

Slim 3 works with any dependency manager that implements Container interop interface, which PHP-DI does.

Go for this package. This is what I'm using for my projects and it's simply amazing, because of autowiring. To put it simply, PHP-DI reads constructor of the class and understands what needs to be injected, son you don't have to register dependencies as you would with Pimple.

Sometimes I think (hope?) that PHP-DI will replace Pimple as Slim's default DI container, because it's simply more advanced.

Here's how you do with Pimple:

<?php
namespace Controllers;

class UsersController
{
    // Inject Container in controller (which is bad, actually)
    public function __construct(ContainerInterface $container)
    {
        // grab instance from container
        $this->repository = $container['userRepository'];
    }

    // Handler of a route
    public function getAllUsers($request, $response)
    {
        $user = $this->repository->getAllUsers();
        return $response->withJson($users);
    }
}

Here's the same controller with PHP-DI:

<?php
namespace Controllers;

class UsersController
{
    // Declare your dependencies in constructor:
    // PHP-DI will find the classes and inject them automatically
    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }

    // Handler of a route
    public function getAllUsers($request, $response)
    {
        $user = $this->repository->getAllUsers();
        return $response->withJson($users);
    }
}

The problem with this is, if I have say 100 classes, do I have to pass (or inject) the container 100 times? That seems really, really tedious.

If you use Slim bundled with PHP-DI, the problem is solved autmatically with autowiring. :)

Georgy Ivanov
  • 1,573
  • 1
  • 17
  • 24
  • do you have any comments on the solution give below (user 'Audrey Roberts')? I've accepted your answer but the other solution doesn't depend on using things that don't come with a default Slim installation, and seems simpler. – Andy Oct 24 '16 at 08:18
  • 4
    Audrey's solution is totally acceptable; it is provided in Slim's tutorial. However, passing container as constructor argument really nullifies the very purpose of dependency injection and adds testability complications. It only seems simpler. – Georgy Ivanov Oct 24 '16 at 09:02
  • 2
    It's quite concerning (and somewhat annoying) the documentation for Slim goes on about Dependency Injection, and it transpires it doesn't really use it properly. This wasn't actually something I was properly aware of and have spent a long time reading up on DI and Slim, only to find out it doesn't even use it properly - very frustrating. I will look at autowiring and Slim Bridge in more detail though, thank you for the suggestions. – Andy Oct 24 '16 at 09:59
3

The simplest way to do this is like so:

index.php

$app->get('/mytest', '\TestController:mytest');

TestController.php

class TestController {

    protected $ci;

    public function __construct(Slim\Container $ci) {
        //var_dump($ci);
        $this->ci = $ci;
    }

    public function mytest() {
        $sql = ''; // e.g. SQL query
        $stmt = $this->ci->db->prepare($sql);
    }
}

I'm not sure if this is the "correct" way of doing it, but what happens is that the constructor of TestController receives the container as the first argument. This is mentioned in their documentation: http://www.slimframework.com/docs/objects/router.html#container-resolution

So when you're using a function like TestController::mytest() it has access to anything in the container, such as the PDO Database instance you set up in index.php (if following their First Application example tutorial).

As I say, I'm not sure if that's the "right" way of doing it, but it works.

If you uncomment the var_dump($ci) line in you'll see the Slim Container object.

If anyone has any feedback on this please comment as I'd be interested in knowing.

John
  • 199
  • 10