2018-10-18

<h2>前言</h2>

<p>  老实说,第一次老大让我看laravel框架手册的那天早上,我是很绝望的,因为真的没接触过,对我这种渣渣来说,laravel的入门门槛确实有点高了,但还是得硬着头皮看下去(虽然到现在我还有很多没看懂,也没用过)。<br>  后面慢慢根据公司项目的代码对laravel也慢慢熟悉起来了,但还是停留在一些表面的功能,例如依赖注入,ORM操作,用户认证这些和我项目业务逻辑相关的操作,然后对于一些架构基础的,例如服务提供器,服务容器,中间件,Redis等这些一开始就要设置好的东西,我倒是没实际操作过(因为老大一开始就做好了),所以看手册还是有点懵。<br>  所以有空的时候逛逛论坛,搜下Google就发现许多关于laravel核心架构的介绍,以及如何使用的网站(确实看完后再去看手册就好理解多了),下面就根据一个我觉得不错的网站上面的教学来记录一下laravel核心架构的学习<br>网站地址:<a href="https://laraweb.net/" rel="nofollow noreferrer">https://laraweb.net/</a> 这是一个日本的网站,我觉得挺适合新手的,内容用浏览器翻译过来就ok了,毕竟日文直翻过来很好理解的</p>

<h2>关于服务容器</h2>

<p>  手册上是这样介绍的:Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具。依赖注入这个花俏名词实质上是指:类的依赖项通过构造函数,或者某些情况下通过「setter」方法「注入」到类中。。。。。。(真的看不懂啥意思)<br>  服务容器是用于管理类(服务)的实例化的机制。直接看看服务容器怎么用</p>

<p>  1.在服务容器中注册类(bind)</p>

```

$this-&gt;app-&gt;bind('sender','MailSender');

//$this-&gt;app成为服务容器。

```

<p>  2.从服务容器生成类(make)</p>

```

$sender = $this-&gt;app-&gt;make('sender');

//从服务容器($this-&gt;app)创建一个sender类。

在这种情况下,将返回MailSender的实例。

```

<p>  这是服务容器最简单的使用,下面是对服务容器的详细介绍<br>(主要参考:<a href="https://www.cnblogs.com/lyzg/p/6181055.html#_label2" rel="nofollow noreferrer">https://www.cnblogs.com/lyzg/...</a>)</p>

<h3>laravel容器基本认识</h3>

<p>  一开始,index.php 文件加载 Composer 生成定义的自动加载器,然后从 bootstrap/app.php 脚本中检索 Laravel 应用程序的实例。Laravel 本身采取的第一个动作是创建一个 application/ service container 的实例。</p>

```

$app = new Illuminate\Foundation\Application(

    dirname(__DIR__)

);

```

<p>  这个文件在每一次请求到达laravel框架都会执行,所创建的$app即是laravel框架的应用程序实例,它在整个请求生命周期都是唯一的。laravel提供了很多服务,包括认证,数据库,缓存,消息队列等等,$app作为一个容器管理工具,负责几乎所有服务组件的实例化以及实例的生命周期管理。当需要一个服务类来完成某个功能的时候,仅需要通过容器解析出该类型的一个实例即可。从最终的使用方式来看,laravel容器对服务实例的管理主要包括以下几个方面:</p>

<ul>

<li><strong>服务的绑定与解析</strong></li>

<li><strong>服务提供者的管理</strong></li>

<li><strong>别名的作用</strong></li>

<li><strong>依赖注入</strong></li>

</ul>

<p>先了解如何在代码中获取到容器实例,再学习上面四个关键</p>

<h4>如何在代码中获取到容器实例</h4>

<p>第一种是</p>

```

$app = app();

//app这个辅助函数定义在\vendor\laravel\framework\src\Illuminate\Foundation\helper.php

里面,,这个文件定义了很多help函数,并且会通过composer自动加载到项目中。

所以,在参与http请求处理的任何代码位置都能够访问其中的函数,比如app()。

```

<p>第二种是</p>

```

Route::get('/', function () {

    dd(App::basePath());

    return '';

});

//这个其实是用到Facade,中文直译貌似叫门面,在config/app.php中,

有一节数组aliases专门用来配置一些类型的别名,第一个就是'App' =&gt; Illuminate\Support\Facades\App::class,

具体的Google一下laravel有关门面的具体实现方式

```

<p>第三种是</p>

<p>  在服务提供者里面直接使用$this-&gt;app。服务提供者后面还会介绍,现在只是引入。因为服务提供者类都是由laravel容器实例化的,这些类都继承自Illuminate\Support\ServiceProvider,它定义了一个实例属性$app:</p>

```

abstract class ServiceProvider

{

    protected $app;

```

<p>  laravel在实例化服务提供者的时候,会把laravel容器实例注入到这个$app上面。所以我们在服务提供者里面,始终能通过$this-&gt;$app访问到laravel容器实例,而不需要再使用app()函数或者App Facade了。</p>

<h4>如何理解服务绑定与解析</h4>

<p>  浅义层面理解,容器既然用来存储对象,那么就要有一个对象存入跟对象取出的过程。这个对象存入跟对象取出的过程在laravel里面称为服务的绑定与解析。</p>

```

app()-&gt;bind('service', 'this is service1');

app()-&gt;bind('service2', [

    'hi' =&gt; function(){

        //say hi

    }

]);

class Service {

}

app()-&gt;bind('service3', function(){

    return new Service();

});

```

<p>  还有一个单例绑定singleton,是bind的一种特殊情况(第三个参数为true),绑定到容器的对象只会被解析一次,之后的调用都返回相同的实例</p>

```

public function singleton($abstract, $concrete = null)

{

$this-&gt;bind($abstract, $concrete, true);

}

```

<p>  在绑定的时候,我们可以直接绑定已经初始化好的数据(基本类型、数组、对象实例),还可以用匿名函数来绑定。用匿名函数的好处在于,这个服务绑定到容器以后,并不会立即产生服务最终的对象,只有在这个服务解析的时候,匿名函数才会执行,此时才会产生这个服务对应的服务实例。<br>  实际上,当我们使用singleton,bind方法以及数组形式,(这三个方法是后面要介绍的绑定的方法),进行服务绑定的时候,如果绑定的服务形式,不是一个匿名函数,也会在laravel内部用一个匿名函数包装起来,这样的话, 不轮绑定什么内容,都能做到前面介绍的懒初始化的功能,这对于容器的性能是有好处的。这个可以从bind的源码中看到一些细节:</p>

```

if (! $concrete instanceof Closure) {

    $concrete = $this-&gt;getClosure($abstract, $concrete);

}

```

<p>看看bind的底层代码</p>

```

public function bind($abstract, $concrete = null, $shared = false)

```

<p>  第一个参数服务绑定名称,第二个参数服务绑定的结果(也就是闭包,得到实例),第三个参数就表示这个服务是否在多次解析的时候,始终返回第一次解析出的实例(也就是单例绑定singleton)。</p>

<p>  服务绑定还可以通过数组的方式:</p>

```

app()['service'] = function(){

    return new Service();

};

```

<p>绑定大概就这些,接下来看解析,也就是取出来用</p>

```

$service= app()-&gt;make('service');

```

<p>  这个方法接收两个参数,第一个是服务的绑定名称和服务绑定名称的别名,如果是别名,那么就会根据服务绑定名称的别名配置,找到最终的服务绑定名称,然后进行解析;第二个参数是一个数组,最终会传递给服务绑定产生的闭包。</p>

<p>看源码:</p>

```

/**

* Resolve the given type from the container.

*

* @param  string  $abstract

* @param  array  $parameters

* @return mixed

*/

public function make($abstract, array $parameters = [])

{

    return $this-&gt;resolve($abstract, $parameters);

}

/**

* Resolve the given type from the container.

*

* @param  string  $abstract

* @param  array  $parameters

* @return mixed

*/

protected function resolve($abstract, $parameters = [])

{

    $abstract = $this-&gt;getAlias($abstract);

    $needsContextualBuild = ! empty($parameters) || ! is_null(

        $this-&gt;getContextualConcrete($abstract)

    );

    // If an instance of the type is currently being managed as a singleton we'll

    // just return an existing instance instead of instantiating new instances

    // so the developer can keep using the same objects instance every time.

    if (isset($this-&gt;instances[$abstract]) &amp;&amp; ! $needsContextualBuild) {

        return $this-&gt;instances[$abstract];

    }

    $this-&gt;with[] = $parameters;

    $concrete = $this-&gt;getConcrete($abstract);

    // We're ready to instantiate an instance of the concrete type registered for

    // the binding. This will instantiate the types, as well as resolve any of

    // its "nested" dependencies recursively until all have gotten resolved.

    if ($this-&gt;isBuildable($concrete, $abstract)) {

        $object = $this-&gt;build($concrete);

    } else {

        $object = $this-&gt;make($concrete);

    }

    // If we defined any extenders for this type, we'll need to spin through them

    // and apply them to the object being built. This allows for the extension

    // of services, such as changing configuration or decorating the object.

    foreach ($this-&gt;getExtenders($abstract) as $extender) {

        $object = $extender($object, $this);

    }

    // If the requested type is registered as a singleton we'll want to cache off

    // the instances in "memory" so we can return it later without creating an

    // entirely new instance of an object on each subsequent request for it.

    if ($this-&gt;isShared($abstract) &amp;&amp; ! $needsContextualBuild) {

        $this-&gt;instances[$abstract] = $object;

    }

    $this-&gt;fireResolvingCallbacks($abstract, $object);

    // Before returning, we will also set the resolved flag to "true" and pop off

    // the parameter overrides for this build. After those two things are done

    // we will be ready to return back the fully constructed class instance.

    $this-&gt;resolved[$abstract] = true;

    array_pop($this-&gt;with);

    return $object;

}

```

<p>第一步:</p>

```

$needsContextualBuild = ! empty($parameters) || ! is_null(

    $this-&gt;getContextualConcrete($abstract)

);

```

<p>  该方法主要是区分,解析的对象是否有参数,如果有参数,还需要对参数做进一步的分析,因为传入的参数,也可能是依赖注入的,所以还需要对传入的参数进行解析;这个后面再分析。</p>

<p>第二步:</p>

```

if (isset($this-&gt;instances[$abstract]) &amp;&amp; ! $needsContextualBuild) {

    return $this-&gt;instances[$abstract];

}

```

<p>  如果是绑定的单例,并且不需要上面的参数依赖。我们就可以直接返回 $this-&gt;instances[$abstract]。</p>

<p>第三步:</p>

```

$concrete = $this-&gt;getConcrete($abstract);

...

/**

* Get the concrete type for a given abstract.

*

* @param  string  $abstract

* @return mixed  $concrete

*/

protected function getConcrete($abstract)

{

    if (! is_null($concrete = $this-&gt;getContextualConcrete($abstract))) {

        return $concrete;

    }

    // If we don't have a registered resolver or concrete for the type, we'll just

    // assume each type is a concrete name and will attempt to resolve it as is

    // since the container should be able to resolve concretes automatically.

    if (isset($this-&gt;bindings[$abstract])) {

        return $this-&gt;bindings[$abstract]['concrete'];

    }

    return $abstract;

}

```

<p>  这一步主要是先从绑定的上下文找,是不是可以找到绑定类;如果没有,则再从 $bindings[] 中找关联的实现类;最后还没有找到的话,就直接返回 $abstract 本身。</p>

```

// We're ready to instantiate an instance of the concrete type registered for

// the binding. This will instantiate the types, as well as resolve any of

// its "nested" dependencies recursively until all have gotten resolved.

if ($this-&gt;isBuildable($concrete, $abstract)) {

    $object = $this-&gt;build($concrete);

} else {

    $object = $this-&gt;make($concrete);

}

...

/**

* Determine if the given concrete is buildable.

*

* @param  mixed  $concrete

* @param  string  $abstract

* @return bool

*/

protected function isBuildable($concrete, $abstract)

{

    return $concrete === $abstract || $concrete instanceof Closure;

}

```

<p>  如果之前找到的 $concrete 返回的是 $abstract 值,或者 $concrete 是个闭包,则执行 $this-&gt;build($concrete),否则,表示存在嵌套依赖的情况,则采用递归的方法执行 $this-&gt;make($concrete),直到所有的都解析完为止。</p>

<p><em>$this-&gt;build($concrete)</em></p>

```

/**

* Instantiate a concrete instance of the given type.

*

* @param  string  $concrete

* @return mixed

*

* @throws \Illuminate\Contracts\Container\BindingResolutionException

*/

public function build($concrete)

{

    // If the concrete type is actually a Closure, we will just execute it and

    // hand back the results of the functions, which allows functions to be

    // used as resolvers for more fine-tuned resolution of these objects.

    // 如果传入的是闭包,则直接执行闭包函数,返回结果

    if ($concrete instanceof Closure) {

        return $concrete($this, $this-&gt;getLastParameterOverride());

    }

    // 利用反射机制,解析该类。

    $reflector = new ReflectionClass($concrete);

    // If the type is not instantiable, the developer is attempting to resolve

    // an abstract type such as an Interface of Abstract Class and there is

    // no binding registered for the abstractions so we need to bail out.

    if (! $reflector-&gt;isInstantiable()) {

        return $this-&gt;notInstantiable($concrete);

    }

    $this-&gt;buildStack[] = $concrete;

    // 获取构造函数

    $constructor = $reflector-&gt;getConstructor();

    // If there are no constructors, that means there are no dependencies then

    // we can just resolve the instances of the objects right away, without

    // resolving any other types or dependencies out of these containers.

    // 如果没有构造函数,则表明没有传入参数,也就意味着不需要做对应的上下文依赖解析。

    if (is_null($constructor)) {

        // 将 build 过程的内容 pop,然后直接构造对象输出。

        array_pop($this-&gt;buildStack);

        return new $concrete;

    }

    // 获取构造函数的参数

    $dependencies = $constructor-&gt;getParameters();

    // Once we have all the constructor's parameters we can create each of the

    // dependency instances and then use the reflection instances to make a

    // new instance of this class, injecting the created dependencies in.

    // 解析出所有上下文依赖对象,带入函数,构造对象输出

    $instances = $this-&gt;resolveDependencies(

        $dependencies

    );

    array_pop($this-&gt;buildStack);

    return $reflector-&gt;newInstanceArgs($instances);

}

```

<p>上面这一段有关解析make的介绍主要参考:<br><a href="https://juejin.im/post/5b012f796fb9a07ace5925be" rel="nofollow noreferrer">coding01:看 Laravel 源代码了解 Container</a></p>

<p>  这一篇就主要学习laravel的服务容器以及它的绑定和解析,虽然目前能力无法对框架源码每一个地方都弄懂,但通过这几篇优秀的文章,我将其进行整理结合,这过程让我更加理解laravel的一些核心内容,起码别人问起来我多多少少能说出一些,这就是进步。</p>

<p>  后面有关服务提供者,依赖注入,中间件等内容的学习将放在后续的博客文章中,欢迎看看我的其他博客文章:<a href="https://zgxxx.github.io/" rel="nofollow noreferrer">https://zgxxx.github.io/</a>。<br>  以上相关知识的引用已经注明出处,若有侵权,请联系我,感谢这些优秀文章的作者</p>

原文地址:https://zgxxx.github.io/2018/10/14/20181013/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 本文面向php语言的laravel框架的用户,介绍一些laravel框架里面容器管理方面的使用要点。文章很长,但是...
    spacexxxx阅读 1,141评论 0 1
  • 由于本人在学习laravel框架,在框架里面发现了很多的匿名函数,由于本来对这个没有了解,所以理解不了对应的框架逻...
    小山丘321阅读 666评论 0 0
  • 每天早晨走路半小时上班,坚持了有快4个月了,身体感觉越来越健康,走路变成了一件很愉快的事情。 久不见面的朋友见面时...
    感觉教练_PCC阅读 1,000评论 0 2
  • 以我数百次大大小小的经验来看,要想完全不害怕考试根本就是不可能的(除非在内心深处你压根不把它当做考试)。...
    有头脑阅读 437评论 0 0