PHP延迟加载对象和依赖注入

Iva*_*tar 18 php dependency-injection data-access lazy-loading

我最近开始阅读有关依赖注入的内容,它让我重新思考了我的一些设计.

我遇到的问题是:让我说我有两个班:汽车和乘客; 对于这两个类,我有一些数据映射器来处理数据库:CarDataMapper和PassengerDataMapper

我希望能够在代码中执行以下操作:

$car = CarDataMapper->getCarById(23); // returns the car object
foreach($car->getPassengers() as $passenger){ // returns all passengers of that car
    $passenger->doSomething();
}
Run Code Online (Sandbox Code Playgroud)

在我对DI有所了解之前,我会像这样构建我的类:

class Car {
    private $_id;
    private $_passengers = null;

    public function getPassengers(){
        if($this->_passengers === null){
            $passengerDataMapper = new PassengerDataMapper;
            $passengers = $passengerDataMapper->getPassengersByCarId($this->getId());
            $this->setPassengers($passengers);
        }
        return $this->_passengers;  
    }

}
Run Code Online (Sandbox Code Playgroud)

我也会在Passenger-> getCar()方法中使用类似的代码来获取乘客所在的汽车.

我现在明白,这会在Car和Passenger对象以及数据映射器对象之间创建依赖关系(好吧,我之前也理解它,但我不知道这是"错误的").

虽然想到了解决这两个选项的解决方案,但我真的不喜欢它们中的任何一个:

1:做这样的事情:

$car = $carDataMapper->getCarById(23);
$passengers = $passengerDataMapper->getPassengersByCarId($car->getId());
$car->setPassengers($passengers);

foreach($car->getPassengers() as $passenger){
    $passenger->doSomething();
}
Run Code Online (Sandbox Code Playgroud)

但是如果乘客有需要注入的对象怎么办呢?如果嵌套进入十到二十级呢?我最终会在我的应用程序开始时实例化几乎所有对象,而这会反过来查询整个数据库.处理.如果我必须将乘客送到另一个必须对乘客拥有的物体做某事的物体,我也不想立即实例化这些物体.

2:将数据映射器注入汽车和乘客对象,并具有以下内容:

class Car {
    private $_id;
    private $_passengers = null;
    private $_dataMapper = null;

    public function __construct($dataMapper){
        $this->setDataMapper($dataMapper);
    }

    public function getPassengers(){
        if($this->_passengers === null && $this->_dataMapper instanceof PassengerDataMapper){
            $passengers = $this->_dataMapper->getPassengersByCarId($this->getId());
            $this->setPassengers($passengers);
        }
        return $this->_passengers;  
    }
}
Run Code Online (Sandbox Code Playgroud)

我不喜欢这样,因为它不像汽车真的没有意识到数据映射器,并且没有数据映射器,汽车可能会出现不可预测的行为(当它实际拥有它们时不会返回乘客)

所以我的第一个问题是:我在这里采取了一种完全错误的方法,因为我看得越多,看起来我正在建立一个ORM,而不是一个业务层?

第二个问题是:有没有一种方法可以实现将对象和数据映射器分离,从而允许我使用第一个代码块中描述的对象?

第三个问题:我已经看到了其他语言(我认为某些版本的C)的一些答案,解决了这个问题,如下所述: 为延迟加载注入数据访问依赖的正确方法是什么? 由于我没有时间玩其他语言,这对我来说没有任何意义,所以如果有人在PHP-ish中解释链接中的示例,我将不胜感激.

我还查看了一些DI框架,并阅读了有关DI容器和控制反转的内容,但据我所知,它们用于定义和注入"非动态"类的依赖关系,例如,汽车将依赖于引擎,但它不需要从db动态加载引擎,它只是被实例化并注入到Car中.

很抱歉这篇冗长的帖子,并提前致谢.

ale*_*tzo 10

也许偏离主题,但我认为它会对你有所帮助:

我认为你试图实现完美的解决方案.但无论你想出什么,在几年内,你将会更有经验,你一定能够改进你的设计.

在过去的几年里,我和同事一起开发了许多ORM /商业模式,但几乎每个新项目我们都是从头开始,因为每个人都经验丰富,每个人都从以前的错误中吸取教训,每个人都遇到了新的模式和想法.所有这些都增加了一个月左右的开发时间,这增加了最终产品的成本.

无论工具有多好,关键问题是最终产品必须以最低成本尽可能好.客户不会关心也不会为无法看到或理解的东西付费.

当然,除非你为研究或乐趣编码.

TL; DR:你未来的自我将总是超越你现在的自我,所以不要过分思考它.只需仔细挑选一个有效的解决方案,掌握它并坚持下去,直到它无法解决您的问题:D

回答你的问题:

  1. 你的代码非常好,但你会越努力使它"聪明"或"抽象"或"无依赖",你就越倾向于ORM.

  2. 你想要的第一个代码块是非常可行的.看看Doctrine ORM的工作原理,或者几个月前我为周末项目做的非常简单的ORM方法:

https://github.com/aletzo/dweet/blob/master/app/models


tue*_*tre 5

我打算说"我知道这是一个古老的问题,但是......"然后我意识到你在9小时前发布了它,这很酷,因为我刚刚为自己找到了一个令人满意的"解决方案".我想到了实现,然后我意识到人们称之为'依赖注入'.

这是一个例子:

class Ticket {

    private $__replies;
    private $__replyFetcher;
    private $__replyCallback;
    private $__replyArgs;

    public function setReplyFetcher(&$instance, $callback, array $args) {
        if (!is_object($instance))
            throw new Exception ('blah');
        if (!is_string($callback))
            throw new Exception ('blah');
        if (!is_array($args) || empty($args))
            throw new Exception ('blah');
        $this->__replyFetcher = $instance;
        $this->__replyCallback = $callback;
        $this->__replyArgs = $args;
        return $this;
    }

    public function getReplies () {
        if (!is_object($this->__replyFetcher)) throw new Exception ('Fetcher not set');
        return call_user_func_array(array($this->__replyFetcher,$this->__replyCallback),$this->__replyArgs);
    }

}
Run Code Online (Sandbox Code Playgroud)

然后,在您的服务层(您可以协调多个映射器和模型之间的操作)中,您可以在将所有票对象调用到调用服务层的任何内容之前调用所有票对象上的'setReplyFetcher'方法 - 或者 - 您通过为映射器提供对象所需的每个映射器的私有'fetcherInstance'和'callback'属性,然后在服务层中设置THAT,然后映射器将处理,可以对每个映射器执行非常类似的操作准备对象.我仍在权衡这两种方法之间的差异.

在服务层中协调的示例:

class Some_Service_Class {
    private $__mapper;
    private $__otherMapper;
    public function __construct() {
        $this->__mapper = new Some_Mapper();
        $this->__otherMapper = new Some_Other_Mapper();
    }
    public function getObjects() {
        $objects = $this->__mapper->fetchObjects();
        foreach ($objects as &$object) {
            $object->setDependentObjectFetcher($this->__otherMapper,'fetchDependents',array($object->getId()));
        }
        return $objects;
    }
}
Run Code Online (Sandbox Code Playgroud)

无论哪种方式,对象类都独立于映射器类,映射器类彼此独立.

编辑:这是另一种方法的例子:

class Some_Service {
    private $__mapper;
    private $__otherMapper;
    public function __construct(){
        $this->__mapper = new Some_Mapper();
        $this->__otherMapper = new Some_Other_Mapper();
        $this->__mapper->setDependentFetcher($this->__otherMapper,'someCallback');
    }
    public function fetchObjects () {
        return $this->__mapper->fetchObjects();
    }        
}

class Some_Mapper {
    private $__dependentMapper;
    private $__dependentCallback;
    public function __construct ( $mapper, $callback ) {
        if (!is_object($mapper) || !is_string($callback)) throw new Exception ('message');
        $this->__dependentMapper = $mapper;
        $this->__dependentCallback = $callback;
        return $this;
    }
    public function fetchObjects() {
        //Some database logic here, returns $results
        $args[0] = &$this->__dependentMapper;
        $args[1] = &$this->__dependentCallback;
        foreach ($results as $result) {
            // Do your mapping logic here, assigning values to properties of $object
            $args[2] = $object->getId();
            $objects[] = call_user_func_array(array($object,'setDependentFetcher'),$args)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,映射器要求其他资源可用于甚至实例化.您还可以看到,使用此方法,您只能使用对象ID作为参数调用映射器函数.我确信有些人会坐下来认为有一个优雅的解决方案可以包含其他参数,例如提取"开放"门票与属于部门对象的"关闭"门票.