我对如何“正确”编写我的应用程序有疑问。
我想关注服务/ DAO / mapper / entity模型,我认为我完全理解它,但我发现我想做的一些事情可能会与这个模型的一般概念相冲突。
我有这些数据库表:
我有简单的ProductDbMapper,它从数据库获取产品数据并将它们作为Product实体返回,所以我可以像这样使用它:
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层模型,同时我试图避免它。所以我的问题是:这看起来是一个很好的解决方案吗?是否有意义?我怎样才能以更好的方式实现同样的(魔术)?
答案 0 :(得分:9)
如果您想坚持使用Data Mapper模式,您可以从数据库加载产品及其所有依赖项(制造商,库存,税),也可以使用延迟加载。第一种选择不是一种好的做法。但是要使用延迟加载,您将需要通过虚拟代理获得的附加层。
为什么呢?因为否则你必须在你的实体中放入一些数据库代码,而关于这些层的整个想法是脱钩的。
根据Martin Fowler(2003)的说法:
虚拟代理是一个对象,看起来像应该在字段中的对象,但实际上并不包含任何内容。只有在调用其中一个方法时,才会从数据库中加载正确的对象。
接口定义了将由真实实体和虚拟代理实现的方法:
// 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的实例而不是制造商。
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)这样的作者同时使用数据映射器和数据访问器。
使用更复杂的Data Mapper,您只能使用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调用Data Mapper来创建实体,然后在服务中使用DAO。它实际上更简单,因为您不需要重复两次方法,但这取决于您。
如果您不想实现虚拟代理,并且仍然需要流畅的界面,则可以切换到Active Record模式。有些库可以完成这项工作,例如PHP-ActiveRecord和Paris。或者,如果您想自己动手,可以查看here和here。 Martin Fowler的这本书也是一个好的开始。
答案 1 :(得分:3)
我已经做了几天的研究,我得出的结论是,实现我想要的最好的方法是停止尝试自己编写代码并学会使用现有的ORM。因为我一直在努力做的就是创建自己的ORM。我知道这是个坏主意。我会坚持学说2。
答案 2 :(得分:1)
别误会我的意思,我不想成为批评家。为什么不加入这两个表然后只有一个类扩展另一个?就像你那样$this->manufacturer_name = $row['name']
。对于其他制造商的行也是如此。