Допустим, у меня есть два объекта, категория и продукт:
категория
<?php class Category {
private $name;
public function __construct(string $name) {
$this->name = $name;
}
}
?>
Товар
<?php class Product {
private $name, $category;
public function __construct(string $name, Category $category) {
$this->name = $name;
$this->category = $category;
}
}
?>
И у меня есть CategoryMapper, который получает (и вставляет / обновляет / удаляет) Category-объекты из базы данных:
CategoryMapper
<?php
class CategoryMapper {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function ofId($id) : ?Category {
$query = 'SELECT name FROM category WHERE id = ?';
$stmt = $pdo->prepare($query);
$stmt->execute([$id]);
if ($row = $stmt->fetch()) {
return new Category($row['name']);
}
return null;
}
// Other methods
}
?>
Мне также нужен ProductMapper, но ProductMapper ofId()
Метод должен возвращать объект Product с объектом Category внутри него.
Итак, теперь мой вопрос: как должен быть структурирован мой ProductMapper?
Вариант 1 — передать ProductMapper CategoryMapper в конструкторе
Я предполагаю, что это может стать довольно большим, если CategoryMapper, в свою очередь, потребует еще один Mapper, и что Mapper требует еще один Mapper, и так далее.
<?php
class ProductMapper {
private $pdo, $categoryMapper;
public function __construct(PDO $pdo, CategoryMapper $categoryMapper) {
$this->pdo = $pdo;
$this->categoryMapper = $categoryMapper;
}
public function ofId($id) {
$query = 'SELECT name, categoryId FROM product WHERE id = ?';
$stmt = $pdo->prepare($query);
$stmt->execute([$id]);
if ($row = $stmt->fetch()) {
$category = $this->categoryMapper->ofId($row['categoryId']);
return new Product($row['name'], $category);
}
return null;
}
}
?>
Вариант 2 — пусть ProductMapper создаст CategoryMapper в методе, где он нужен
Если для ProductMapper требуется много других Mappers, я мог бы здесь не заметить, какие Mappers на самом деле используются ProductMapper, потому что они не перечислены в одном месте, а разбросаны по всему коду.
<?php
class ProductMapper {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function ofId($id) {
$query = 'SELECT name, categoryId FROM product WHERE id = ?';
$stmt = $pdo->prepare($query);
$stmt->execute([$id]);
if ($row = $stmt->fetch()) {
$categoryMapper = new CategoryMapper($this->pdo);
$category = $categoryMapper->ofId($row['categoryId']);
return new Product($row['name'], $category);
}
return null;
}
}
?>
Вариант 3. Не используйте CategoryMapper и дублируйте код build-a-category в ProductMapper
Это приводит к большому количеству дублирующегося кода (особенно, если внедренные объекты становятся больше или в них также встроены объекты).
<?php
class ProductMapper {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function ofId($id) {
$query = 'SELECT p.name as productName, c.name as categoryName FROM product p join category c on p.categoryId = c.id WHERE p.id = ?';
$stmt = $pdo->prepare($query);
$stmt->execute([$id]);
if ($row = $stmt->fetch()) {
$category = new Category($row['categoryName']);
return new Product($row['productName'], $category);
}
return null;
}
}
?>
Так как это обычно делается?
Есть ли другие варианты, о которых я не думал?
Краткий ответ: первый метод правильный. Вы должны передать все зависимости как параметры в конструкторе.
Длинный ответ: первый метод правильный, но рекомендуется использовать некоторый контейнер для инъекций зависимостей. Он позаботится об инициализации объектов и передаче их в качестве параметров другим объектам. Это также обеспечит инициализацию только одного объекта каждого класса. Если вы хотите что-то легкое, вы можете пойти с Прыщ или, если вы предпочитаете что-то более настраиваемое с множеством функций, Компонент DependencyInjection от Symfony.
Я думаю, что вы должны добавить метод singleton в классе CategoryMapper, как показано ниже
public static $instance = null;
public static function getInstance() {
if (self::$instance == null) {
self::$instance = new self();
}
return self::$instance;
}
Призыв к ProductMapper, как показано ниже
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
$this->categoryMapper = CategoryMapper::getInstance();
}