PHP:5层模型

标签 php design-patterns service model dao

我对如何“正确”编写我的应用程序有疑问。

我想遵循 service/DAO/mapper/entity 模型,我认为我完全理解它,但我发现我想做的一些事情可能与这个模型的一般概念相冲突。

我有这些数据库表: Product table and it's related table - manufacturers

我有一个简单的 ProductDbMapper,它从数据库中获取产品数据并将它们作为产品实体返回,因此我可以像这样使用它:

echo $productEntity->getName();
$productEntity->setPrice(29.9);

我认为能够使用这样的想法会很棒:

echo $productEntity->getManufacturer()->getName();

“getManufacturer()”方法将简单地通过 id 查询制造商的另一个服务/DAO。我知道如何获得产品制造商的正确方法是:

$product = $productService->getProduct(5);
$manufacturer = $manufacturerService->getManufacturerByProduct($product);

但我认为“流畅”的解决方案要简单得多 - 它易于理解且使用起来很有趣。其实这是很自然的。我试图通过回调来实现这一点。我将调用制造商服务的回调传递给 ProductMapper 中的 Product 实体。

问题是我似乎在尝试遵循 5 层模型,同时又在尝试避免它。所以我的问题是:这看起来是一个好的解决方案吗?是否有意义?我怎样才能以更好的方式实现相同的(魔法)?

最佳答案

如果您想坚持使用 Data Mapper 模式,您可以从数据库中加载产品及其所有依赖项(制造商、库存、税收),也可以使用延迟加载。第一个选项不是一个好的做法。但是要使用延迟加载,您需要通过虚拟代理获得一个额外的层。

为什么?因为否则你将不得不在你的实体中放置一些数据库代码,而关于这些层的整个想法就是解耦。

那么,什么是虚拟代理?

根据 Martin Fowler (2003) 的说法:

A virtual proxy is an object that looks like the object that should be in the field, but doesn't actually contain anything. Only when one of its methods is called does it load the correct object from the database.

例子

该接口(interface)定义了将由真实实体和虚拟代理实现的方法:

// The interface
interface ManufacturerInterface
{
    public function getName();
}

这是实体,您可能也在扩展一些通用模型类:

// The concrete Manufacturer class
class Manufacturer implements ManufacturerInterface
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

这是代理:

// The proxy
class ManufacturerProxy implements ManufacturerInterface
{
    private $dao;
    private $manufacturer;
    private $manufacturerId;

    public function __construct($dao, $manufacturerId)
    {
        $this->dao = $dao;

        // set manufacturer to NULL to indicate we haven't loaded yet
        $this->manufacturer = null;
    }

    public function getName()
    {
        if ($this->manufacturer === null)
        {
            $this->lazyLoad();
        }
        return $this->manufacturer->getName();
    }

    private function lazyLoad()
    {
        $this->manufacturer = $this->dao->getById($this->manufacturerId);
    }
}

当实例化与制造商有某种关系的其他类(如产品)时,数据映射器将使用代理。因此,当一个产品被实例化时,它的字段被填充并且制造商字段接收一个 ManufacturerProxy 实例而不是 Manufacturer。

Data Mapper 的实现会有所不同,所以我举一个简单的例子:

class ProductMapper {
    private $dao;
    private $manufacturerDao;

    // .......

    public function find($id) {
        // call the DAO to fetch the product from database (or other source)
        // returns an associative array
        $product = $this->dao->find($id);
        
        // instantiate the class with the associative array
        $obj = new Product($product);
        
        // create the virtual proxy
        $obj->manufacturer = new ManufacturerProxy($this->manufacturerDao, $product['manufacturer_id']);
        
        return $obj;
    }
}

正如我所说,上面的例子非常简单。我已经包含了您正在使用的 DAO,但是像 Martin Fowler 这样的作者将处理 SQL 查询或任何其他底层技术的任务交给了 Data Mapper。但也有像 Nock (2004) 这样的作者同时使用数据映射器和数据访问器。

使用更复杂的数据映射器,您只能拥有 XML 或 YAML 等语言的文件,类似于 Doctrine 或 Hibernate。

刚刚结束,服务:

class ProductService {
    private $productMapper;
    
    /**
     * Execute some service
     * @param {int} $productId The id of the product
     */
    public function someService($producId) {
        // get the product from the database
        // in this case the mapper is handling the DAO
        // maybe the data mapper shouldn't be aware of the DAO
        $product = $this->productMapper->find($productId);
        
        // now the manufacturer has been loaded from the database
        $manufacturerName = $product->manufacturer->getName();
    }
}

也许您可以让 DAO 调用数据映射器来创建实体,然后在服务中使用 DAO。它实际上更简单,因为您不需要重复两次方法,但这取决于您。

替代方案

如果你不想实现虚拟代理,并且仍然想要一个流畅的界面,你可以切换到 Active Record 模式。有一些图书馆可以完成这项工作,比如 PHP-ActiveRecordParis .或者想自己做的可以看herehere . Martin Fowler 的书也是一个好的开始。

引用资料

福勒,马丁。 企业应用架构模式。艾迪生 - 卫斯理出版社,2003 年。 诺克,克利夫顿。 数据访问模式:面向对象应用程序中的数据库交互。 Addison-Wesley,2004 年。

关于PHP:5层模型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13627570/

相关文章:

java - 调整 Builder 模式以进行方法调用

Grails 数据源在服务中变为空

php - 存储信息并拉出数组

php - 使用 __get() (魔术)模拟只读属性和延迟加载

JavaScript EventSource 监听推送到 PHP 脚本的 Webhook

design-patterns - 建厂的最佳方法

c++ - 代理模式 - 适用性和示例

java - Android 服务线程

android剪贴板广播接收器

php - curl php HTTP/2 流 0 没有完全关闭