PHP OOP:如何构建需要来自另一个 Mapper 的功能的 Mapper

PHP OOP: How to structure a Mapper that requires functionality from another Mapper

假设我有两个对象,类别和产品:

类别

<?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,它从数据库中获取(和 inserts/updates/deletes)类别对象:

分类映射器

<?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() 方法需要 return 一个包含 Category 对象的 Product 对象。

那么现在我的问题是:我的 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 需要很多其他映射器,我可能会在这里忘记 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 并在 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;

    }
}
?>

那么这通常是如何完成的呢? 还有其他我没有想到的选择吗?

我认为您应该在 CategoryMapper class 中添加一个单例方法,如下所示

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();
}

简答:第一种方法是正确的。您应该在构造函数中将所有依赖项作为参数传递。

长答案:第一种方法是正确的,但最好使用一些依赖注入容器。它将负责初始化对象并将它们作为参数传递给另一个对象。它还将确保每个 class 中只有一个对象被初始化。如果你想要轻量级的东西,你可以选择 Pimple or, if you prefer something more configurable with many features, Symfony's DependencyInjection component.