3

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

例如。

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 时,我可以根据传递给 hydrator 的用户 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)都需要它自己的 hydrator 来生成它。这正常吗?所以我需要一个 UserAddressHydrator、CompanyAddressHydrator、SupplierAddressHydrator、AppointmentAddressHydrator - 都与上述代码几乎相同,只是填充不同的对象?

拥有一个 AddressHydrator 会更简洁,它接受一个 addressId 并返回 Address 模型。这导致我查看 hydrator 策略,这似乎是完美的,尽管它们仅适用于单个值,因此无法查看传入数组以查看是否存在 pk/fk / 识别键并基于该值加载.

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

4

1 回答 1

3

你是绝对正确的。Hydrator 策略仅适用于与您的实体成员对应的单个值。因此,您必须在保湿器中添加几种策略。另一方面,您可以继承\Zend\Hydrator\AbstractHydrator并覆盖addStrategy()处理具有多个名称的数组的方法。使用该解决方案,您可以将相同的 Hydrator 设置为您添加到的数组的值addStrategy()

简单的保湿策略

这个例子展示了一个简单的 hydrator 策略的使用。

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对象作为参数。该对象将由我们添加到ClassMethodshydrator 的策略生成。

接下来调用 hydrator 调用一些数据,这将导致一个嵌套复杂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 进行初始化,该 Prototype 是HydratingResultSet. 它使用UserHydratorFactory. 当复杂数据直接从数据库或其他来源(如 Web 服务)中获取时,Hydrator 策略是有意义的,它返回嵌套数据。

结论

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

于 2017-06-22T10:37:41.747 回答