zf2 - 在没有servicemanager的mapper中创建模型(带有依赖项)

时间:2016-03-18 00:20:27

标签: php dependency-injection zend-framework2

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

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

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时都返回对象,但依赖项被传递给构造函数。随着模型传递到构造函数中,我可以克隆对象,例如。

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

...但是它的某些东西似乎不对,代码闻起来了?

1 个答案:

答案 0 :(得分:1)

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

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

实体应该在没有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

Buniness Logic

建议不要将任何与模型相关的业务逻辑直接放入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;
    }
}

数据对象

现在让我们创建包含数据普通旧PHP对象的对象,不受任何限制的约束。这意味着它们没有任何逻辑耦合,我们可以在任何地方使用它们。例如,如果我们决定用另一个像Doctrine那样替换我们的ORM。

class User
{
    protected $name;

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

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

有关Plain Old PHP Objects概念的更多信息可以在Wikipedia's explanation of POJO上找到。