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

时间:2017-06-21 22:27:29

标签: zend-framework2 zend-hydrator

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

例如。

class UserService{
    public function __construct(UserMapper $userMapper, AddressMapper $addressMapper, AppointmentsMapper $appointmentsMapper){}
    public function loadById($id) : User {
        $user = $this->userMapper->find($id);
        $appointments = $this->appointmentsMapper->findByUser($user);
        $user->setAppointments($appointments);
        $address = $this->addressMapper->findByUser($user);
        $user->setAddress($address);
        //..etc..
    }
}

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

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

我想我需要一些关于如何在现实世界中完成这项工作的建议。例如,当使用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方法,但这意味着每个具有Address作为属性的对象意味着它将需要它自己的水合器。因此,如果我正在构建一个也具有地址的公司模型,那么将需要另一个(几乎相同的)水化器,因为上面的Hydrator被硬编码以填充用户模型(并且期望包含userId键的数据)。这意味着对于每个关系/交互(has-a)将需要它自己的水合器来生成它。这是正常的吗?所以我需要一个UserAddressHydrator,CompanyAddressHydrator,SupplierAddressHydrator,AppointmentAddressHydrator - 几乎与上面的代码完全相同,只是填充一个不同的对象?

拥有一个AddressHydrator会更加清晰,它接受addressId并返回Address模型。这让我看看看起来很完美的保温策略,虽然它们只能处理单个值,所以不能通过传入的数组来查看是否存在pk / fk /识别键并基于该加载

我很欣赏这种方法的一些澄清,感觉就像我一路上迷路了。

1 个答案:

答案 0 :(得分:2)

你是对的。 Hydrator策略仅适用于与您的实体成员对应的单个值。因此,您必须为您的保湿器添加几种策略。另一方面,您可以继承\Zend\Hydrator\AbstractHydrator并覆盖addStrategy()方法以处理具有多个名称的数组。使用该解决方案,您可以将相同的Hydrator设置为您添加到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水合物。这种类型的水化器在您的实体中采用吸气剂和固定器方法来对实体成员进行水力分析。此外,还为成员地址和公司添加了策略。

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保水器的策略生成。

接下来跟着水化器调用一些数据会产生一个nestet复杂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类使用Prototype初始化,该类型为HydratingResultSet。它使用UserHydratorFactory中的保湿剂。当从数据库或其他来源(如返回嵌套数据的Web服务)直接获取复杂数据时,Hydrator策略是有意义的。

结论

对我来说,亲自使用保湿剂策略比使用聚合物保湿剂更有意义。当然,您可以为策略添加不多于一个名称。否则,您可以像我在开头所说的那样,根据您的要求覆盖继承类中的addStrategy方法。保湿策略在我眼中的编码较少。