Dependency Injection Slim Framework 3
Asked Answered
D

1

7

I'm using Slim Framework 3 to create an API. The app structure is: MVCP (Model, View, Controller, Providers).

Is it possible to have Slim Dependency Inject all my classes?

I'm using composer to autoload all my dependencies.

My directory structure looks like this:

/app
   - controllers/
   - Models/
   - services/
   index.php
/vendor
 composer.json

Here's my composer.json file.

{
  "require": {
    "slim/slim": "^3.3",
    "monolog/monolog": "^1.19"
  },
  "autoload" : {
    "psr-4" : {
        "Controllers\\" : "app/controllers/",
        "Services\\" : "app/services/",
        "Models\\" : "app/models/"
    }
  }
}

Here's my index.php file. Again, the dependencies are being auto injected by composer

<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

require '../vendor/autoload.php';

$container = new \Slim\Container;
$app = new \Slim\App($container);

$app->get('/test/{name}', '\Controllers\PeopleController:getEveryone');

$app->run();

My controller looks like this

<?php #controllers/PeopleController.php

namespace Controllers;

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;


class PeopleController
{
    protected $peopleService;

    protected $ci;
    protected $request;
    protected $response;

    public function __construct(Container $ci, PeopleService $peopleService)
    {
        $this->peopleService = $peopleService;
        $this->ci = $ci;
    }

    public function getEveryone($request, $response)
    {
        die($request->getAttribute('name'));

        return $this->peopleService->getAllPeoples();
    }
}

My PeopleService file looks like this:

<?php

namespace Services;

use Model\PeopleModel;
use Model\AddressModel;
use Model\AutoModel;


class PeopleService
{
    protected $peopleModel;
    protected $autoModel;
    protected $addressModel;

    public function __construct(PeopleModel $peopleModel, AddressModel $addressModel, AutoModel $autoModel)
    {
        $this->addressModel = $addressModel;
        $this->autoModel = $autoModel;
        $this->peopleModel = $peopleModel;
    }

    public function getAllPeopleInfo()
    {
        $address = $this->addressModel->getAddress();
        $auto = $this->autoModel->getAutoMake();
        $person = $this->peopleModel->getPeople();

        return [
            $person[1], $address[1], $auto[1]
        ];
    }
}

Models/AddressModels.php

<?php

namespace Model;

class AddressModel
{

    public function __construct()
    {
        // do stuff
    }

    public function getAddress()
    {
        return [
            1 => '123 Maple Street',
        ];
    }
}

Models/AutoModel.php

namespace Model;

class AutoModel
{

    public function __construct()
    {
        // do stuff
    }

    public function getAutoMake()
    {
        return [
            1 => 'Honda'
        ];
    }
}

Models/PeopleModel.php

<?php
namespace Model;

class PeopleModel
{

    public function __construct()
    {
        // do stuff
    }

    public function getPeople()
    {
        return [
            1 => 'Bob'
        ];
    }

}

ERROR I'm getting the following error now:

PHP Catchable fatal error:  Argument 2 passed to Controllers\PeopleController::__construct() must be an instance of Services\PeopleService, none given, called in /var/www/vendor/slim/slim/Slim/CallableResolver.php on line 64 and defined in /var/www/app/controllers/PeopleController.php on line 21

THE QUESTION How do I dependency inject all my classes? Is there a way to automagically tell Slim's DI Container to do it?

Donoghue answered 25/4, 2016 at 16:9 Comment(8)
So you want to inject or autoload them? If you mean autoloading, you probably forgot to include vendor/autoload.php file at the beginning of index.php. It contains generated autoloaders.Magnific
Well, that was one of my issues.Thank you! I've updated the index.php to include the vendor autoload PHP file. I've updated the question to include the error I'm now getting. Again, the issue is how to Dependency Inject my classes.Donoghue
Well, the error is self-explaining. What's not clear here?Magnific
What's not clear is how to pass parameter to my controller's constructor method. $app->get('/test', '\Controllers\PeopleController:getEveryone(new Service\PeopleService'); doesn't workDonoghue
Here's an example that does exactly what you want: slimframework.com/docs/objects/router.html#container-resolution You need to register your controller as a service in DIC, and pass dependencies in its deifnition.Magnific
I followed that code exactly. As long as the only argument to the controller's constructor method is (Slim\Container $ci), then the code works fine. I was wanting to do constructor injection. So, every time I instantiate my constructor the service is called along with all the necessary modelsDonoghue
No, you didn't. It's all here: Slim first looks for an entry of \HomeController in the container, if it’s found it will use that instance otherwise it will call it’s constructor with the container as the first argument. You didn't register your controller in the container, did you?Magnific
Let us continue this discussion in chat.Donoghue
G
9

When you reference a class in the route callable Slim will ask the DIC for it. If the DIC doesn't have a registration for that class name, then it will instantiate the class itself, passing the container as the only argument to the class.

Hence, to inject the correct dependencies for your controller, you just have to create your own DIC factory:

$container = $app->getContainer();
$container['\Controllers\PeopleController'] = function ($c) {
    $peopleService = $c->get('\Services\PeopleService');
    return new Controllers\PeopleController($c, $peopleService);
};

Of course, you now need a DIC factory for the PeopleService:

$container['\Services\PeopleService'] = function ($c) {
    $peopleModel = new Models\PeopleModel;
    $addressModel = new Models\AddressModel;
    $autoModel = new Models\AutoModel;
    return new Services\PeopleService($peopleModel, $addressModel, $autoModel);
};

(If PeopleModel, AddressModel, or AutoModel had dependencies, then you would create DIC factories for those too.)

Gerome answered 26/4, 2016 at 6:34 Comment(2)
does Slim require you to pass the container to every class that needs it? That doesn't seem like a very DRY approach. I'm used to working with CakePHP and it seems like things are "globally" available when using that (possibly due to the AppModel and AppController). It seems in Slim that you're required to pass things such as the container every single time you need them? So if I have 100 classes, I have to use code equivalent to what you put above 100 times?Tarbox
It's up to you. There's nothing to stop you creating a global static function that allows you to grab the container. Personally, I dislike that level of coupling, so don't do it that way.Gerome

© 2022 - 2024 — McMap. All rights reserved.