IOC 容器是一个实现依赖注入的便利机制 - Taylor Otwell文章转自:https://learnku.com/laravel/t...php
Laravel 是当今最流行、最常使用的开源现代 web 应用框架之一。它提供了一些独特的特性,好比 Eloquent ORM, Query 构造器,Homestead 等时髦的特性,这些特性只有 Laravel 中才有。mysql
我喜欢 Laravel 是因为它犹如建筑风格同样的独特设计。Laravel 的底层使用了多设计模式,好比单例、工厂、建造者、门面、策略、提供者、代理等模式。随着本人知识的增加,我愈来愈发现 Laravel 的美。Laravel 为开发者减小了苦恼,带来了更多的便利。laravel
学习 Laravel,不单单是学习如何使用不一样的类,还要学习 Laravel 的哲学,学习它优雅的语法。Laravel 哲学的一个重要组成部分就是 IoC 容器,也能够称为服务容器。它是一个 Laravel 应用的核心部分,所以理解并使用 IoC 容器是咱们必须掌握的一项重要技能。git
IoC 容器是一个很是强大的类管理工具。它能够自动解析类。接下来我会试着去说清楚它为何如此重要,以及它的工做原理。github
首先,我想先谈下依赖反转原则,对它的了解会有助于咱们更好地理解 IoC 容器的重要性。web
该原则规定:sql
高层次的模块不该该依赖于低层次的模块,二者都应该依赖于抽象接口。抽象接口不该该依赖于具体实现。而具体实现则应该依赖于抽象接口。数据库
一言以蔽之: 依赖于抽象而非具体设计模式
class MySQLConnection { /** * 数据库链接 */ public function connect() { var_dump(‘MYSQL Connection’); } } class PasswordReminder { /** * @var MySQLConnection */ private $dbConnection; public function __construct(MySQLConnection $dbConnection) { $this->dbConnection = $dbConnection; } }
你们经常会有一个误解,那就是依赖反转就只是依赖注入的另外一种说法。但其实两者是不一样的。在上面的代码示例中,尽管在 PasswordReminder 类中注入了 MySQLConnection 类,但它仍是依赖于 MySQLConnection 类。api
然而,高层次模块 PasswordReminder 是不该该依赖于低层次模块 MySQLConnection 的。
若是咱们想要把 MySQLConnection 改为 MongoDBConnection,那咱们就还得手动修改 PasswordReminder 类构造函数里的依赖。
PasswordReminder 类应该依赖于抽象接口,而非具体类。那咱们要怎么作呢?请看下面的例子:
interface ConnectionInterface { public function connect(); } class DbConnection implements ConnectionInterface { /** * 数据库链接 */ public function connect() { var_dump(‘MYSQL Connection’); } } class PasswordReminder { /** * @var DBConnection */ private $dbConnection; public function __construct(ConnectionInterface $dbConnection) { $this->dbConnection = $dbConnection; } }
经过上面的代码,若是咱们想把 MySQLConnection 改为 MongoDBConnection,根本不须要去修改 PasswordReminder 类构造函数里的依赖。由于如今 PasswordReminder 类依赖的是接口,而非具体类。
若是你对接口的概念还不是很了解,能够看下 这篇文章 。它会帮助你理解依赖反转原则和 IoC 容器等。
如今我要讲下 IoC 容器里到底发生了什么。咱们能够把 IoC 容器简单地理解为就是一个容器,里面装的是类的依赖。
OrderRepositoryInterface 接口:
namespace App\Repositories; interface OrderRepositoryInterface { public function getAll(); }
DbOrderRepository 类:
namespace App\Repositories; class DbOrderRepository implements OrderRepositoryInterface { function getAll() { return 'Getting all from mysql'; } }
OrdersController 类:
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests; use App\Repositories\OrderRepositoryInterface; class OrdersController extends Controller { protected $order; function __construct(OrderRepositoryInterface $order) { $this->order = $order; } public function index() { dd($this->order->getAll()); return View::make(orders.index); } }
路由:
Route::resource('orders', 'OrdersController');
如今,在浏览器中输入这个地址 <http://localhost:8000/orders>
报错了吧,错误的缘由是服务容器正在尝试去实例化一个接口,而接口是不能被实例化的。解决这个问题,只需把接口绑定到一个具体的类上:
把下面这行代码加在路由文件里就搞定了:
App::bind('App\Repositories\OrderRepositoryInterface', 'App\Repositories\DbOrderRepository');
如今刷新浏览器看看:
咱们能够这样定义一个容器类:
class SimpleContainer { protected static $container = []; public static function bind($name, Callable $resolver) { static::$container[$name] = $resolver; } public static function make($name) { if(isset(static::$container[$name])){ $resolver = static::$container[$name] ; return $resolver(); } throw new Exception("Binding does not exist in containeer"); } }
这里,我想告诉你服务容器解析依赖是多么简单的事。
class LogToDatabase { public function execute($message) { var_dump('log the message to a database :'.$message); } } class UsersController { protected $logger; public function __construct(LogToDatabase $logger) { $this->logger = $logger; } public function show() { $user = 'JohnDoe'; $this->logger->execute($user); } }
绑定依赖:
SimpleContainer::bind('Foo', function() { return new UsersController(new LogToDatabase); }); $foo = SimpleContainer::make('Foo'); print_r($foo->show());
输出:
string(36) "Log the messages to a file : JohnDoe"
Laravel 的服务容器源码:
public function bind($abstract, $concrete = null, $shared = false) { $abstract = $this->normalize($abstract); $concrete = $this->normalize($concrete); if (is_array($abstract)) { list($abstract, $alias) = $this->extractAlias($abstract); $this->alias($abstract, $alias); } $this->dropStaleInstances($abstract); if (is_null($concrete)) { $concrete = $abstract; } if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); if ($this->resolved($abstract)) { $this->rebound($abstract); } } public function make($abstract, array $parameters = []) { $abstract = $this->getAlias($this->normalize($abstract)); if (isset($this->instances[$abstract])) { return $this->instances[$abstract]; } $concrete = $this->getConcrete($abstract); if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete, $parameters); } else { $object = $this->make($concrete, $parameters); } foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } if ($this->isShared($abstract)) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); $this->resolved[$abstract] = true; return $object; } public function build($concrete, array $parameters = []) { if ($concrete instanceof Closure) { return $concrete($this, $parameters); } $reflector = new ReflectionClass($concrete); if (! $reflector->isInstantiable()) { if (! empty($this->buildStack)) { $previous = implode(', ', $this->buildStack); $message = "Target [$concrete] is not instantiable while building [$previous]."; } else { $message = "Target [$concrete] is not instantiable."; } throw new BindingResolutionException($message); } $this->buildStack[] = $concrete; $constructor = $reflector->getConstructor(); if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } $dependencies = $constructor->getParameters(); $parameters = $this->keyParametersByArgument( $dependencies, $parameters ); $instances = $this->getDependencies($dependencies,$parameters); array_pop($this->buildStack); return $reflector->newInstanceArgs($instances); }
若是你想了解关于服务容器的更多内容,能够看下 vendor/laravel/framwork/src/Illuminate/Container/Container.php
简单的绑定
$this->app->bind('HelpSpot\API', function ($app) { return new HelpSpot\API($app->make('HttpClient')); });
单例模式绑定
经过 singleton
方法绑定到服务容器的类或接口,只会被解析一次。
$this->app->singleton('HelpSpot\API', function ($app) { return new HelpSpot\API($app->make('HttpClient')); });
绑定实例
也能够经过 instance
方法把具体的实例绑定到服务容器中。以后,就会一直返回这个绑定的实例:
$api = new HelpSpot\API(new HttpClient); $this->app->instance('HelpSpot\API', $api);
若是没有绑定,PHP 会利用反射机制来解析实例和依赖。
若是想了解更多细节,能够查看 官方文档
关于 Laravel 服务容器的练习代码, 能够从个人 GitHub (若是喜欢,烦请不吝 star )仓库获取。
感谢阅读。
文章转自: https://learnku.com/laravel/t...
更多文章: https://learnku.com/laravel/c...