资讯专栏INFORMATION COLUMN

为什么我们需要 Laravel IoC 容器?

xiaokai / 2678人阅读

摘要:哲学的一个重要组成部分就是容器,也可以称为服务容器。那我们要怎么做呢请看下面的例子数据库连接通过上面的代码,如果我们想把改成,根本不需要去修改类构造函数里的依赖。现在我要讲下容器里到底发生了什么。

</>复制代码

  1. IOC 容器是一个实现依赖注入的便利机制 - Taylor Otwell

    文章转自:https://learnku.com/laravel/t...

Laravel 是当今最流行、最常使用的开源现代 web 应用框架之一。它提供了一些独特的特性,比如 Eloquent ORM, Query 构造器,Homestead 等时髦的特性,这些特性只有 Laravel 中才有。

我喜欢 Laravel 是由于它犹如建筑风格一样的独特设计。Laravel 的底层使用了多设计模式,比如单例、工厂、建造者、门面、策略、提供者、代理等模式。随着本人知识的增长,我越来越发现 Laravel 的美。Laravel 为开发者减少了苦恼,带来了更多的便利。

学习 Laravel,不仅仅是学习如何使用不同的类,还要学习 Laravel 的哲学,学习它优雅的语法。Laravel 哲学的一个重要组成部分就是 IoC 容器,也可以称为服务容器。它是一个 Laravel 应用的核心部分,因此理解并使用 IoC 容器是我们必须掌握的一项重要技能。

IoC 容器是一个非常强大的类管理工具。它可以自动解析类。接下来我会试着去说清楚它为什么如此重要,以及它的工作原理。

首先,我想先谈下依赖反转原则,对它的了解会有助于我们更好地理解 IoC 容器的重要性。

该原则规定:

</>复制代码

  1. 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。

    抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

一言以蔽之: 依赖于抽象而非具体

</>复制代码

  1. class MySQLConnection
  2. {
  3. /**
  4. * 数据库连接
  5. */
  6. public function connect()
  7. {
  8. var_dump(‘MYSQL Connection’);
  9. }
  10. }
  11. class PasswordReminder
  12. {
  13. /**
  14. * @var MySQLConnection
  15. */
  16. private $dbConnection;
  17. public function __construct(MySQLConnection $dbConnection)
  18. {
  19. $this->dbConnection = $dbConnection;
  20. }
  21. }

大家常常会有一个误解,那就是依赖反转就只是依赖注入的另一种说法。但其实二者是不同的。在上面的代码示例中,尽管在 PasswordReminder 类中注入了 MySQLConnection 类,但它还是依赖于 MySQLConnection 类。

然而,高层次模块 PasswordReminder 是不应该依赖于低层次模块 MySQLConnection 的。

如果我们想要把 MySQLConnection 改成 MongoDBConnection,那我们就还得手动修改 PasswordReminder 类构造函数里的依赖。

PasswordReminder 类应该依赖于抽象接口,而非具体类。那我们要怎么做呢?请看下面的例子:

</>复制代码

  1. interface ConnectionInterface
  2. {
  3. public function connect();
  4. }
  5. class DbConnection implements ConnectionInterface
  6. {
  7. /**
  8. * 数据库连接
  9. */
  10. public function connect()
  11. {
  12. var_dump(‘MYSQL Connection’);
  13. }
  14. }
  15. class PasswordReminder
  16. {
  17. /**
  18. * @var DBConnection
  19. */
  20. private $dbConnection;
  21. public function __construct(ConnectionInterface $dbConnection)
  22. {
  23. $this->dbConnection = $dbConnection;
  24. }
  25. }

通过上面的代码,如果我们想把 MySQLConnection 改成 MongoDBConnection,根本不需要去修改 PasswordReminder 类构造函数里的依赖。因为现在 PasswordReminder 类依赖的是接口,而非具体类。

如果你对接口的概念还不是很了解,可以看下 这篇文章 。它会帮助你理解依赖反转原则和 IoC 容器等。

现在我要讲下 IoC 容器里到底发生了什么。我们可以把 IoC 容器简单地理解为就是一个容器,里面装的是类的依赖。

OrderRepositoryInterface 接口:

</>复制代码

  1. namespace AppRepositories;
  2. interface OrderRepositoryInterface
  3. {
  4. public function getAll();
  5. }

DbOrderRepository 类:

</>复制代码

  1. namespace AppRepositories;
  2. class DbOrderRepository implements OrderRepositoryInterface
  3. {
  4. function getAll()
  5. {
  6. return "Getting all from mysql";
  7. }
  8. }

OrdersController 类:

</>复制代码

  1. namespace AppHttpControllers;
  2. use IlluminateHttpRequest;
  3. use AppHttpRequests;
  4. use AppRepositoriesOrderRepositoryInterface;
  5. class OrdersController extends Controller
  6. {
  7. protected $order;
  8. function __construct(OrderRepositoryInterface $order)
  9. {
  10. $this->order = $order;
  11. }
  12. public function index()
  13. {
  14. dd($this->order->getAll());
  15. return View::make(orders.index);
  16. }
  17. }

路由:

</>复制代码

  1. Route::resource("orders", "OrdersController");

现在,在浏览器中输入这个地址  

报错了吧,错误的原因是服务容器正在尝试去实例化一个接口,而接口是不能被实例化的。解决这个问题,只需把接口绑定到一个具体的类上:

把下面这行代码加在路由文件里就搞定了:

</>复制代码

  1. App::bind("AppRepositoriesOrderRepositoryInterface", "AppRepositoriesDbOrderRepository");

现在刷新浏览器看看:

我们可以这样定义一个容器类:

</>复制代码

  1. class SimpleContainer
  2. {
  3. protected static $container = [];
  4. public static function bind($name, Callable $resolver)
  5. {
  6. static::$container[$name] = $resolver;
  7. }
  8. public static function make($name)
  9. {
  10. if(isset(static::$container[$name])){
  11. $resolver = static::$container[$name] ;
  12. return $resolver();
  13. }
  14. throw new Exception("Binding does not exist in containeer");
  15. }
  16. }

这里,我想告诉你服务容器解析依赖是多么简单的事。

</>复制代码

  1. class LogToDatabase
  2. {
  3. public function execute($message)
  4. {
  5. var_dump("log the message to a database :".$message);
  6. }
  7. }
  8. class UsersController {
  9. protected $logger;
  10. public function __construct(LogToDatabase $logger)
  11. {
  12. $this->logger = $logger;
  13. }
  14. public function show()
  15. {
  16. $user = "JohnDoe";
  17. $this->logger->execute($user);
  18. }
  19. }

绑定依赖:

</>复制代码

  1. SimpleContainer::bind("Foo", function()
  2. {
  3. return new UsersController(new LogToDatabase);
  4. });
  5. $foo = SimpleContainer::make("Foo");
  6. print_r($foo->show());

输出:

</>复制代码

  1. string(36) "Log the messages to a file : JohnDoe"

Laravel 的服务容器源码:

</>复制代码

  1. public function bind($abstract, $concrete = null, $shared = false)
  2. {
  3. $abstract = $this->normalize($abstract);
  4. $concrete = $this->normalize($concrete);
  5. if (is_array($abstract)) {
  6. list($abstract, $alias) = $this->extractAlias($abstract);
  7. $this->alias($abstract, $alias);
  8. }
  9. $this->dropStaleInstances($abstract);
  10. if (is_null($concrete)) {
  11. $concrete = $abstract;
  12. }
  13. if (! $concrete instanceof Closure) {
  14. $concrete = $this->getClosure($abstract, $concrete);
  15. }
  16. $this->bindings[$abstract] = compact("concrete", "shared");
  17. if ($this->resolved($abstract)) {
  18. $this->rebound($abstract);
  19. }
  20. }
  21. public function make($abstract, array $parameters = [])
  22. {
  23. $abstract = $this->getAlias($this->normalize($abstract));
  24. if (isset($this->instances[$abstract])) {
  25. return $this->instances[$abstract];
  26. }
  27. $concrete = $this->getConcrete($abstract);
  28. if ($this->isBuildable($concrete, $abstract)) {
  29. $object = $this->build($concrete, $parameters);
  30. } else {
  31. $object = $this->make($concrete, $parameters);
  32. }
  33. foreach ($this->getExtenders($abstract) as $extender) {
  34. $object = $extender($object, $this);
  35. }
  36. if ($this->isShared($abstract)) {
  37. $this->instances[$abstract] = $object;
  38. }
  39. $this->fireResolvingCallbacks($abstract, $object);
  40. $this->resolved[$abstract] = true;
  41. return $object;
  42. }
  43. public function build($concrete, array $parameters = [])
  44. {
  45. if ($concrete instanceof Closure) {
  46. return $concrete($this, $parameters);
  47. }
  48. $reflector = new ReflectionClass($concrete);
  49. if (! $reflector->isInstantiable()) {
  50. if (! empty($this->buildStack)) {
  51. $previous = implode(", ", $this->buildStack);
  52. $message = "Target [$concrete] is not instantiable while building [$previous].";
  53. } else {
  54. $message = "Target [$concrete] is not instantiable.";
  55. }
  56. throw new BindingResolutionException($message);
  57. }
  58. $this->buildStack[] = $concrete;
  59. $constructor = $reflector->getConstructor();
  60. if (is_null($constructor)) {
  61. array_pop($this->buildStack);
  62. return new $concrete;
  63. }
  64. $dependencies = $constructor->getParameters();
  65. $parameters = $this->keyParametersByArgument(
  66. $dependencies, $parameters
  67. );
  68. $instances = $this->getDependencies($dependencies,$parameters);
  69. array_pop($this->buildStack);
  70. return $reflector->newInstanceArgs($instances);
  71. }

如果你想了解关于服务容器的更多内容,可以看下  vendor/laravel/framwork/src/Illuminate/Container/Container.php

简单的绑定

</>复制代码

  1. $this->app->bind("HelpSpotAPI", function ($app) {
  2. return new HelpSpotAPI($app->make("HttpClient"));
  3. });

单例模式绑定

通过 singleton 方法绑定到服务容器的类或接口,只会被解析一次。

</>复制代码

  1. $this->app->singleton("HelpSpotAPI", function ($app) {
  2. return new HelpSpotAPI($app->make("HttpClient"));
  3. });

绑定实例

也可以通过 instance 方法把具体的实例绑定到服务容器中。之后,就会一直返回这个绑定的实例:

</>复制代码

  1. $api = new HelpSpotAPI(new HttpClient);
  2. $this->app->instance("HelpSpotAPI", $api);

如果没有绑定,PHP 会利用反射机制来解析实例和依赖。

如果想了解更多细节,可以查看 官方文档

关于 Laravel 服务容器的练习代码, 可以从我的  GitHub (如果喜欢,烦请不吝 star )仓库获取。

感谢阅读。

</>复制代码

  1. 文章转自:https://learnku.com/laravel/t...

    更多文章:https://learnku.com/laravel/c...

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

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

相关文章

  • 详解 Laravel 中的依赖注入和 IoC

    摘要:依赖注入依赖注入一词是由提出的术语,它是将组件注入到应用程序中的一种行为。就像说的依赖注入是敏捷架构中关键元素。类依赖于,所以我们的代码可能是这样的创建一个这是一种经典的方法,让我们从使用构造函数注入开始。 showImg(https://segmentfault.com/img/remote/1460000018806800); 文章转自:https://learnku.com/la...

    haitiancoder 评论0 收藏0
  • php实现依赖注入(DI)和控制反转(IOC)

    摘要:工厂模式,依赖转移当然,实现控制反转的方法有几种。其实我们稍微改造一下这个类,你就明白,工厂类的真正意义和价值了。虽然如此,工厂模式依旧十分优秀,并且适用于绝大多数情况。 此篇文章转载自laravel-china,chongyi的文章https://laravel-china.org/top...原文地址: http://www.insp.top/learn-lar... ,转载务必保...

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

    摘要:本文一大半内容都是通过举例来让读者去理解什么是控制反转和依赖注入,通过理解这些概念,来更加深入。这种由外部负责其依赖需求的行为,我们可以称其为控制反转。工厂模式,依赖转移当然,实现控制反转的方法有几种。 容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描...

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

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

    abson 评论0 收藏0
  • PHPer面试指南-Laravel

    摘要:简述的生命周期采用了单一入口模式,应用的所有请求入口都是文件。分发请求一旦应用完成引导和所有服务提供者都注册完成,将会移交给路由进行分发。此外,由于对动态方法的独特用法,也使测试起来非常容易。 本书的 GitHub 地址:https://github.com/todayqq/PH... Laravel 作为现在最流行的 PHP 框架,其中的知识较多,所以单独拿出来写一篇。 简述 La...

    alaege 评论0 收藏0

发表评论

0条评论

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