Zend\Db 模型与子模型

Zend\Db Model with Child Models

ZF2 项目 - 无 Doctrine,使用原生 Zend\Db:具有以下结构:

Controller 
    ProductController
Model
    Product
    ProductTable
    ProductType
    ProductTypeTable

Product 是模型,具有对应于“products”table 字段的变量。

ProductTable 是 table class,它通过 table 网关连接到数据库。 ProductTable 有 getItem() 方法来通过“id”检索请求的产品。

ProductType 是模型,具有与“productTypes”table 字段相对应的 id、名称、描述等变量。

ProductTypeTable 与 ProductTable 一样 table class。

每个产品都属于特定的 ProductType

products.productTypeId = productTypes.id 

是关系。

在 ProductTable->getItem() 方法中,我可以简单地获取 productTypeId。 我可以使用联接获取 productTypes.name、productTypes.description 或 "productTypes" table 中的任何字段。 但我不想这样做——而是处理 Product 实体中的新变量,如 productTypeName、productTypeDesc、 我想要 Product->getProductType() 并将其设置为 ProductType 对象,这样我就可以得到 Product->getProductType() ->getName() 来获取产品类型名称。

简单地说,我想指定一个子模型作为父模型的变量。

我可以像下面这样在控制器中执行此操作:

$product = $this->getProductTable()->getItem(7); // id = 7 
$product->setProductType($this->getProductTypeTable()
                    ->getItem($product->getProductTypeId());

但我想在产品 table class getItem() 方法中实现它。这样我就不用在每个controller里都考虑了,有点封装了。

正确的做法是什么?

谢谢。

你可以做到这一点,你只需要遵循以下3个步骤:

  1. 让您的 Product->exchangeArray() 功能更智能
  2. 获取所有必需的 ProductType 字段,使用前缀有助于例如:type_
  3. 添加 @var ProductType 这样您就可以进行适当的自动竞争(在 Eclipse 中对我有效)

    <?php
    namespace Product\Model\Product;
    
    class Product {
        public $id;
        ...
        /**
         * @var ProductType
         */
        public $productType;
        ...
        public function exchangeArray( $data ) {
            $this->id = (isset($data['id'])) ? $data['id'] : null;
            ...
            $productType = new ProductType();
            $typeData = array(
                'id' => $data['type_id'],
                'value' => $data['type_value']
            );
            $productType->exchangeArray( $typeData );
            $this->productType = $productType;
        }
    }
    

您遇到的问题是 Table Gateway pattern 仅擅长将数据库访问抽象为单个数据库 table。它无论如何都不允许实体的水合作用或关系的管理。对象关系映射器 (ORM),例如 Doctrine,解决了这个问题。

如果 Doctrine 无论出于何种原因不适合您的用例,替代方案可能是实施 Data Mapper Pattern

The Data Mapper is a layer of software that separates the in-memory objects from the database. Its responsibility is to transfer data between the two and also to isolate them from each other

数据映射器将使用 table 网关为每个table 获取所需的数据并构造Product 实例,包括它关联 ProductType。然后,您会将映射器公开给控制器(而不是 table 网关)。

一个ProductMapper.

的简单例子
class ProductMapper
{
    // @var  \Zend\Db\TableGateway\TableGateway
    protected $productTable;

    protected $productTypeMapper;

    // an 'identity map' of loaded products
    protected $loaded = []; 

    public function __construct(ProductTable $productTable, ProductTypeMapper $productTypeMapper)
    {
        $this->productTable = $productTable;
        $this->productTypeMapper = $productTypeMapper;
    }

    protected function hydrate(Product $product, array $data)
    { 
        $product->setId($data['id']);
        $product->setName($data['name']);
        $product->setFoo($data['foo']);

        if (isset($data['type_id'])) {
            // Load a fully constructed product type from the database
            $type = $this->productTypeMapper->findById($data['type_id']);
            $product->setType($type);
        }

        return $product;
    }

    public function findById($id)
    {
        if (isset($this->loaded[$id])) {
            return $this->loaded[$id];
        }
        // Get the data
        $row = $this->productTable->select(['id' => $id]);

        if (empty($row)) {
            throw new SomeCustomException("No product could be found with id $id");
        }
        // Create and hydrate the product
        $product = $this->hydrate(new Product, $row->current())
        $this->loaded[$id] = $product;

        return $product;
    }

    public function save(array $data);

    public function update($data);

    public function delete($id);
}