laravel核心概念总结

这篇博客在草稿箱堆了挺久的,原本还想趁着过年期间写完这篇博客的,后来又没写成,不过还是趁着上班后的几天写好了,想着使用laravel希望能多了解其内部源码的实现原理,所以结合文档和源码写了这篇博客。以下内容如果有错欢迎指出。

服务容器

服务容器的概念理解可以查看这里我写的服务容器相关概念。

结合文档和源码来分析一下laravel是如何实现的

根据文档的介绍,Laravel 服务容器是一个用于管理类依赖以及实现依赖注入的强有力工具。而服务容器正是laravel实现功能的核心利器。

laravel的服务容器提供了几个比较核心的方法

  • singleton($abstract, $concrete = null)
  • bind($abstract, $concrete = null, $shared = false)
  • make($abstract, array $parameters = [])

实际上singleton和bind调用的函数实现是相同的,只是在调用参数上,将$shared变量置为true,通过$shared来判断是否为单例对象

/**
* 添加单例类到容器中
* @param  string  $abstract
* @param  \Closure|string|null  $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}

查看bind()方法,根据文档所给出的内容,bind 方法的第一个参数为要绑定的类 / 接口名,第二个参数是一个返回类实例的 Closure

    /**
     * 添加绑定类到容器中
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        //删除容器实例和别名中$abstract的值
        $this->dropStaleInstances($abstract);

        //判断是否为空,若为空则等于$abstract,后续容器可通过反射类获取类实例
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        //判断$concrete是否为闭包,如果不为闭包,则转换成闭包的形式
        if (! $concrete instanceof Closure) {
            if (! is_string($concrete)) {
                throw new \TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
            }
            //将$concrete以闭包形式返回,这里调用反射类获取类实例
            $concrete = $this->getClosure($abstract, $concrete);
        }
        //将当前$abstract以key=>value的形式绑定到$this->binding变量中,其中$concrete为返回类实例,$shared用于判断是否为单例对象
        $this->bindings[$abstract] = compact('concrete', 'shared');

        //判断当前实例是否已存在,判断是否需要回调重新更新类实例
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

再来看看make()方法,从容器中解析出类实例,也是容器中比较核心的概念,其本质是调用了resolve方法实现,可查看文档make方法

    /**
     * 从容器中解析当前实例
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

以下为resolve的源码实现

    /**
     * 解析指定类到容器中
     * @param  string  $abstract
     * @param  array  $parameters
     * @param  bool  $raiseEvents
     * @return mixed
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        //查看$abstract是否为别名,如果是则返回真正的类名
        $abstract = $this->getAlias($abstract);
        //获取返回类实例
        $concrete = $this->getContextualConcrete($abstract);
        //
        $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);

        //判断单例数组中是否已实例过,若已实例过则直接返回,这样在整个框架的生命周期中,单例对象永远只有一个
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;
        //判断类实例是否为空,为空则查看$this->bingding绑定对象中是否存在实例,若没有则等于$abstract类名
        if (is_null($concrete)) {
            $concrete = $this->getConcrete($abstract);
        }

        //判断当前类是否可被解析,可以则调用build()方法对闭包或类名进行解析,不可解析则回调make()方法,其中build()方法使用反射机制是实现框架依赖注入的核心
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        //遍历定义的扩展,若已定义扩展则实例返回扩展闭包,这里应该是为了方便后续更改某些类的实现,所以在这里遍历扩展
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        //判断是否为单例对象,为单例则将实例添加到$this->instances实例对象中
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        //将$this->resolved中的类名置为true,表示已解析完成
        $this->resolved[$abstract] = true;

        array_pop($this->with);
        //返回实例
        return $object;
    }

最后再来看看依赖注入的核心build()方法

    /**
     * 实例化具体对象类
     * @param  \Closure|string  $concrete
     * @return mixed
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function build($concrete)
    {
        //在上述讲解,$concrete的值是一个闭包或者类名,这里首先判断值是否为闭包,若为闭包则直接执行,其中$this->getLastParameterOverride()方法用于获取闭包对应参数
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }
        //生成$concrete的反射类,若类不存在则抛出异常
        try {
            $reflector = new ReflectionClass($concrete);
        } catch (ReflectionException $e) {
            throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
        }

        //isInstantiable()为反射类中的函数,用于判断类是否可以被实例化,若类为接口类和抽象类则不能被实例化,则抛出异常
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;
        //获取类的构造函数
        $constructor = $reflector->getConstructor();

        //判断构造函数是否为空,若为空则说明该类没有依赖参数,则直接实例化返回
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }
        //获取构造函数参数
        $dependencies = $constructor->getParameters();

        //解析获取类依赖参数,若解析失败则抛出异常
        try {
            $instances = $this->resolveDependencies($dependencies);
        } catch (BindingResolutionException $e) {
            array_pop($this->buildStack);

            throw $e;
        }

        array_pop($this->buildStack);
        //通过newInstanceArgs()创建类实例并返回
        return $reflector->newInstanceArgs($instances);
    }

服务提供者

根据文档所讲解的,服务提供者是所有 Laravel 应用程序的引导中心。你的应用程序,以及 通过服务器引导的 Laravel 核心服务都是通过服务提供器引导。但是,「引导」是什么意思呢? 通常,我们可以理解为注册,比如注册服务容器绑定,事件监听器,中间件,甚至是路由。服务提供者是配置应用程序的中心。

config/app.php文件中,我们可以查看到所有服务提供者

laravel核心概念总结

这里随便查看一个服务提供者,如以下RedisServiceProvider为使用redis操作的类,根据文档定义我们只需要在register()中定义singleton(),bind()将实例注册到容器中,在这里是将redis操作类绑定为单例对象,redis连接类简单绑定到容器中。同时RedisServiceProvider继承了\Illuminate\Contracts\Support\DeferrableProvider接口并实现provides(),使服务可以被延时加载,注册绑定的服务只有真的需要时才会被加载

laravel核心概念总结

深入分析服务提供者,查看源码是如何实现的

    /**
     * 将配置中的服务提供者注册到容器中
     * @return void
     */
    public function registerConfiguredProviders()
    {
        //用于获取config/app.php中providers变量中的所有服务提供者
        $providers = Collection::make($this->config['app.providers'])
                        ->partition(function ($provider) {
                            return strpos($provider, 'Illuminate\\') === 0;
                        });
        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
        //加载注册服务提供者
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }

register()为注册服务器,将服务提供者中绑定的服务添加到容器中

    /**
     * 将单个服务提供者中的服务注册到容器中
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  bool  $force
     * @return \Illuminate\Support\ServiceProvider
     */
    public function register($provider, $force = false)
    {
        //检查该服务提供者是否已被加载过,若加载过则直接返回,如果$force为true则该服务提供者会重新加载
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        //判断如果传入的$provider变量为字符串,则实例化服务提供者类
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }
        //执行服务提供者的register(),实际上就是注册绑定服务到容器中
        $provider->register();

        //检查类中是否定义$bindings变量,如果定义则将其绑定到容器中,可查看文档https://learnku.com/docs/laravel/7.x/providers/7455#311d29
        if (property_exists($provider, 'bindings')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }
        //检查类中是否定义$singletons变量,如果定义则将其绑定到容器中,可查看文档https://learnku.com/docs/laravel/7.x/providers/7455#311d29
        if (property_exists($provider, 'singletons')) {
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }
        //将该服务提供者标记为已加载
        $this->markAsRegistered($provider);

        //检查服务提供者是否定义boot()方法,若定义则会被执行,可查看文档https://learnku.com/docs/laravel/7.x/providers/7455#ea148f
        if ($this->isBooted()) {
            $this->bootProvider($provider);
        }
        //返回服务提供者实例
        return $provider;
    }

Facades(门面)

根据文档描述Facades 为应用的 服务容器 提供了一个「静态」 接口。Laravel 自带了很多 Facades,可以访问绝大部分功能。Laravel Facades 实际是服务容器中底层类的 「静态代理」 ,相对于传统静态方法,在使用时能够提供更加灵活、更加易于测试、更加优雅的语法。

Facades流程如下所示:
laravel核心概念总结

以Log门面类为例,查看其代码,代码只定义了getFacadeAccessor()方法返回log字符串,实际上我们使用Log日志门面通常为Log::info()这样的形式,当调用静态方法时会触发__callStatic魔术方法

laravel核心概念总结

    public static function __callStatic($method, $args)
    {
        //获取容器中指定的实例,如上述为log,则返回容器中绑定实例中log的实例
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }
        //调用实例中指定方法
        return $instance->$method(...$args);
    }

以上就是Facades(门面)的概念,也比较简单,当然具体的内容也可以看文档Facades

Contracts(契约)

Contracts实际上就是定义一组接口,其理念符合基于接口而非实现编程的设计思想,这里就没什么好说的了。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!