资讯专栏INFORMATION COLUMN

Laravel Dependency Injection (依赖注入) 概念详解

Fundebug / 2857人阅读

摘要:依赖注入并不限于构造函数作为经验,注入最适合必须的依赖关系,比如示例中的情况注入最适合可选依赖关系,比如缓存一个对象实例。

本文翻译自 Symfony 作者 Fabien Potencier 的 《Dependency Injection in general and the implementation of a Dependency Injection Container in PHP》 系列文章。

Part 1: What is Dependency Injection?

Part 2: Do you need a Dependency Injection Container?

Part 3: Introduction to the Symfony Service Container

Part 4: Symfony Service Container: Using a Builder to create Services

Part 5: Symfony Service Container: Using XML or YAML to describe Services

Part 6: The Need for Speed

依赖注入 设计模式非常简单,但又很难解释清楚。造成这个现象的主要原因是,别的介绍 依赖注入 的文章里太多废话,让人混淆。下面我将通过一些更适合 PHP 的例子来讲解它。

HTTP 协议是无状态的,我们的 Web 应用程序如果需要在请求之间存储用户信息,可以通过 COOKIESESSION

$_SESSION["language"] = "fr";

上述代码中,我们将 language 存储在全局变量 $_SESSION 中,因此可以这样获取它:

$user_language = $_SESSION["language"];

只有在 OOP 开发时中才会遇到 依赖注入 ,因此假设我们有一个封装 SESSIONSessionStorage 类:

class SessionStorage
{
    function __construct($cookieName="PHPSESSID")
    {
        session_name($cookieName);
        session_start();
    }

    function set($key, $value)
    {
        $_SESSION[$key] = $value;
    }

    function get($key)
    {
        return $_SESSION[$key];
    }

    // ...
}

以及一个更高层的 User 类:

class User
{
    protected $storage;

    function __construct()
    {
        $this->storage = new SessionStorage();
    }

    function setLanguage($language)
    {
        $this->storage->set("language", $language);
    }

    function getLanguage()
    {
        return $this->storage->get("language");
    }

    // ...
}

这两个类很简单,并且用起来也很方便:

$user = new User();
$user->setLanguage("fr");
$user_language = $user->getLanguage();

这种方式看起来很完美,但是并不够灵活。比如:现在想修改会话的 COOKIE 名称(默认为 PHPSESSID) ,怎么办?这时有一大堆办法:

把会话的 COOKIE 名称写死在 User 类中 SessionStorage 的构造函数中 (Hardcode):

class User
{
    function __construct()
    {
        $this->storage = new SessionStorage("SESSION_ID");
    }

    // ...
}

或者在 User 类外面定义一个常量:

class User
{
    function __construct()
    {
        $this->storage = new SessionStorage(SESSION_COOKIE_NAME);
    }

    // ...
}

define("SESSION_COOKIE_NAME", "SESSION_ID");

或者把会话的 COOKIE 名称作为 User 类构造函数的一个参数传进去:

class User
{
    function __construct($cookieName)
    {
        $this->storage = new SessionStorage($cookieName);
    }

    // ...
}

$user = new User("SESSION_ID");

或者给 SessionStorage 类加个选项数组:

class User
{
    function __construct($storageOptions)
    {
        $this->storage = new SessionStorage($storageOptions["cookie_name"]);
    }

    // ...
}

$user = new User(["cookie_name" => "SESSION_ID"]);

上述方法都很糟糕:

把会话的 COOKIE 名称写死的话,每次想再改名,都要修改 User

使用常量的话,User 类的变化将取决于设置的常量

使用参数或者选项数组看起来很灵活,但它把与 User 本身无关的东西掺杂在了构造函数中

通过构造函数,把一个外部的 SessionStorage 实例"注入"进 User 实例内部,而不是在 User 实例内部创建 SessionStorage 实例,就是 依赖注入
class User
{
    function __construct($storage)
    {
        $this->storage = $storage;
    }

    // ...
}

很清爽吧!只需先创建 SessionStorage 实例,再创建 User 实例:

$storage = new SessionStorage("SESSION_ID");
$user = new User($storage);

用这个方法,配置 SessionStorage 很简单,给 User 替换 $storage 类型也很简单,都不需要去修改 User 类。这就实现了解耦。

依赖注入 并不限于构造函数:

Constructor Injection:

class User
{
    function __construct($storage)
    {
        $this->storage = $storage;
    }

    // ...
}

Setter Injection:

class User
{
    function setSessionStorage($storage)
    {
        $this->storage = $storage;
    }

    // ...
}

Property Injection:

class User
{
    public $sessionStorage;
}

$user->sessionStorage = $storage;

作为经验, Constructor 注入 最适合必须的依赖关系,比如示例中的情况; Setter 注入 最适合可选依赖关系,比如缓存一个对象实例。

现在,大多数现代 PHP 框架都大量使用依赖注入来提供一组 去耦粘合 的组件:

// symfony: A constructor injection example
$dispatcher = new sfEventDispatcher();
$storage = new sfMySQLSessionStorage([
    "database" => "session",
    "db_table" => "session",
]);
$user = new sfUser($dispatcher, $storage, ["default_culture" => "en"]);

// Zend Framework: A setter injection example
$transport = new Zend_Mail_Transport_Smtp("smtp.gmail.com", [
  "auth"     => "login",
  "username" => "foo",
  "password" => "bar",
  "ssl"      => "ssl",
  "port"     => 465,
]);

$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);

如果你有兴趣了解更多有关 依赖注入 的信息,我强烈建议你阅读 Martin Fowler 的介绍 或 Jeff Moore 的 PPT。你还可以看看我去年关于 依赖注入 的 PPT,其中我详细了介绍本文中所讨论的示例。

希望本文能让你更好的理解 依赖注入,在本系列的下一部分中,我将讨论 依赖注入容器 (Dependency Injection Containers)。

原创。 所有 Laravel 文章均已收录至 laravel-tips 项目。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/25954.html

相关文章

  • Laravel Container (容器) 概念详解 (上)

    摘要:上文书,创建对象需要先创建对象。创建对象的杂活是嵌入在中的。对象使用来管理依赖关系非常好,但不是必须的。很容易实现,但手工维护各种乱七八糟的对象还是很麻烦。所有文章均已收录至项目。 本文翻译自 Symfony 作者 Fabien Potencier 的 《Dependency Injection in general and the implementation of a Depend...

    FullStackDeveloper 评论0 收藏0
  • Laravel Container (容器) 深入理解 (下)

    摘要:意味着依赖被注入进构造函数或者方法如果需要复用实例,可以定义为单例可以用接口或任何名称来代替具体类。技能重写构造函数参数方法允许将附加参数传递给构造函数。 本文大部分翻译自 DAVE JAMES MILLER 的 《Laravel’s Dependency Injection Container in Depth》 。 上文介绍了 Dependency Injection Contai...

    eternalshallow 评论0 收藏0
  • 深入理解IoC(控制反转)、DI(依赖注入

    摘要:引述最近看设计模式以及代码,对于控制反转以及依赖注入这些概念非常困惑,于是找了一些资料,以下是对于控制反转的一下理解。其中最常见的方式叫做依赖注入,简称,还有一种方式叫依赖查找。在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。 引述 最近看设计模式以及laravel代码,对于控制反转以及依赖注入这些概念非常困惑,于是找了一些资料,以下是对于控制反转的一下理解。 概念 Io...

    xcc3641 评论0 收藏0
  • 深入剖析 Laravel 服务容器

    摘要:划下重点,服务容器是用于管理类的依赖和执行依赖注入的工具。类的实例化及其依赖的注入,完全由服务容器自动的去完成。 本文首发于 深入剖析 Laravel 服务容器,转载请注明出处。喜欢的朋友不要吝啬你们的赞同,谢谢。 之前在 深度挖掘 Laravel 生命周期 一文中,我们有去探究 Laravel 究竟是如何接收 HTTP 请求,又是如何生成响应并最终呈现给用户的工作原理。 本章将带领大...

    abson 评论0 收藏0
  • React 源码深度解读(六):依赖注入

    摘要:依赖注入和控制反转,这两个词经常一起出现。一句话表述他们之间的关系依赖注入是控制反转的一种实现方式。而两者有大量的代码都是可以共享的,这就是依赖注入的使用场景了。下一步就是创建具体的依赖内容,然后注入到需要的地方这里的等于这个对象。 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级...

    glumes 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<