Здравствуйте! Я изучаю паттерны проектирования, и сейчас мне интересен 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);
или через биндинг.