Задать вопрос

Как грамотно реализовать одно соединение с базой данных на все приложение, с помощью Dependency Injection Container?

Здравствуйте, изучаю паттерны проектирования. В данном случае Dependency Injection Container. Нужен совет от опытных коллег.

Есть базовая модель
abstract class Model
{
    protected $connection;

    public function __construct()
    {
        $this->connection = (new PDOMysqlConnection)->getConnection();
    }
}


Модель
namespace models;

class User extends Model
{
    public function getAll(): array
    {
        return $this->connection->executeQuery('SELECT * FROM users')->fetchAllAssociative();
    }
}


Контроллер
namespace controllers;

use models\User as UserModel;

class User extends Controller
{
    private $model;

    public function index()
    {
        $this->model = new UserModel;
    }
}


При каждом создании нового объекта модели, будет создаваться новое соединение с базой данных. Обычно соединение делал с помощью Singleton. Подскажите пожалуйста, есть ли возможность решить это с помощью DIC?

Хранить зависимости в статическом свойстве-массиве?

Код контейнера
class Container implements ContainerInterface
{
    private array $instances = [];

    public function set(string $id, callable $resolver): void
    {
        $this->instances[$id] = $resolver;
    }

    public function has(string $id): bool
    {
        return isset($this->instances[$id]);
    }

    public function get(string $id)
    {
        if (!$this->has($id)) {
            throw new NotFoundException("Dependency '$id' not found in the container.");
        }

        return $this->instances[$id]($this);
    }

    public function bind(string $id)
    {
        return $this->buildDependencies($id);
    }

    private function buildDependencies(string $className)
    {
        $reflector = new \ReflectionClass($className);

        if (empty($constructor = $reflector->getConstructor())
            || empty($parameters = $constructor->getParameters())) {
            return new $className;
        }

        $dependencies = [];

        foreach ($parameters as $parameter) {
            $dependency = $parameter->getType()?->getName();
            $dependencies[] = $this->get($dependency);
        }

        return $reflector->newInstanceArgs($dependencies);
    }
}
  • Вопрос задан
  • 419 просмотров
Подписаться 3 Простой Комментировать
Ответ пользователя Vitsliputsli К ответам на вопрос (2)
@Vitsliputsli
Создать зависимость можно только 2 способами, получив объект изнутри или проведя инъекцию снаружи. Если изнутри, то так или иначе понадобится синглтон, если снаружи, то ктото снаружи должен контролировать передачу одного и того же объекта во все нуждающиеся объекты.

1. Изнутри. Очевидно просто инстанцировать объект - это очень плохой вариант. Более менее нормальный вариант, это сервис локатор, с методом возвращающим нужный объект работающим как синглтон (т.е. не нужен прям класс синглтона, только метод который проверяет существует ли объект). Выглядит это не очень, синглтон и сервис локатор не считают хорошим решением. Из минусов статичное обращение к сервис локатору создаст проблемы для подмены объекта. Для разных подключений вы можете создать отдельные методы сервис локатора или разрулить в одном методе динамический выбор. Реальная проблема - тестирование, вы не сможете мокировать объект работы с БД. Но, можно это сделать ухищрениями, сделав ленивое подключение, и метод подмены объекта БД, хотя придется этот метод тащить в каждый класс. Как вариант, делать инъекцию объекта сервис локатора. Но остается другой большой минус, сервис локатор "гвоздями прибит" к большинству классов и вам придется его везде таскать, т.е. о красивых компонентах без странной зависимости от сервис локатора можно забыть.
2. Снаружи. Оптимально, и считается трендом - инъекция нужного объекта в конструктор. Будет ли это делать DIC или чтото иное не имеет значения. Не нужно таскать какието дополнительные сервис локаторы. Но данное решение имеет тот же минус, что и п.1, вы не сможете подменить объект на другой при мокировании:
public function __construct(PDO $db)
внедряемый объект обязан быть классом PDO, если хочется чтобы мок просто подсовывал одни и те же значения нужно менять на интерфейс, тогда моку достаточно выполнять требования интерфейса. Но, если ваш DIC использует автоматическое определение требуемых объектов по структуре конструктора, то использовать интерфейс не получится.
Ответ написан
Комментировать