laravel的核心

:-}

依赖注入容器不止laravel一家

目前市面上流行有数十个依赖注入容器, 而这些DI(Dependency Injection)容器有着大相径庭的方式来存储条目.

有一些基于回调(callbacks) (Pimple, Laravel, …)
其他的基于配置(Symfony, ZendFramework, …), 配置有多种格式(PHP数组, YAML文件, XML文件)
有一些可以 利用工厂(leverage factories)
有一些使用PHP API来创建条目(PHP-DI, ZF, Symfony, Mouf, …)
有一些可以自动装配(Laravel, PHP-DI)

    即自动处理依赖

其他的一些能根据注释来解决依赖(PHP-DI, JMS Bundle…)
有一些提供图形用户界面(Mouf)
有一些可以使用配置文件编译成PHP类(Symfony, ZF)
有一些支持别名
有一些可以使用代理以提供依赖关系的延迟加载(lazy loading of dependencies)

局势就是如此, 当前有着非常多种方式来解决依赖注入问题, 因此有很多种DI容器的实现方式.
然而, 所有的DI容器都在满足同样的需求: 它们为应用程序提供一种检索一组配置对象(通常是服务)的方法.

通过标准化从容器中获取条目的方式, 使用 容器PSR规范 的框架和库可以与与任何兼容的容器.
这更便于用户(指使用容器的开发者)根据各自的喜好来选择容器.

laravel的IOC

上面一篇文章《正确的理解IOC》提到,laravel的核心【IOC容器】很轻量,具体有以下几个特点:

反射

类也不依赖DI对象,依赖注入是通过反射分析出类所依赖的其他类,然后从容器中获取相应的对象并自动注入到类里面。
依赖注入的写法是强类型参数,也就是参数有提示类名/接口。
laravel的服务容器实现了 PSR-11 接口。 因此,你可以使用 PSR-11容器『接口类型提示』来获取 Laravel 容器的实例。

Facades

在 Laravel 应用程序的环境中,facade 是个提供从容器访问对象的类。Facade 类是让这个机制可以运作的原因。Laravel 的 facades 和你建立的任何自定义 facades,将会继承基类 Facade。

你的 facade 类只需要去实现一个方法:getFacadeAccessor。getFacadeAccessor 方法的工作是定义要从容器解析什么。基类 Facade 利用 __callStatic() 魔术方法来从你的 facade 调用到解析出来的对象。

所以当你对 facade 调用,例如 Cache::get,Laravel 从服务容器解析缓存管理类出来,并对该类调用 get 方法。用专业口吻来说,Laravel Facades 是使用 Laravel 服务容器作为服务定位器的便捷语法。
下面是route的facade类:

namespace Illuminate\Support\Facades;
class Route extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'router';
    }
}

serviceProvider

服务提供者是所有 Laravel 应用程序的启动中心。你的应用程序,以及所有 Laravel 的核心服务,都是透过服务提供者启动。

但我们所说的「启动」指的是什么?一般而言,我们指注册事物,包括注册服务容器绑定、事件监听器、过滤器,甚至路由。服务提供者是你的应用程序配置中心所在。

如果你打开包含于 Laravel 中的 config/app.php 这一文件,你会看到 providers 数组。这些是所有将加载至你的应用程序里的服务提供者类。当然,它们之中有很多属于「缓载」提供者,意思是除非真正需要它们所提供的服务,否则它们并不会在每一个请求中都被加载。

serviceProvider和Facades的关系

如果要把所有可能的service绑定,container类会变得非常庞大,如果在应用的开始调用N次bind方法,依次绑定需要的所有service,这个文件将难以维护,比如

class Container {
  protected $service_arr = [];
  public function bind($name, $instance) {
     $this->servie_arr[$name] = $instance;
  }
  public function get($name) {
    return $this->service_arr[$name];
  }
}
$con = new Container();
$db = new DBClass(['locahost',3306,root,pwd]);
$con->bind('db', $db);
$cache = new Cache(['127.0.0.1',11211]);
$con->bind('cache',$cache);
//...

所有的类的实例化都写到一个文件里了,讲会是耦合性升高。
ServiceProvider就是解决这个问题的
每一个需要绑定到container的service,你需要创建一个对应ServiceProvider,这个ServiceProvider中有一个register方法,在这里面进行绑定,而不是像上述那样。比如:

class SomeServiceProvider extends ServiceProvider {
  public function register() {
      $this->app->bind('some', new Some());
  }
}

Laravel的container为了知道需要绑定哪些service,它会去读一个数组,这个数组是config/app.php中的Providers,然后你只需要把你的SomeServiceProvider写进这个数组,就可以绑定上了。
Facade

刚开始看Facade其实还是不太好理解,到底什么是Facade呢,它存在的意义又是什么呢?
我的理解

简化对service的使用,可以理解为语法糖
可以方便的替换

可能你有疑惑,到底替换什么呢?看看下面的代码吧

use Illuminate\Support\Facades\Redis;

class IndexController extends Controller {
  public function index() {
    $redis = Redis::connect();
    //do something
  }
}

然而当我们打开Illuminate\Support\Facades\Redis看看,并没有connect的静态方法啊?但是可以发现唯一的一个方法返回了一个redis字符串,有什么玄机呢?
那么我们再看看RedisServiceProvider,我们的RedisServiceProvider在container里绑定了一个redis……所以大概你也猜到了,没错!

Redis::connect() 等价于 $this->app->get('redis')->connect()

get的到底是什么,取决于Facade中getFacadeAccessor方法返回的字符串!
这样有什么好处呢?文档也说了,除了简便使用以外最大的用处是测试,想想看,你把Redis的getFacadeAccessor方法返回值变成’memcached’,那么你所有使用Redis::some()是不是就全部切换成memcache啦?

Application.php

我们可以在Illuminate\Foundation命名空间下的Application.php里面看下,这个文件时laravel的基础。

class Application extends Container implements ApplicationContract, HttpKernelInterface{}

Application.php继承自 Container ,也就是说Foundation命名空间下的Application 类本身是一个容器。
我们可以在Illuminate\Container命名空间下的Container.php里面看下,这个文件时laravel的容器,基础中的基础。

后期静态绑定

laravel框架中Model类的部分代码,$model = new static($attributes); 和 return static::create($attributes);
因为该类是抽象类,所以他的实现类在调用这些函数时,最终动态绑定的是实现类,而非model抽象类。

trait

Trait是PHP 5.4引入的新概念,看上去既像类又像接口,其实都不是,Trait可以看做类的部分实现,可以混入一个或多个现有的PHP类中。
其作用有两个:表明类可以做什么;提供模块化实现。
Trait是一种代码复用技术,为PHP的单继承限制提供了一套灵活的代码复用机制。

从index.php进入项目

<?php

/*设置脚本开始时间 define('LARAVEL_START', microtime(true));
  引入composer的自动加载,在composer.json中可以看出相当于
    require('app/*') require('database/*') require('vendor/*')
    之后使用时只要引入命名空间即可
*/
require __DIR__.'/../bootstrap/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';

/*在phpstrom中ctrl+左键单击查看app.php代码。*/


/*app.php代码如下:*/
/*首先创建app服务容器,即ioc容器,稍后分析*/
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

/*
看官方文档可知,
singleton 方法绑定一个只需要解析一次的类或接口到容器,然后接下来对容器的调用将会返回同一个实例
即以后需要使用Illuminate\Contracts\Http\Kernel这个类时,会返回App\Http\Kernel
使用代码 $app->make('Illuminate\Contracts\Http\Kernel')
*/
/*
在laravel中Contracts(契约)文件夹里面的都是interfere类
实现时都在Foundation这个文件夹中
而查看App\Http\Kernel会发现它继承了实现接口类的类
*/
/*
这里绑定的是http启动的一些启动服务(session。env。config等)和中间件。以后分析。
*/
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);
/*
这里绑定的是控制台用的服务,即php artisan 
如果上线了可以省去。
*/
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);
/*
绑定错误提示类
上线后可以省去。
*/
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;




/*继续查看index.php*/
/*
这里解析http的核心
*/
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

/*
处理请求,生成响应
这里的handle函数是一大难点。其实现贼复杂
*/
$response = $kernel->handle(
/*
这里利用symfont的http request解析http请求
里面有post。get。文件。路径等很多信息。
*/
    $request = Illuminate\Http\Request::capture()
);

/*发送响应*/
$response->send();

/*后续处理,比如写log等*/
$kernel->terminate($request, $response);



/*由app.php里面的singleton很容易得知handle函数的所在*/


public function handle($request)
    {
        try {
/*这是symfont的允许在路由中使用GET和POST以外的HTTP方法 */
            $request->enableHttpMethodParameterOverride();
/*把request交给router处理,首先肯定是要匹配的你在route里面定义的方法*/
            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
/*接下来都是些错误处理*/
            $this->reportException($e);

            $response = $this->renderException($request, $e);
        } catch (Throwable $e) {
            $this->reportException($e = new FatalThrowableError($e));

            $response = $this->renderException($request, $e);
        }
/*分发事件,让事件监听获取信息,以后说*/
        event(new Events\RequestHandled($request, $response));

        return $response;
    }




/*然后我们专注于sendRequestThroughRouter这函数*/
protected function sendRequestThroughRouter($request)
    {
/*
绑定一个实例。
laravel实现3种绑定,bind singleton instance。每次绑定如果名字相同会覆盖掉上次绑定的。
bind每次make返回的都是新的实例
singleton每次make返回的都是同一个实例
instance每次返回的都是绑定时给的实例
*/
        $this->app->instance('request', $request);
/*未知作用*/
        Facade::clearResolvedInstance('request');
/*启动初始服务,比如config。env等,以后有机会分析*/
        $this->bootstrap();
/*利用管道类处理请求,先绑定app容器,然后发送request请求,通过中间件,最后匹配路由,并执行route里面定义的方法,生成并返回reponse,其中源码绕来绕去就不一一解析*/
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

laravel的spl_autoload_register()注册了什么

在路由文件中经常会出现Route::get()这样的写法,但实际上并没有Route类,Route只是\Illuminate\Support\Facades\Route::class外观类的别名,这样取个别名只是为了简化作用。

namespace Illuminate\Foundation;

class AliasLoader
{

   /**
     * Register the loader on the auto-loader stack.
     *
     * @return void
     */
     public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();

            $this->registered = true;
        }
    }


     /**
     * Prepend the load method to the auto-loader stack.
     *
     * @return void
     */
    protected function prependToLoaderStack()
    {
        spl_autoload_register([$this, 'load'], true, true);
    }


     /**
     * Load a class alias if it is registered.
     *
     * @param  string  $alias
     * @return bool|null
     */
    public function load($alias)
    {
        if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
            $this->loadFacade($alias);

            return true;
        }

        if (isset($this->aliases[$alias])) {
            return class_alias($this->aliases[$alias], $alias);
        }
    }


   ...
}

Route::get()的调用过程就是,首先发现没有Route类,就去自动加载函数堆栈中通过AliasLoader::load()函数查找到Route是IlluminateSupportFacadesRoute的别名,那就调用IlluminateSupportFacadesRoute::get(),当然这里IlluminateSupportFacadesRoute没有get()静态方法,那就调用父类Facade的__callStatic()来找到名为router的服务,名为’router’的服务那就是早就注册到容器中的IlluminateRoutingRouter对象,所以最终就是调用IlluminateRoutingRouter::get()方法。这个过程主要使用了两个技术:一个是外观类的别名;一个是PHP的重载,

参考:
https://segmentfault.com/a/1190000006946685

Ioc

route.php文件中,

Class Foo{}//如果没有这个class会报错。

Route::get('/',function(Foo $foo){
  dd($foo);
});

Foo $foo 是依赖注入的写法,

laravel框架利用反射机制,尝试解释语义Foo $foo,
在laravel内核执行实例化

$foo = new Foo();

当laravel尝试new依赖的这个类,找不到的话就会报错找不到这个类。

当laravel找到的依赖的类。这个类本身依赖注入的类不明确时。
会爆其他错误:不能解析。

另一种方式

当使用App:bind这个方法时,laravel会优先通过这里加载这个类
而不是 反射机制 根据命名空间或者require路径去寻找。

App::bind('Foo',function(){
    dd('called here');
    return new Foo();
})

service provider

service provider 的类中,有意义的是register()方法,

在index.php里面有$app这个变量,这是框架的核心。

$this->app->sigleton('files',functionZ(){
    return new Filesystem;
})

在这里的‘files’我们可以看做键名,指向Filesystem这个类的实例化。
这样我们可以在路由中试用下看看。

Route::get('/',function(Foo $foo){
  $content = app('files')->get(__DIR__.'/kernel.php');
  dd($content);
});
//打印出来这个文件的内容

使用service provider中注册并提供一个简明的键名去申明这个类,
可以节约代码,易于管理。

在Ioc容器中添加自己的类

比如说项目中需要收费的功能,

app目录下新建目录billing,生成类文件

Class stripe{
  public function pay(){
        dd('I pay');
    }
}

artisan命令:
php artisan make:provider BillingServicePovider

在 app/Providers目录下生成BillingServicePovider.php文件,
里面有意义的就是register(),我们在这里面注册stripe类。

public function register(){
  $this->app->bind('billing',fintion(){
    return new Stripe();
    });
}

再把它放到IOC容器中,config/app.php中添加。

此时,app(‘billing’)就可以相当于new Stripe()了。

app(‘billing’)->pay();//输出 I pay

Facade(门面)运行机制

config/app.php下 alias数组,

用IDE的查找类的功能点进去,比如点route,Mail
进去一个类是继承的Facade,

里面有 getFacadeAccessor()函数返回一个字符串,//return ‘mailer’;
这个字符串对应MailserviceProvider.php中register()的字符串。

Facade类里面有__callStatic(),里面有

$instance = static::getFacadeRoot();//IOC容器生成$instance,可以使用真正类中的方法

//看下面
pubilic static function getFacadeRoot(){
  return static::resolveFacadeInstance(static::getFacadeAccessor());//app('mailer');
}

流程:

app.php下的别名数组的值指向一个类,这个类通过返回给Facade一个字符串实现app(‘name’),
然后通过serviceProvider去注册返回实例化之后的类。


contract (契约)

在项目目录framework/src/Illuminate/Contracts下面定义了很多interface。

面向接口编程。在依赖注入中很大好处。该目录下的类都是接口类。

比如:Config真正是新的类,在Illuminate\Contracts\Config\Repository,

app()方法:

dd(app('Illuminate\Contracts\Config\Repository')['database']['default']);//可以得到 mysql

dd(Config::get());//可以看到laravel的配置

dd(app('config')['database']['default']);//可以得到 mysql

contract给出整个框架给出的扩展接口,比如config类不能满足你的需求,
可以自己集成config接口实现这个类。

Container的背后

这几种的返回结果都一样

dd(Hash::make('pwd'));//dsdsvdrssrbsssret5u4h6l8m

dd(app('Hash')->make('pwd'));

dd(app()['Hash']->make('pwd'));

dd(app('Illuminate\Hashing\BcryptHasher')->make('pwd'));

想知道laravel单个功能的源代码,比如hash的功能,去serviceProvider去找。

$this->app->singleton('hash',function(){
  return new BcryptHasher;
})

singleton,bind是在核心的Contianer里面,

callFunction 是 Clouse ,会在singleton,bind中进行判断。

singleton,将hash的值保存到container的binding数组。

make(),build()可以通过serviceProvider、别名实现实例。

app()判断传入的是key => serviceProvider去实现实例

包含命名空间的类 => Application.php 中 registerCoreContainerAliases()

Application.php里面是laravel的核心内容。