Laravel版本5.4.33
1. IoC(Inversion of Control) 和 DI (Dependency injection)
IoC -
Inversion of Control,依赖反转,简言之就是:将依赖的生产由内部移至外部,从而降低耦合程度。
DI - Dependency injection,依赖注入,简言之就是:根据反射机制,自动识别目标类的所需依赖,并将依赖自动提供给目标类的过程。
2. Laravel 中的 IoC 容器
Laravel实现了一个管理各种依赖的容器,并通过这个容器实现了依赖注入。下面是laravel中对IoC容器的接口定义:
<?php
interface Container
{
/**
* Determine if the given abstract type has been bound.
*/
public function bound($abstract);
/**
* Alias a type to a different name.
*/
public function alias($abstract, $alias);
/**
* Assign a set of tags to a given binding.
*/
public function tag($abstracts, $tags);
/**
* Resolve all of the bindings for a given tag.
*/
public function tagged($tag);
/**
* Register a binding with the container.
*/
public function bind($abstract, $concrete = null, $shared = false);
/**
* Register a binding if it hasn't already been registered.
*/
public function bindIf($abstract, $concrete = null, $shared = false);
/**
* Register a shared binding in the container.
*/
public function singleton($abstract, $concrete = null);
/**
* "Extend" an abstract type in the container.
*/
public function extend($abstract, Closure $closure);
/**
* Register an existing instance as shared in the container.
*/
public function instance($abstract, $instance);
/**
* Define a contextual binding.
*/
public function when($concrete);
/**
* Get a closure to resolve the given type from the container.
*/
public function factory($abstract);
/**
* Resolve the given type from the container.
*/
public function make($abstract);
/**
* Call the given Closure / class@method and inject its dependencies.
*/
public function call($callback, array $parameters = [], $defaultMethod = null);
/**
* Determine if the given abstract type has been resolved.
*/
public function resolved($abstract);
/**
* Register a new resolving callback.
*/
public function resolving($abstract, Closure $callback = null);
/**
* Register a new after resolving callback.
*/
public function afterResolving($abstract, Closure $callback = null);
}
下面是我对IoC容器的一个简单实现(未完整):
<?php
require_once('./ContainerInterface.php');
require_once('./DB.php');
error_reporting(1);
class Container implements ContainerInterface{
// 抽象服务和具体实现的映射,以及是否为单例
// $bindings = [
// 'abstract' => ['concrete' => Object/Closure, 'shared' => true/false],
// ...
// ];
protected $bindings = [];
// 服务实例
// $instance = [
// 'abstract' => object,
// ...
// ];
protected $instance = [];
// 别名和抽象服务映射
// $aliases = [
// 'alias' => 'abstract',
// ...
// ];
protected $aliases = [];
// 抽象服务和别名映射
// $abstractAliases = [
// 'abstract' => ['alias1', 'alias2'],
// ...
// ];
protected $abstractAliases = [];
//
public function bound($abstract){
return isset($this->bindings[$abstract]) || isset($this->aliases[$abstract]);
}
public function alias($abstract, $alias){
$this->aliases[$alias] = $abstract;
$this->abstractAliases[$abstract] = $alias;
}
public function tag($abstracts, $tags){}
public function tagged($tag){}
public function bind($abstract, $concrete = null, $shared = false){
unset($this->aliases[$abstract]);
unset($this->instance[$abstract ]);
$this->bindings[$abstract] = ['concrete' => $concrete, 'shared' => $shared];
}
public function bindIf($abstract, $concrete = null, $shared = false){
if(!$this->bound($abstract)){
$this->bind($abstract, $concrete, $shared);
}
}
public function singleton($abstract, $concrete = null){
$this->bind($abstract, $concrete, true);
}
public function extend($abstract, Closure $closure){
if(isset($this->instance[$abstract])){
$instance = $this->instance[$abstract];
}else{
if($this->bound($abstract)){
$instance = $this->make($abstract);
}
}
$closure($instance, $this);
}
public function instance($abstract, $instance){
if(isset($this->bindings[$abstract])){
unset($this->bindings[$abstract]);
}
if(isset($this->instance[$abstract])){
unset($this->instance[$abstract]);
}
$this->singleton($abstract, $instance);
}
public function when($concrete){}
public function factory($abstract){}
public function make($abstract){
if(isset($this->instance[$abstract])){
return $this->instance[$abstract];
}
if($this->bindings[$abstract]['concrete'] instanceof Closure){
$object = $this->bindings[$abstract]['concrete']();
}else{
$object = $this->bindings[$abstract]['concrete'];
}
if($this->bindings[$abstract]['shared']){
$this->instance[$abstract] = $object;
}
return $object;
}
public function call($callback, array $parameters = [], $defaultMethod = null){}
public function resolved($abstract){}
public function resolving($abstract, Closure $callback = null){}
public function afterResolving($abstract, Closure $callback = null){}
}
echo '<pre>';
$container = new Container();
$container->bindIf('db', function(){
return new DB();
}, true);
$db = $container->make('db');
$container->extend('db', function($instance, $container){
$instance->container = $container;
print_r($instance);
});
2. Laravel 中如何实现 DI(依赖注入)
/**
* Get all dependencies for a given method.
*
* @param \Illuminate\Container\Container
* @param callable|string $callback
* @param array $parameters
* @return array
*/
protected static function getMethodDependencies($container, $callback, array $parameters = [])
{
$dependencies = [];
foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
}
return array_merge($dependencies, $parameters);
}
/**
* Get the proper reflection instance for the given callback.
*
* @param callable|string $callback
* @return \ReflectionFunctionAbstract
*/
protected static function getCallReflector($callback)
{
if (is_string($callback) && strpos($callback, '::') !== false) {
$callback = explode('::', $callback);
}
return is_array($callback)
? new ReflectionMethod($callback[0], $callback[1])
: new ReflectionFunction($callback);
}
/**
* Get the dependency for the given call parameter.
*
* @param \Illuminate\Container\Container $container
* @param \ReflectionParameter $parameter
* @param array $parameters
* @param array $dependencies
* @return mixed
*/
protected static function addDependencyForCallParameter($container, $parameter,
array &$parameters, &$dependencies)
{
if (array_key_exists($parameter->name, $parameters)) {
$dependencies[] = $parameters[$parameter->name];
unset($parameters[$parameter->name]);
} elseif ($parameter->getClass()) {
$dependencies[] = $container->make($parameter->getClass()->name);
} elseif ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
}
}
getCallReflector - 建立对所调函数的反射。
getMethodDependencies/addDependencyForCallParameter - 利用识别所调函数的依赖,从IoC容器中构建这些依赖。
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param \Illuminate\Container\Container $container
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*/
public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
{
if (static::isCallableWithAtSign($callback) || $defaultMethod) {
return static::callClass($container, $callback, $parameters, $defaultMethod);
}
return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
return call_user_func_array(
$callback, static::getMethodDependencies($container, $callback, $parameters)
);
});
}
call_user_func_array - 将依赖注入到所调函数。
如此,我们可以在
NewsController@addNews 中指定需要的依赖。
public function addNews(Request $request){}
2. Application Object
下面是 bootstrap/app.php return 时的 $app 结构:(第215行即为上述的 Request $request 依赖)
下面是 bootstrap/app.php return 时的 $app 结构:(第215行即为上述的 Request $request 依赖)
$app = new Illuminate\Foundation\Application{
'basePath' => 'C:\cygwin64\home\fe\laravel',
'hasBeenBootstrapped' => '',
'booted' => '',
'bootingCallbacks' => [],
'bootedCallbacks' => [],
'terminatingCallbacks' => [],
'deferredServices' => [],
'monologConfigurator' => '',
'databasePath' => '',
'storagePath' => '',
'environmentPath' => '',
'environmentFile' => '.env',
'namespace' => '',
'resolved' => [],
'extenders' => [],
'tags' => [],
'buildStack' => [],
'with' => [],
'contextual' => [],
'reboundCallbacks' => [],
'globalResolvingCallbacks' => [],
'globalAfterResolvingCallbacks' => [],
'resolvingCallbacks' => [],
'afterResolvingCallbacks' => [],
'methodBindings' => [],
'serviceProviders' => [
new Illuminate\Events\EventServiceProvider{
'app' => {{this}}
'defer' => '',
},
new Illuminate\Log\LogServiceProvider{
'app' => {{this}}
'defer' => '',
},
new Illuminate\Routing\RoutingServiceProvider{
'app' => {{this}}
'defer' => '',
},
]
'loadedProviders' => [
'Illuminate\Events\EventServiceProvider' => '1',
'Illuminate\Log\LogServiceProvider' => '1',
'Illuminate\Routing\RoutingServiceProvider' => '1',
],
'bindings' => [
'events' => [
'concrete' => new Closure{
'this' => new Illuminate\Events\EventServiceProvider{
'app' => {{this}},
'defer' => '',
},
'parameter' => ['$app' => ''],
},
'shared' => '1',
],
'log' => [
'concrete' => new Closure{
'this' => new Illuminate\Log\LogServiceProvider{
'app' => {{this}},
'defer' => '',
},
},
'shared' => 1,
],
'router' =>[
'concrete' => new Closure{
'this' => new Illuminate\Routing\RoutingServiceProvider{
'app' => {{this}},
'defer' => '',
},
'parameter' => ['$app' => ''],
},
'shared' => 1
],
'url' => [
'concrete' => new Closure{
'this' => new Illuminate\Routing\RoutingServiceProvider{
'app' => {{this}}.
'defer' => '',
},
'parameter' => ['$app' => ''],
},
'shared' => 1
],
'redirect' => [
'concrete' => new Closure{
'this' => new Illuminate\Routing\RoutingServiceProvider{
'app' => {{this}},
'defer' => '',
},
'parameter' => ['$app' => ''],
}
'shared' => 1
],
'Psr\Http\Message\ServerRequestInterface' => [
'concrete' => new Closure{
'this' => new Illuminate\Routing\RoutingServiceProvider{
'app' => {{this}},
'defer' => '',
},
'parameter' => ['$app' => ''],
}
'shared' =>
],
'Psr\Http\Message\ResponseInterface' =>[
'concrete' => new Closure{
'this' => new Illuminate\Routing\RoutingServiceProvider{
'app' => {{THIS}},
'defer' => '',
},
'parameter' => ['$app' => ''],
}
'shared' =>
],
'Illuminate\Contracts\Routing\ResponseFactory' =>[
'concrete' => new Closure{
'this' => mew Illuminate\Routing\RoutingServiceProvider{
'app' => {{THIS}},
'defer' => '',
},
'parameter' => ['$app' => ''],
}
'shared' => 1
],
'Illuminate\Contracts\Http\Kernel' => [
'concrete' => new Closure{
'static' => Array(
'abstract' => 'Illuminate\Contracts\Http\Kernel',
'concrete' => 'App\Http\Kernel',
)
'this' => {{THIS}},
'parameter' => ['$container' => '', '$parameters' => ''],
},
'shared' => 1
],
'Illuminate\Contracts\Debug\ExceptionHandler' => [
'concrete' => new Closure{
'static' => [
'abstract' => 'Illuminate\Contracts\Debug\ExceptionHandler',
'concrete' => 'App\Exceptions\Handler',
],
'this' => {{this}},
'parameter' =>['$container' => '', '$parameters' => ''],
},
'shared' => '1',
],
],
'instances' => [
'path' => 'C:\cygwin64\home\fe\laravel\app',
'path.base' => 'C:\cygwin64\home\fe\laravel',
'path.lang' => 'C:\cygwin64\home\fe\laravel\resources\lang',
'path.config' => 'C:\cygwin64\home\fe\laravel\config',
'path.public' => 'C:\cygwin64\home\fe\laravel\public',
'path.storage' => 'C:\cygwin64\home\fe\laravel\storage',
'path.database' => 'C:\cygwin64\home\fe\laravel\database',
'path.resources' => 'C:\cygwin64\home\fe\laravel\resources',
'path.bootstrap' => 'C:\cygwin64\home\fe\laravel\bootstrap',
'Illuminate\Container\Container' => {{this}},
],
'aliases' => [
'Illuminate\Foundation\Application' => 'app',
'Illuminate\Contracts\Container\Container' => 'app',
'Illuminate\Contracts\Foundation\Application' => 'app',
'Illuminate\Routing\UrlGenerator' => 'url',
'Illuminate\Contracts\Routing\UrlGenerator' => 'url',
'Illuminate\Validation\Factory' => 'validator',
'Illuminate\Contracts\Validation\Factory' => 'validator',
'Illuminate\Auth\AuthManager' => 'auth',
'Illuminate\Contracts\Auth\Factory' => 'auth',
'Illuminate\Contracts\Auth\Guard' => 'auth.driver',
'Illuminate\Auth\Passwords\PasswordBrokerManager' => 'auth.password',
'Illuminate\Contracts\Auth\PasswordBrokerFactory' => 'auth.password',
'Illuminate\Auth\Passwords\PasswordBroker' => 'auth.password.broker',
'Illuminate\Contracts\Auth\PasswordBroker' => 'auth.password.broker',
'Illuminate\Cache\CacheManager' => 'cache',
'Illuminate\Contracts\Cache\Factory' => 'cache',
'Illuminate\Cache\Repository' => 'cache.store',
'Illuminate\Contracts\Cache\Repository' => 'cache.store',
'Illuminate\Session\SessionManager' => 'session',
'Illuminate\Session\Store' => 'session.store',
'Illuminate\Contracts\Session\Session' => 'session.store',
'Illuminate\Cookie\CookieJar' => 'cookie',
'Illuminate\Contracts\Cookie\Factory' => 'cookie',
'Illuminate\Contracts\Cookie\QueueingFactory' => 'cookie',
'Illuminate\Log\Writer' => 'log',
'Illuminate\Contracts\Logging\Log' => 'log',
'Psr\Log\LoggerInterface' => 'log',
'Illuminate\Redis\RedisManager' => 'redis',
'Illuminate\Contracts\Redis\Factory' => 'redis',
'Illuminate\View\Factory' => 'view',
'Illuminate\Contracts\View\Factory' => 'view',
'Illuminate\Mail\Mailer' => 'mailer',
'Illuminate\Contracts\Mail\Mailer' => 'mailer',
'Illuminate\Contracts\Mail\MailQueue' => 'mailer',
'Illuminate\Config\Repository' => 'config',
'Illuminate\Contracts\Config\Repository' => 'config',
'Illuminate\Encryption\Encrypter' => 'encrypter',
'Illuminate\Contracts\Encryption\Encrypter' => 'encrypter',
'Illuminate\Database\DatabaseManager' => 'db',
'Illuminate\Database\Connection' => 'db.connection',
'Illuminate\Database\ConnectionInterface' => 'db.connection',
'Illuminate\Filesystem\Filesystem' => 'files',
'Illuminate\Filesystem\FilesystemManager' => 'filesystem',
'Illuminate\Contracts\Filesystem\Factory' => 'filesystem',
'Illuminate\View\Compilers\BladeCompiler' => 'blade.compiler',
'Illuminate\Events\Dispatcher' => 'events',
'Illuminate\Contracts\Events\Dispatcher' => 'events',
'Illuminate\Contracts\Filesystem\Filesystem' => 'filesystem.disk',
'Illuminate\Contracts\Filesystem\Cloud' => 'filesystem.cloud',
'Illuminate\Contracts\Hashing\Hasher' => 'hash',
'Illuminate\Translation\Translator' => 'translator',
'Illuminate\Contracts\Translation\Translator' => 'translator',
'Illuminate\Routing\Redirector' => 'redirect',
'Illuminate\Http\Request' => 'request',
'Symfony\Component\HttpFoundation\Request' => 'request',
'Illuminate\Routing\Router' => 'router',
'Illuminate\Contracts\Routing\Registrar' => 'router',
'Illuminate\Contracts\Routing\BindingRegistrar' => 'router',
'Illuminate\Queue\QueueManager' => 'queue',
'Illuminate\Contracts\Queue\Factory' => 'queue',
'Illuminate\Contracts\Queue\Monitor' => 'queue',
'Illuminate\Contracts\Queue\Queue' => 'queue.connection',
'Illuminate\Queue\Failed\FailedJobProviderInterface' => 'queue.failer',
],
'abstractAliases' => [
'app' => ['Illuminate\Foundation\Application', 'Illuminate\Contracts\Container\Container', 'Illuminate\Contracts\Foundation\Application'],
'auth' => ['Illuminate\Auth\AuthManager', 'Illuminate\Contracts\Auth\Factory'],
'cache' => ['Illuminate\Cache\CacheManager', 'Illuminate\Contracts\Cache\Factory'],
'config' => ['Illuminate\Config\Repository', 'Illuminate\Contracts\Config\Repository'],
'cookie' => ['Illuminate\Cookie\CookieJar', 'Illuminate\Contracts\Cookie\Factory', 'Illuminate\Contracts\Cookie\QueueingFactory'],
'db' => ['Illuminate\Database\DatabaseManager'],
'files' => ['Illuminate\Filesystem\Filesystem'],
'events' => ['Illuminate\Events\Dispatcher', 'Illuminate\Contracts\Events\Dispatcher'],
'hash' => ['Illuminate\Contracts\Hashing\Hasher'],
'log' => ['Illuminate\Log\Writer', 'Illuminate\Contracts\Logging\Log', 'Psr\Log\LoggerInterface'],
'mailer' => ['Illuminate\Mail\Mailer', 'Illuminate\Contracts\Mail\Mailer', 'Illuminate\Contracts\Mail\MailQueue'],
'queue' => ['Illuminate\Queue\QueueManager', 'Illuminate\Contracts\Queue\Factory', 'Illuminate\Contracts\Queue\Monitor'],
'redis' => ['Illuminate\Redis\RedisManager', 'Illuminate\Contracts\Redis\Factory'],
'request' => ['Illuminate\Http\Request', 'Symfony\Component\HttpFoundation\Request'],
'router' => ['Illuminate\Routing\Router', 'Illuminate\Contracts\Routing\Registrar', 'Illuminate\Contracts\Routing\BindingRegistrar'],
'session' => ['Illuminate\Session\SessionManager'],
'redirect' => ['Illuminate\Routing\Redirector'],
'url' => ['Illuminate\Routing\UrlGenerator', 'Illuminate\Contracts\Routing\UrlGenerator'],
'view' => ['Illuminate\View\Factory', 'Illuminate\Contracts\View\Factory'],
'validator' => ['Illuminate\Validation\Factory', 'Illuminate\Contracts\Validation\Factory'],
'queue.failer' => ['Illuminate\Queue\Failed\FailedJobProviderInterface'],
'encrypter' => ['Illuminate\Encryption\Encrypter', 'Illuminate\Contracts\Encryption\Encrypter'],
'auth.driver' => ['Illuminate\Contracts\Auth\Guard',],
'cache.store' => ['Illuminate\Cache\Repository', 'Illuminate\Contracts\Cache\Repository'],
'session.store' => ['Illuminate\Session\Store', 'Illuminate\Contracts\Session\Session'],
'translator' => ['Illuminate\Translation\Translator', 'Illuminate\Contracts\Translation\Translator'],
'auth.password' => ['Illuminate\Auth\Passwords\PasswordBrokerManager', 'Illuminate\Contracts\Auth\PasswordBrokerFactory'],
'db.connection' => ['Illuminate\Database\Connection', 'Illuminate\Database\ConnectionInterface'],
'filesystem' => ['Illuminate\Filesystem\FilesystemManager', 'Illuminate\Contracts\Filesystem\Factory'],
'blade.compiler' => ['Illuminate\View\Compilers\BladeCompiler'],
'filesystem.disk' => ['Illuminate\Contracts\Filesystem\Filesystem'],
'filesystem.cloud' => ['Illuminate\Contracts\Filesystem\Cloud'],
'queue.connection' => ['Illuminate\Contracts\Queue\Queue'],
'auth.password.broker' => ['Illuminate\Auth\Passwords\PasswordBroker', 'Illuminate\Contracts\Auth\PasswordBroker'],
],
};
4. 给出一些深入理解 IoC 和 DI 的文章