0

For loading classes I use PSR-4 autoload. In my index.php I use FastRoute component. In that index.php I create $db connection and pass it to the controllers.

call_user_func_array([new $class($db), $method], $vars);

In the controllers I receive it and pass it to the models and use it there. I know that is bad approach. DB connection that was created in index.php how can I use it in my models not pass it through controllers etc and without singleton instance of a connection? How can I use DI or DIC to set up db connection in index.php and get in all models?

index.php:

<?php

require_once 'vendor/autoload.php';

$db = new DbConnection(new DbConfig($config));

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/users', 'UserController/actionGetAllUsers');
    $r->addRoute('GET', '/users/{id:\d+}', 'UserController/actionGetUserById');
});

// Fetch method and URI from somewhere
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

// Strip query string (?foo=bar) and decode URI
if (false !== $pos = strpos($uri, '?')) {
    $uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);

switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        // ... 404 Not Found
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        $allowedMethods = $routeInfo[1];
        // ... 405 Method Not Allowed
        break;
    case FastRoute\Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $vars = $routeInfo[2];
        list($class, $method) = explode("/", $handler, 2);
        $module = strtolower(str_replace('Controller', '', $class));
        $class = 'Vendorname\\' . $module . '\\controllers\\' . $class;
        $routeInfo = $dispatcher->dispatch($httpMethod, $uri);
        call_user_func_array([new $class($db), $method], $vars);
        break;
} 

controller:

class UserController extends BaseApiController
{
    protected $db;

    public function __construct($db)
    {
        $this->db = $db;
        parent::__construct();
    }

    public function actionGetAllUsers()
    {
        $model = new User($this->db);
        echo json_encode($model->getAllUsers());
    }

model:

class User
{
    protected $db;

    public function __construct($db)
    {
        $this->db = $db;
    }

    public function getAllUsers()
    {
        $stmt = $this->db->prepare('SELECT * FROM user');
        $stmt->execute();
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }
Alex Pavlov
  • 571
  • 1
  • 7
  • 24
  • 1
    Look into using some Dependency Injection Container, like [Pimple](https://pimple.symfony.com/). [Illuminate/Container](https://github.com/illuminate/container), [league/container](https://github.com/illuminate/container) or similar. That's what most frameworks today does it. – M. Eriksson Jul 20 '17 at 08:47
  • @MagnusEriksson but I also should inject it to the controllers and from controllers inject it to the models in my example? Am I right understanding DIC? – Alex Pavlov Jul 20 '17 at 08:53
  • 2
    No. You should only inject dependencies that the specific class needs itself. Say if "Controller1" needs "UserClass" and "UserClass" needs "Database", the controller only gets "UserClass", but the container will make sure you got an instance of "UserClass" which already have the "Database" injected to it. The controller shouldn't know, or care, about the "UserClass"-dependencies. – M. Eriksson Jul 20 '17 at 08:53
  • 1
    With most DiC's (except from Illuminate), you will register all classes with their dependencies to the container in the beginning. When you then get an instance through the container, the container will make sure you get all the correct instances with their respective dependencies already injected (which you already have registered to the container). Check out the mentioned containers, they have some examples which might make it clearer. – M. Eriksson Jul 20 '17 at 08:58

1 Answers1

1

First of all, you have to understand, that "Dependency Injection" and "Dependency Injection Container" are two different things:

  • DI is a technique, that you use to separate two classes in OOP.
  • DIC is a piece of software, that assembles object graphs

It seems, that you are already, that you way of passing of DB connection is bad. The exact technical reason can be described as violation of LoD. Basically, your $db variable is crossing two layer boundaries.

But, the solution for this is not: "just add DI container". That won't solve the problem. Instead, you should improve the design of the model layer. You might do a quick read here, but I will give a bit shorter version here:

  • separate your persistence logic from your domain logic
  • controller should depend on service, which would be how controllers (that exist in UI layer) interact with the business logic.
  • the simplest way yo separate business logic is in three parts: datamappers (handle persistence logic), domain objects (for business rules) and services (interaction between domain objects and persistence objects)

The DB should be a dependency for a datamapper, that is specifically made for handling user instances (more here). That mapper should be either a direct dependency of your "account management service" (think of a good name) or be constructed using a factory inside that service (and this factory then would be the dependency of the service.

P.S. As for "which DIC to choose", I personally go with Symfony's standalone container, but that's purely personal preference.

tereško
  • 58,060
  • 25
  • 98
  • 150