zf2 - 在没有 servicemanager 的映射器中创建模型(具有依赖关系)

zf2 - creating models (with dependencies) in mapper without servicemanager

在我之前 post 从我的 zf2 应用程序中删除 ServiceLocatorAwareInterface 之后,我现在面临着一个难题,涉及在使用数据映射器时创建对象。

我的数据映射器的当前实现使用表网关来查找特定行,调用服务管理器来获取域对象,然后填充并 returns 完整对象。

public function findById($userId){
        $rowset = $this->gateway->select(array('id' => $userId));
        $row = $rowset->current();
        if (!$row) {
            throw new \DomainException("Could not find user with id of $userId in the database");
        }
        $user = $this->createUser($row);
        return $user;
    }

public function createUser($data){
        $userModel = $this->getServiceManager()->get('Model\User');
        $hydrator = $this->getHydrator();
        if($data instanceof \ArrayObject){
            $hydrator->hydrate($data->getArrayCopy(), $userModel);
        }else{
            $hydrator->hydrate($data, $userModel);
        }
        return $userModel;
    }

模型需要从服务管理器调用,因为它有其他依赖项,所以从映射器内部调用 $user = new App\Model\User() 不是一个选项。

但是,现在我要从我的代码中删除 servicemanager 的实例,我不确定将模型放入映射器的最佳方法。显而易见的答案是在构造函数中传递它并将实例保存为映射器的 属性:

 public function __construct(TableGateway $gateway, \App\Model\User $userModel){
        $this->_gateway = $gateway;
        $this->_userModel= $userModel;
    }

    public function createUser($data){
        $userModel = $this->_userModel;
        //....snip....
    }

这在一定程度上有效,但随后对 createUser 的多次调用(例如,在查找所有用户时)会用最后一个对象数据覆盖每个实例(如预期的那样,但不是什么我要)

所以我需要在每次调用 createUser 时返回一个 "new" 对象,但是依赖项被传递到构造函数中。通过将模型传递给构造函数,我可以克隆对象,例如。

    public function createUser($data){
        $userModel = clone $this->_userModel
        //....snip....
    }

...但它似乎有些不对,代码味道?

你是对的,它闻起来不好。

设计 ORM 并不容易。关于 ORM 的设计方式的讨论一直存在,而且可能永远都会存在。现在,当我试图理解您的设计时,我注意到您指出您的模型包含数据但也有 "other" 依赖项。这是错误的,包含您的数据的模型应该在您的应用程序中没有任何层的情况下工作。

实体应该在没有 ORM 的情况下工作

在我看来,您应该将业务逻辑(依赖项)与数据分开。这将有很多好处:

  • 更具表现力
  • 更容易测试
  • 更少耦合
  • 更灵活
  • 更容易重构

有关如何设计 ORM 层的更多信息,我强烈建议浏览 these slides

DataMaper

UserMapper 负责将内存中的对象(仅包含数据)与数据库分离。

class UserMapper
{
    protected $gateway;
    protected $hydrator;        

    public function __construct(TableGateway $gateway, HydratorInterface $hydrator)
    {
        $this->gateway = $gateway;
        $this->hydrator = $hydrator;
    }

    public function findOneById($id)
    {
        $rowset = $this->_gateway->select(array('id' => $id));
        $row = $rowset->current();

        if(!$row) {
            throw new \DomainException("Could not find user with id of $id in the database.");
        }
        $user = new User;
        $this->hydrator->hydrate($row, $user);
        return $user;
    }

    public function findManyBy(array $criteria)
    {
       // $criteria would be array('colum_name' => 'value')
    }

    public function save(User $user)
    {
        $data = $this->hydrator->extract($user);
        // ... and save it using the $gateway.
    }
}

有关数据映射者责任的更多信息,请查看 Martin Fowler's definition

业务逻辑

建议不要将任何与模型相关的业务逻辑直接放入 Controller。因此,让我们创建一个简单的 UserService 来处理验证。如果您喜欢表单对象,您也可以在此过程中使用 Zend\Form\Form

class UserService
{
    protected $inputFilter;
    protected $hydrator;

    public function __construct(InputFilter $inputFilter, HydratorInterface $hydrator)
    {
        $this->inputFilter = $inputFilter;
        $this->hydrator = $hydrator;
    }

    protected function validate(array $data)
    {
        // Use the input filter to validate the data;
    }

    public function createUser(array $data)
    {
        $validData = $this->validate($data);
        $user = new User;
        $this->hydrator->hydrate($validData, $user);
        return $user;
    }
}

数据对象

现在让包含数据的对象 Plain Old PHP Objects,不受任何限制。这意味着它们不与任何逻辑耦合,我们可以在任何地方使用它们。例如,如果我们决定用另一个类似 Doctrine 的 ORM 替换我们的 ORM。

class User
{
    protected $name;

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

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

有关 Plain Old PHP 对象概念的更多信息,请参见 Wikipedia's explanation of POJO