Здравствуйте! Я изучаю паттерны проектирования, и сейчас мне интересен Dependency Injection Container. Мне нужна консультация от более опытных специалистов. Имеется базовая модель: ```php abstract class Model { protected $connection; public function __construct() { $this->connection = (new PDOMysqlConnection)->getConnection(); } } ``` А также модель: ```php namespace models; class User extends Model { public function getAll(): array { return $this->connection->executeQuery('SELECT * FROM users')->fetchAllAssociative(); } } ``` И контроллер: ```php namespace controllers; use models\User as UserModel; class User extends Controller { private $model; public function index() { $this->model = new UserModel; } } ``` Проблема в том, что при каждом создании нового объекта модели будет устанавливаться новое соединение с базой данных. Ранее я использовал паттерн Singleton для этого. Могу ли я решить данную задачу с помощью Dependency Injection Container? Следует ли хранить зависимости в статическом свойстве массива? Вот код моего контейнера: ```php 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"); } } } ``` Заранее благодарю за советы и рекомендации!
Здравствуйте! Отличный вопрос, и вам абсолютно правы, что использование Dependency Injection (DI) Container поможет решить вашу проблему и позволить вам иметь одно подключение к базе данных на всё приложение, вместо создания нового подключения при каждом создании модели. ### Основная идея Чтобы использовать DI Container для управления длиной жизни соединений с базой данных, вам нужно: 1. Создать соединение с базой данных один раз и затем передавать это соединение в ваши модели через конструктор. 2. Использовать метод `set` в контейнере, чтобы хранить объект подключения и возвращать его при запросах. ### Изменения в коде 1. Измените класс модели так, чтобы он принимал подключение через конструктор: ```php abstract class Model { protected $connection; public function __construct($connection) { $this->connection = $connection; } } ``` 2. Измените класс `User` так, чтобы он передавал подключение: ```php namespace models; class User extends Model { public function getAll(): array { return $this->connection->executeQuery('SELECT * FROM users')->fetchAllAssociative(); } } ``` 3. В вашем контроллере передавайте подключение из контейнера: ```php namespace controllers; use models\User as UserModel; class User extends Controller { private $model; public function __construct($dbConnection) { $this->model = new UserModel($dbConnection); } public function index() { // Используйте $this->model для работы с базой } } ``` 4. Теперь организуйте контейнер, чтобы он создавал это единственное подключение и передавал его: ```php 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]; } } ``` 5. Настройте контейнер и используйте его: ```php $container = new Container(); // Создаем подключение, например $databaseConnection = (new PDOMysqlConnection())->getConnection(); // Регистрируем соединение в контейнере $container->set('db', function () use ($databaseConnection) { return $databaseConnection; }); // Можно создавать контроллеры $userController = new controllers\User($container->get('db')); ``` ### Заключение Теперь класс `User` будет получать одно соединение с базой данных, что поможет оптимизировать ваши запросы и уменьшить нагрузку на базу данных. Такой подход также легко модифицировать: если вам когда-нибудь понадобится подключение к другой базе данных или сделать его временным, вы сможете сделать это, не меняя внешний код. Использование статических свойств, как вы упомянули, не рекомендуется, так как это не позволяет легко тестировать и управлять зависимостями. Использование DI Container — более современный и гибкий подход, который позволяет улучшить ваш код и сделать его более тестируемым.
Без DI контейнера ты создаёшь какую-то конкретную реализацию подключения к БД и каждый раз к ней обращаешься, без возможности изменить или создать новую. То есть экземпляр объекта класса PDO создаёшь внутри.
При Dependency Injection, в сухом остатке, тебе не нужно привязываться к реализации, достаточно создать объект класса pdo снаружи и передать его.
DI-контейнер позволяет автоматизировать создание объектов и внедрение зависимостей. Это означает, что нам нужно иметь класс, который хранит все варианты созданных подключений к БД.
То есть, в любой момент мы можем положить в контейнер нужное подключение, а потом просто обращаться к уже готовому подключению. Но в отличие от Singleton, таких подключений может быть несколько и в коде можно выбирать разные, там где это нужно.
В твоём случае это будет что-то типа такого:
$container = new Container(); $container->set('primary_db', function () { return new PDO( 'mysql:host=localhost;dbname= primary_db', 'db_user', 'password' ); }); $db = $container->get('primary_db');
А в той экземпляр Model нужно передавать в конструктор уже существующее подключение. Например, так:
class User { private $db; public function __construct(PDO $db) { $this->db = $db; } ... } $db = $container->get('primary_db'); $user = new User($db);
или через биндинг.