Zend framework2 使用水合器创建复杂模型:聚合/策略

Zend framework2 使用水合器创建复杂模型:聚合/策略,zend-framework2,zend-hydrator,Zend Framework2,Zend Hydrator,我以前使用一个服务层构建了复杂的模型(一个包含许多其他类型模型的对象),并将不同的映射器作为构造函数传入 例如 以上是一个简化的例子。在我的领域中,我使用工厂中使用的多个服务来创建复杂的对象图 在阅读了关于聚合水合器的非常有趣的文章之后,我尝试采用这种方法来简化对象创建过程。我喜欢将RowObjectPrototype设置为要返回的对象来创建一个Ulset的想法 我想我需要一些关于如何在现实世界中实现这一点的建议。例如,当使用AggregateHydrator时,我可以根据传递到该聚合器的用户i

我以前使用一个服务层构建了复杂的模型(一个包含许多其他类型模型的对象),并将不同的映射器作为构造函数传入

例如

以上是一个简化的例子。在我的领域中,我使用工厂中使用的多个服务来创建复杂的对象图

在阅读了关于聚合水合器的非常有趣的文章之后,我尝试采用这种方法来简化对象创建过程。我喜欢将RowObjectPrototype设置为要返回的对象来创建一个Ulset的想法

我想我需要一些关于如何在现实世界中实现这一点的建议。例如,当使用AggregateHydrator时,我可以根据传递到该聚合器的用户id加载用户约会历史记录

class UserModelHydratorFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator) {
        $serviceManager = $serviceLocator->getServiceLocator();

        /**
         * Core hydration
         */
        $arrayHydrator = new ArraySerializable();
        $arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());

        $aggregateHydrator = new AggregateHydrator();
        $aggregateHydrator->add($arrayHydrator);
        $aggregateHydrator->add($serviceLocator->get('Hydrator\Address'));
        $aggregateHydrator->add($serviceLocator->get('Hydrator\Appointments'));
        return $aggregateHydrator;
    }
}
…例如,用户地址如下所示:

class UserAddressHydrator implements HydratorInterface{
    protected $locationMapper;

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

    public function hydrate(array $data, $object){
        if(!$object instanceof User){
            return;
        }

        if(array_key_exists('userId', $data)){
            $object->setAddress($this->locationMapper->findByClientId($data['userId']));
        }
        return $object;
    }
}
这很好用。虽然使用AggregateHydrator方法,但这意味着每一个具有地址作为属性的对象都需要它自己的hydrator。因此,如果我正在构建一个公司模型,那么需要另一个(几乎相同的)hydrator,它也有一个地址,因为上面的hydrator是硬编码的,用于填充用户模型(并且需要包含userId密钥的数据)。这意味着,对于每个关系/交互(has-a),都需要它自己的工具来生成它。这正常吗?所以我需要一个UserAddressHydrator,CompanyAddressHydrator,SupplierAddressHydrator,AppointmentAddressHydrator-所有这些都与上面的代码几乎相同,只是填充了一个不同的对象

如果只有一个addressor,它接受一个addressId并返回地址模型,那么会更简洁。这让我想到了一个更完美的方法或策略,尽管它们只对一个值起作用,因此不能通过传入数组查看pk/fk/Identification键是否存在并基于此加载


我希望能对这种方法进行一些澄清,我觉得我在这一过程中迷失了方向。

你完全正确。或策略只对与实体成员对应的单个值起作用。因此,你必须为你的补水器添加一些策略。另一方面,您可以继承
\Zend\hydractor\AbstractHydrator
并覆盖
addStrategy()
方法来处理具有多个名称的数组。使用该解决方案,您可以将相同的值设置为添加到
addStrategy()
的数组的值

简单水合器策略 这个例子展示了一个简单的水合器策略的使用

class UserHydratorFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $oServiceLocator)
    {
        $oHydrator = (new ClassMethods(false))
            ->addStrategy('address', new AddressHydratorStrategy())
            ->addStrategy('company', new AddressHydratorStrategy());

        return $oHydrator;
    }
}
这是用户实体的示例或工厂。在这个工厂中,调用一个普通的
ClassMethods
hydrator。这种类型的水合器假定实体中的getter和setter方法用于水合实体成员。此外,还为成员地址和公司添加了策略

class AddressHydratorStrategy extends DefaultStrategy
{
    public function hydrate($aData)
    {
        return (new ClassMethods(false))
            ->hydrate($aData, new AdressEntity())
    }
}
该策略只允许添加某种类型的子实体。如果数据中有地址或公司密钥,则地址实体将添加到这些成员中

class UserEntity
{
    protected $name;

    protected $address;

    protected $company;

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

    public function setName(string $sName) : UserEntity
    {
        $this-> name = $sName;
        return $this;
    }

    public function getAddress() : AddressEntity
    {
        return $this->address;
    }

    public function setAddress(AddressEntity $oAddress) : UserEntity
    {
        $this->address = $oAddress;
        return $this;
    }

    public function getCompany() : AddressEntity
    {
        return $this->company;
    }

    public function setCompany(AddressEntity $oCompany) : UserEntity
    {
        $this->company = $oCompany;
        return $this; 
    }
}
你注意到字体提示了吗?例如,
setAddress
方法将
AddressEntity
对象作为参数。这个对象将由我们添加到
ClassMethods
or的策略生成

class UserModelHydratorFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator) {
        $serviceManager = $serviceLocator->getServiceLocator();

        /**
         * Core hydration
         */
        $arrayHydrator = new ArraySerializable();
        $arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());

        $aggregateHydrator = new AggregateHydrator();
        $aggregateHydrator->add($arrayHydrator);
        $aggregateHydrator->add($serviceLocator->get('Hydrator\Address'));
        $aggregateHydrator->add($serviceLocator->get('Hydrator\Appointments'));
        return $aggregateHydrator;
    }
}
下一步是调用带有一些数据的对象,这些数据将生成一个嵌套复杂的
UserEntity
对象

$oUserHydrator = $this->getServiceLocator(UserHydrator::class);
$oUserHydrator->hydrate(
    [
        'name' => 'Marcel',
        'address' => 
        [
            'street' => 'bla',
            'zipcode' => 'blubb',
        ],
        'company' => 
        [
            'street' => 'yadda',
            'zipcode' => 'yadda 2',
        ],
    ], 
    new UserEntity()
);
结果集中的用法 为了让它更清楚,这里有一个小例子,我如何在结果集中直接使用补水剂和策略

class UserTableGateway extends TableGateway
{
    public function __construct(Adapter $oAdapter, $oUserHydrator)
    {
        $oPrototype = new HydratingResultSet(
            $oHydrator,
            new UserEntity()
        );

        parent::__construct('user_table', $oAdapter, null, $oPrototype);
    }

    public function fetchUser()
    {
        // here a complex join sql query is fired
        // the resultset is of the hydrated prototype
    }
}
在本例中,
TableGateway
类使用原型进行初始化,原型是
结果集
。它使用来自
UserHydratorFactory
的水合器。当复杂数据直接从数据库或其他源(如返回嵌套数据的webservice)中获取时,这种方法或策略是有意义的

结论
对我个人来说,使用水合器策略比使用聚合水合器更有意义。当然,您不能向策略中添加多个名称。否则,正如我在开始时所说,您可以根据您的需求覆盖继承类中的
addStrategy
方法。在我看来,水化或策略的编码更少。

谢谢你的回复。我同意策略方法似乎是一个更好的解决方案。但我有点困惑——如果我没有一个“地址”数据数组,而只有一个要加载的相关id,比如用于加载关联地址的clientId,该怎么办?在属性(例如“addressId”)上设置策略,甚至在clientId(通常为整数)上进行查找,如何返回对象?因此,在您的addressor示例中,我如何定义一个通用接口来允许任何对象从给定的Id/关联加载Address对象呢?我会走另一条路。我将在一个复杂的sql连接查询中获取所有数据,并直接创建
UserEntity
。您将直接从数据库中获得一个水合结果集。正如我看到的,您通过用户ID获得用户地址。在这种情况下,我将加入地址表并直接读取所有数据。通过将水合结果集设置为表格网关中的原型,所有工作都会自动完成。我已经用“结果集中的用法”部分编辑了我的答案。也许这会对你有所帮助。感谢你的回答和清晰的示例,但我实际上是从另一个角度来看待这个问题——使用应用程序层来构建对象图,而不是大型、复杂的查询,希望通过一种可重用的方式来附加每种类型的域对象。