依赖注入,其中每个类都依赖于其他几个类

Ale*_*kin 5 php oop dependency-injection

我正在尝试在学习PHP OOP的同时实现最佳实践.我理解这个概念,但对正确的实施有点怀疑.在我试图找出基本的实现原理时,我没有在这段代码中实现DI容器.

结构体

  • 用于数据库连接的Db类.

  • 设置类,从db检索设置.

  • 语言类,检索特定语言的信息.

  • 页面类,产品类,客户类等等.

理念

设置类需要Db类来检索设置.

语言类需要DbSettings才能根据数据库中的设置检索信息.

页面类需要Db,设置语言.它可能还需要一些其他课程.

简化代码

Db.php扩展了PDO

的settings.php

class Settings
{
    /* Database instance */
    protected $db;

    /* Cached settings */
    private $settings   = array();

    public function __construct(Db $db)
    {
        $this->db = $db;
    }

    public function load ()
    {
        $selq = $this->db->query('SELECT setting, value FROM settings');
        $this->settings = $selq->fetchAll();
    }
}
Run Code Online (Sandbox Code Playgroud)

Languages.php

class Languages
{

    public $language;

    protected $db;
    protected $settings;

    private $languages = array();

    public function __construct(Db $db, Settings $settings)
    {
        $this->db = $db;
        $this->settings = $settings;
        // set value for $this->language based on user choice or default settings
        ...
    }

    public function load() 
    {
        $this->languages = array();
        $selq = $this->db->query('SELECT * FROM languages');
        $this->languages = $selq->fetchAll();
    }

}
Run Code Online (Sandbox Code Playgroud)

page.php文件

class Page
{
    protected $db;
    protected $settings;
    protected $language;

    public function __construct(Db $db, Settings $settings, Languages $languages)
    {
        $this->db = $db;
        $this->settings = $settings;
        $this->languages = $languages;
    }

    public function load() 
    {
        // load page info from db with certain settings and in proper language
        ...
    }

}
Run Code Online (Sandbox Code Playgroud)

config.php文件

$db = new Db;

/* Load all settings */
$settings   = new Settings($db);
$settings->load();

/* Load all languages */
$languages  = new Languages($db, $settings);
$languages->load();

/* Instantiate page */
$page   = new Page($db, $settings, $languages);
Run Code Online (Sandbox Code Playgroud)

我不喜欢一遍又一遍地注入相同类的想法.通过这种方式,我将达到这一点,我需要注入10个类.所以,我的代码从一开始就错了.

也许,更好的方法是执行以下操作:

config.php文件

$db = new Db;

/* Load all settings */
$settings   = new Settings($db);
$settings->load();

/* Load all languages */
$languages  = new Languages($settings);
$languages->load();

/* Instantiate page */
$page   = new Page($languages);
Run Code Online (Sandbox Code Playgroud)

因为设置已经可以访问$ db和$ db和$ settings的语言.但是以这种方式,我将不得不拨打电话,如$ this-> languages-> settings-> db - > ...

我的所有代码架构似乎都是完全错误的:)应该怎么做?

Ale*_*kin 5

我将尝试回答我自己的问题,正如我在研究了大量材料后所看到的那样。

1. 最佳实践是创建如下对象:

$db = new Db();

$settings = new Settings ($db);

$languages = new Languages ($db, $settings);

// ...
Run Code Online (Sandbox Code Playgroud)

2.使用DI容器。

如果你不能写一个,使用现有的。有一些,他们称自己为 DI 容器而不是他们,比如 Pimple(在这个网站上有几篇关于这个的帖子)。有些往往更慢、更复杂(Zend、Symfony),然后是其他,但也提供了更多的功能。如果您正在阅读本文,那么您可能应该选择更简单的一个,例如 Aura、Auryn、Dice、PHP-DI(按字母顺序)。同样重要的是要知道,适当的 DI 容器(如我所见)应该具有递归遍历依赖项的能力,这意味着找到某个对象所需的依赖项。它们还应该提供共享相同对象的能力(如 $db 实例)。

3.在尝试动态创建对象时(如果您使用前端控制器和路由),手动注入依赖项会给您带来很多问题。这就是为什么请参阅第 2 点。

在这里看到很好的例子:

https://github.com/rdlowrey/Auryn#user-content-recursive-dependency-instantiation

https://github.com/rdlowrey/Auryn#instance-sharing

观看视频:

https://www.youtube.com/watch?v=RlfLCWKxHJ0 (它不是 PHP,但试着理解这个想法)


Tom*_*Tom 0

如果您在另一个对象内使用一个对象的依赖关系,那么您将在这两个组件之间创建依赖关系。这意味着,如果您需要更改Settings类的依赖关系,则会破坏依赖于您的Settings类的所有内容。

依赖注入库

如果您担心每次都必须手动构建新对象,请查看依赖注入库来处理对象创建(例如PimpleAuraPHP DI)。

使用痘痘:

// Define this once in your bootstrap
use Pimple\Container;
$container = new Container()

$container['Db'] = function ($c) {
    return new Db();
};

$container['Settings'] = function ($c) {
    return new Settings($c['Db']);
};

$container['Languages'] = function ($c) {
    return new Languages($c['Db'], $c['Settings']);
};

$container['Page'] = function ($c) {
    return new Page($c['Db'], $c['Settings'], $c['Languages']);
};


// Where ever you have access to your $container you can use this
// (and it knows how to build your object for you every time).
$page = $container['Page'];
Run Code Online (Sandbox Code Playgroud)

使用访问器注入依赖项

您可以使用访问器函数来设置依赖关系:

class Settings {
    protected $db;

    function setDb(Db $db) {
        $this->db = $db;
    }

    // ...
}

$settings = new Settings();
$settings->setDb(new Db());
Run Code Online (Sandbox Code Playgroud)

AuraPHP DI支持使用访问器进行依赖注入。

use Aura\Di\Container;
use Aura\Di\Factory;

$di = new Container(new Factory());
$di->set('db', new Db());
$di->set('settings', new Settings());
$di->setter['settings']['setDb'] = $di->get('db');
Run Code Online (Sandbox Code Playgroud)

您可以进一步扩展它,并通过基于您扩展的接口进行注入,自动注入您不想在每个类(如PSR-3 Logger )上手动设置的常见依赖项。

use Aura\Di\Container;
use Aura\Di\Factory;
use Psr\Log\LoggerAwareTrait;

class Db {
    use LoggerAwareTrait;
    // ...
}

$di = new Container(new Factory());
$di->set('logger', new MyCustomLogger());
$di->set('db', new Db());
$di->setter['LoggerAwareTrait']['setLogger'] = $di->get('logger');

// $db->logger will contain an instance of MyCustomLogger
// so will any other class that uses LoggerAwareTrait
$db = $di->get('db');
Run Code Online (Sandbox Code Playgroud)

单一责任原则

当然,如果您的类有 10 个不同的依赖项,那么问题可能出在设计上。一个类应该有单一的职责

可能违反单一责任原则的类的症状:

  • 类有很多实例变量
  • 该类有很多公共方法
  • 类的每个方法都使用其他实例变量
  • 具体任务委托给私有方法

摘自Matthias Noback 的《包装设计原理》