动手写一个Auth扩展,laravel的项目入口文件index.php如下

  • 栏目:前端 时间:2020-04-02 17:55
<返回列表

昨天按照手册教程,动手写一个Auth扩展,按照包独立性的原则,我不希望将Auth::extend()这种方法写在 start.php 中,毫无疑问,我选择了在服务提供器register()方法中注册扩展驱动。然而,事与愿违……

SmartWiki简介请阅读:

laravel的项目入口文件index.php如下

图片 1

因为SmartWiki的演示站点部署在阿里云上,阿里云有一个128M免费的Memcache服务,刚开始按照Memcached的配置方式配置完后,发现Laravel报错,查看日志报错位置是addServer出错,连不上阿里云的Memcache。

图片 2图片 3

发现问题

很无奈,于是安装阿里云的手册写了一个脚本放到服务器上,结果可以连接,也可以写入。

 1 define('LARAVEL_START', microtime(true)); 2  3 require __DIR__.'/../vendor/autoload.php'; 4  5 $app = require_once __DIR__.'/../bootstrap/app.php'; 6  7 $kernel = $app->make(IlluminateContractsHttpKernel::class); 8  9 $response = $kernel->handle(10     $request = IlluminateHttpRequest::capture()11 );12 13 $response->send();14 15 $kernel->terminate($request, $response);

当我在 LoauthServiceProvider 中这样写的时候:

阿里云提供的脚本如下:

View Code

public function register()
{
    //
    Auth::extend('loauth',function($app){});
}
<?php$connect = new Memcached;  //声明一个新的memcached链接$connect->setOption(Memcached::OPT_COMPRESSION, false); //关闭压缩功能$connect->setOption(Memcached::OPT_BINARY_PROTOCOL, true); //使用binary二进制协议$connect->addServer('00000000.ocs.aliyuncs.com', 11211); //添加OCS实例地址及端口号//$connect->setSaslAuthData('aaaaaaaaaa, 'password'); //设置OCS帐号密码进行鉴权,如已开启免密码功能,则无需此步骤$connect->set("hello", "world");echo 'hello: ',$connect->get("hello");print_r( $connect->getVersion;$connect->quit();

第一句记录了项目开始运行时间。

报错

翻看laravel的Memcached驱动,在 /vendor/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php 中创建Memcached对象的代码如下:

第二句引入了基于composer的自动加载模块。

Call to undefined method IlluminateSupportFacadesAuth::extend()
public function connect(array $servers){    $memcached = $this->getMemcached();    // For each server in the array, we'll just extract the configuration and add    // the server to the Memcached connection. Once we have added all of these    // servers we'll verify the connection is successful and return it back.    foreach ($servers as $server) {        $memcached->addServer(            $server['host'], $server['port'], $server['weight']        );    }    $memcachedStatus = $memcached->getVersion();    if (! is_array($memcachedStatus)) {        throw new RuntimeException('No Memcached servers added.');    }    if (in_array('255.255.255', $memcachedStatus) && count(array_unique($memcachedStatus)) === 1) {        throw new RuntimeException('Could not establish Memcached connection.');    }    return $memcached;}

第三句引入了laravel应用主体。

寻找原因

可以看到laravel的Memcached没有设置setOption方法的选项,仅仅包含最简连接建立,紧接着就调用getVersion来测试是否连通。而阿里云的演示代码是设置了关闭压缩和使用binary二进制协议的选项的。

第四句创建了一个用于处理请求的核心。

当时就纳闷了,找原因,怀疑是Auth没注册?检查发现注册了,因为在路由中可以使用;php artisan clear-compiled 没用;百思不得其解,甚至怀疑是我不小心修改了核心类,还重新下载了一次laravel包,问题依旧。

没办法只能自己来扩展Memcached的功能实现自定义选项。laravel中扩展缓存可以使用Cache::extend来扩展。扩展代码如下:

第五句对实例化后的request对象进行解析并返回执行后的response响应对象。

折腾了一晚上,最终我把目光锁定在 AuthServiceProvider 的 $defer 属性。

Cache::extend('MemcachedExtend', function ($app) {    $memcached = $this->createMemcached($app);    // 从配置文件中读取缓存前缀    $prefix = $app['config']['cache.prefix'];    // 创建 MemcachedStore 对象    $store = new MemcachedStore($memcached, $prefix);    // 创建 Repository 对象,并返回    return new Repository($store);});

/** * 创建Memcached对象 * @param $app * @return mixed */protected function createMemcached($app){    // 从配置文件中读取 Memcached 服务器配置    $servers = $app['config']['cache.stores.MemcachedExtend.servers'];    // 利用 IlluminateCacheMemcachedConnector 类来创建新的 Memcached 对象    $memcached = new Memcached;    foreach ($servers as $server) {        $memcached->addServer(            $server['host'], $server['port'], $server['weight']        );    }    // 如果服务器上的 PHP Memcached 扩展支持 SASL 认证    if (ini_get('memcached.use_sasl') && isset($app['config']['cache.storess.MemcachedExtend.memcached_user']) && isset($app['config']['cache.storess.MemcachedExtend.memcached_pass'])) {        // 从配置文件中读取 sasl 认证用户名        $user = $app['config']['cache.storess.MemcachedExtend.memcached_user'];        // 从配置文件中读取 sasl 认证密码        $pass = $app['config']['cache.storess.MemcachedExtend.memcached_pass'];        // 指定用于 sasl 认证的账号密码        $memcached->setSaslAuthData($user, $pass);    }    //扩展    if (isset($app['config']['cache.stores.MemcachedExtend.options'])) {        foreach ($app['config']['cache.stores.MemcachedExtend.options'] as $key => $option) {            $memcached->setOption($key, $option);        }    }    $memcachedStatus = $memcached->getVersion();    if (! is_array($memcachedStatus)) {        throw new RuntimeException('No Memcached servers added.');    }    if (in_array('255.255.255', $memcachedStatus) && count(array_unique($memcachedStatus)) === 1) {        throw new RuntimeException('Could not establish Memcached connection.');    }    return $memcached;}

第六句将响应内容进行输出。

根据手册以及注释,我们得知 $defer 属性是用来延迟加载该服务提供器,说直白点就是延迟执行 register() 方法,只需要配合provides()方法即可实现。举个例子:

网上有流传的laravel缓存扩展的文章,其中对配置读取在5.2以上版本不适用。

第七句结束应用并释放资源。

public function provides()
{
    return array('auth');
}

缓存扩展后的代码是需要创建一个ServiceProvider来注册服务提供者。服务提供者是Laravel应用启动的中心,你自己的应用以及所有Laravel的核心服务都是通过服务提供者启动。

关于第二句,这里我先解释一下自动加载,我们都知道php中如果要使用文件外的代码,需要使用require等方法先将文件引入,然后就可以使用被引入那个文件的代码了。但是我们平时使用框架编写代码的时候就不需要这么做了,只需要use命名空间,便可以直接new出对象了,这就要归功于自动加载了,php在new出当前不存在的对象时,会触发__autoload、spl_autoload等一些魔术方法。tp3的处理方式遍是非常粗暴的,在autoload魔术方法中,将当前类use的命名空间与我们new的对象名进行字符串拼接,随后require该文件就完了。laravel使用了composer显然就高级的多,不过再怎么高级,composer本身也是做了类似的操作,所以它也使用了spl_autoload函数,它高级在哪呢?我们都知道composer使用时可以新建一个json文件将需要的依赖编写在里面,composer运行时就会自动下载这些文件了。用composer 做自动加载也是一样,它将json文件里写入的依赖进行缓存成了key/value的关联数组,触发spl_autoload函数的时候便根据这些映射来require。存放在laravelvendorcomposerautoload_classmap.php文件内,有兴趣的朋友可自行观看,这里不是重点,便到此为止了。(我初学php面向对象的时候一直以为命名空间下面那些use就是用来替代require、include的。直到后来学习mvc概念的时候自己试着做了个微框架的demo,才搞清楚use只是起到明确命名空间的作用。)

这个是 AuthServiceProvider 里的方法,当框架初始化的时候,会依次加载服务提供器,如果发现这个服务提供器protected $defer=true 那么就会调用它的 provides() 方法,其返回的数组包含需要延迟加载的服务名称,这样当我们在路由、控制器或者其他地方调用 Auth::METHOD() 的时候,才会去调用提供器的 register() 方法。

但是,我们所谓的“启动”指的是什么?通常,这意味着注册事物,包括注册服务容器绑定、事件监听器、中间件甚至路由。服务提供者是应用配置的中心。

我们都知道,通常一个web程序所做的事,不外乎这么几点:

确定症结

如果你打开Laravel自带的config/app.php文件,将会看到一个providers数组,这里就是应用所要加载的所有服务提供者类,当然,其中很多是延迟加载的,也就是说不是每次请求都会被加载,只有真的用到它们的时候才会加载。

1、用户从浏览器进行请求,请求

那么问题来了,既然是被动延迟加载,也就是说当我调用Auth类方法时应该会自动实例化Auth类啊,为什么我在LoauthServiceProvider中调用的时候却提示方法不存在,但是在路由中却可以呢。

所有的服务提供者都继承自IlluminateSupportServiceProvider类。大部分服务提供者都包含两个方法: register 和 boot 。在register方法中,你唯一要做的事情就是绑事物到服务容器,不要尝试在其中注册事件监听器,路由或者任何其它功能。

2、程序接到请求开始运算,网页程序

我猜测是因为优先级的问题,可能在框架注册 LoauthServiceProvider::register() 的时候,Auth 还没有标记为延迟加载,这就造成了一个先后问题,任何即时加载的服务提供器都无法在register方法中调用延迟加载的服务。

通过Artisan命令make:provider可以简单生成一个新的提供者:

3、运算结果渲染成网页返回给浏览器,网页响应

经过研究,顺利在核心代码中找到证据 IlluminateFoundationProviderRepository

php artisan make:provider MemcachedExtendServiceProvider

我们所写的大量代码都只是为了更好、更快、更方便的去做这3件事而已。

public function load(Application $app, array $providers)
{
    //...省略
    // We will go ahead and register all of the eagerly loaded providers with the
    // application so their services can be registered with the application as
    // a provided service. Then we will set the deferred service list on it.
    foreach ($manifest['eager'] as $provider)
    {
        $app->register($this->createProvider($app, $provider));
    }
    //延迟加载标记在即时加载服务之后
    $app->setDeferredServices($manifest['deferred']);
}

所有服务提供者都是通过配置文件config/app.php中进行注册,该文件包含了一个列出所有服务提供者名字的providers数组,默认情况下,其中列出了所有核心服务提供者,这些服务提供者启动Laravel核心组件,比如邮件、队列、缓存等等。

index文件中的$request、$kernel、$response这三个变量就分别与这三点进行对应了。laravel中也将请求、响应、和计算进行了分离,请求部分使用syfmony的request组件对浏览器发出的请求头等信息进行收集打包,形成一个对象来方便我们操作。$kernel算是laravel的请求处理核心了,通过request里的url找到相应路由的控制器,执行后返回视图等响应,并将$response输出至浏览器。知道大概流程后我们来看laravel的核心部分。

解决之道

要注册你自己的服务提供者,只需要将其追加到该数组中即可:

第三句代码就是在引入laravel的应用了,我们跳到G:wamp64wwwtestlaravel55bootstrapapp.php文件内,我们会发现这个文件所做的事情也不多,只是new了一个application对象,调用了三次singleton方法,便将application实例给返回到index文件中了。(而这里new对象的时候整个文件都没有写require等代码,这就是通过composer进行的自动加载起作用了。)application文件位于G:wamp64wwwtestlaravel55vendorlaravelframeworksrcIlluminateFoundationApplication.php,new application时传入了当前的路径到它的构造方法里,它的构造方法执行了setBasePath、registerBaseBindings、registerBaseServiceProviders、registerCoreContainerAliases这几个方法。

虽然发现了问题所在,但并不代表问题就解决了,修改核心代码不是个明智的选择,所以只能在我们自己的包里想办法咯,一个解决方案如下:

'providers' => [    SmartWikiProvidersMemcachedExtendServiceProvider::class //在providers节点添加实现的provider]

setBasePath:就是将各个系统关键类的路径存储在了app容器对象里,跟踪到bindPathsInContainer方法里,我们会发现如下所示的,存储的路径,具体的实现代码在其父类Container类的instance方法中,代码很简单就一句是$this->instances[$abstract]

$instance;。大家可以在Application类的setBasePath方法之后使用dd()打印一下$this看看它的instance属性

 1     protected function bindPathsInContainer() 2     { 3         $this->instance('path', $this->path; 4         $this->instance('path.base', $this->basePath; 5         $this->instance('path.lang', $this->langPath; 6         $this->instance('path.config', $this->configPath; 7         $this->instance('path.public', $this->publicPath; 8         $this->instance('path.storage', $this->storagePath; 9         $this->instance('path.database', $this->databasePath;10         $this->instance('path.resources', $this->resourcePath;11         $this->instance('path.bootstrap', $this->bootstrapPath;12     }

图片 4

registerBaseBindings:这个方法做的事情和上一个差不多,将$this,application对象及它的父类存入了instance属性中,分别起了app和Container两个名字。将vendor路径与bootstrap/cache/packages.php里的providers服务提供者路径传入PackageManifest类中,并绑定在了app对象的instance中,大家在这个方法后dd会发现跟setBasePath一样,instance属性中多了几条,只是其中三个是对象而已。

图片 5

registerBaseServiceProviders:注册了基本的providers,event事件服务提供者、log日志服务提供者、routing路由部分服务提供者。服务提供者的部分会在后面解释,现在把它看做是一个功能模块的入口就可以了。同样的,我们在这个方法后面dd会发先serviceProviders属性与loadedProviders属性增加了对应的值。bindings属性也增加了provider相应的boot闭包,闭包中存储的是实例化对象的代码,运行后会得到一个对象实例,以闭包的形式存储来实现按需加载。

图片 6

registerCoreContainerAliases:跟它的名字说的一样,只是注册了容器的核心类别名,同样打印后发现在aliases、abstractAliases属性中增加了相应的映射数组。以后会根据这个别名来方便的实例化对象,这个列表太长我就不放图了

好的,总结一下,application类初始化的时候它做了这么些工作:

1、设置路径 2、绑定了app对象和packages包的实例 3、注册了基本服务提供者 4、增加了核心类的别名 全都是一些配置工作。

好,回到app.php文件,$app执行了三个singleton方法,通过注释我们可以知道它绑定了一些重要的接口道容器中。我们点击跳转后一路跟踪到G:wamp64wwwtestlaravel55vendorlaravelframeworksrcIlluminateContainerContainer.php文件的bind方法中看着很长的代码,其实都是状态判断,这个函数所做的事情还是讲传入的类名路径转换为一个启动服务的闭包,并保存在容器的bindings属性中。见下方代码,getClosure方法也可以看一下,比较简单。

 1     public function bind($abstract, $concrete = null, $shared = false) 2     { 3         //抽象类型判断 4         $this->dropStaleInstances($abstract); 5  6         if (is_null($concrete)) { 7             $concrete = $abstract; 8         } 9 10         //这一阶段重点,刚刚我们index传入的类路径不是闭包,就会在这里被getClosure方法转换成一个返回对象实例的闭包了11         if (! $concrete instanceof Closure) {12             $concrete = $this->getClosure($abstract, $concrete);13         }14         //将闭包绑定在bindings属性中15         $this->bindings[$abstract] = compact('concrete', 'shared');16 17         // If the abstract type was already resolved in this container we'll fire the18         // rebound listener so that any objects which have already gotten resolved19         // can have their copy of the object updated via the listener callbacks.20         if ($this->resolved($abstract)) {21             $this->rebound($abstract);22         }23     }    

我们在app.php文件的singleton之后再次dd会发现bindings属性中多出了几个相应的属性,见下图,其中httpkernel用来处理http请求,consolekernel用来处理artisan命令行请求,debugexceptionHandler便是处理异常错误的了。

图片 7

app.php文件看完了,我们再回到index.php文件,第四行laravel制造了一个kernel实例,还记得刚刚在app.php文件时,我们通过singleton绑定的那个闭包函数吗?这里马上就派上用场了,make顾名思义就制造,这个方法通过类名路径或别名返回一个对象实例(对,还记得刚刚application对象构造函数绑定了一大堆别名吗)。G:wamp64wwwtestlaravel55vendorlaravelframeworksrcIlluminateFoundationApplication.php类的make方法

 1     public function make($abstract, array $parameters = []) 2     { 3         //这里获取了传入类的别名,getAlias方法通过递归取出存储在容器中的别名,不过现在kernel没有别名所以还是刚刚传入的类路径 4         $abstract = $this->getAlias($abstract); 5         //也不是延迟加载服务直接跳转到父类make方法 6         if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) { 7             $this->loadDeferredProvider($abstract); 8         } 9 10         return parent::make($abstract, $parameters);11     }

G:wamp64wwwtestlaravel55vendorlaravelframeworksrcIlluminateContainerContainer.php在container类的make方法就开始从容器中解析类了,一开始那一大段都是检测上下文绑定的,这个属于契约接口的动态调用,暂时可以不去看它,重点在于从getConcrete方法获取到闭包后,直接进入了build构建方法。

图片 8图片 9

    public function make($abstract, array $parameters = [])    {        return $this->resolve($abstract, $parameters);    }    protected function resolve($abstract, $parameters = [])    {        $abstract = $this->getAlias($abstract);        //是否存在构建上下文,此出为了服务提供者的契约        $needsContextualBuild = ! empty($parameters) || ! is_null(            $this->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.        //instances数组中有该类,并且不需要构建上下文的话,便直接返回该类实例        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {            return $this->instances[$abstract];        }        //将实例化类所需的参数存入数组        $this->with[] = $parameters;        //获取该类闭包,若无则还是返回类名字符串        $concrete = $this->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.        //若当前所make的类没有上下文绑定,并且是一个闭包则直接进行构建,否则再次递归make方法获得契约所绑定类        if ($this->isBuildable($concrete, $abstract)) {            $object = $this->build($concrete);        } else {            $object = $this->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->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.        //若该类绑定时设置为共享,则缓存至instances单例数组        if ($this->isShared($abstract) && ! $needsContextualBuild) {            $this->instances[$abstract] = $object;        }        $this->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->resolved[$abstract] = true;        array_pop($this->with);        return $object;    }

View Code

还记得刚刚在app.php文件中有一个singleton函数将IlluminateContractsHttpKernel::class绑定为了AppHttpKernel::class类吗?当build方法执行到kernel的构造函数时,跳转到其父类G:wamp64wwwtestlaravel55vendorlaravelframeworksrcIlluminateFoundationHttpKernel.php看一看

图片 10图片 11

 1     public function build($concrete) 2     { 3         // If the concrete type is actually a Closure, we will just execute it and 4         // hand back the results of the functions, which allows functions to be 5         // used as resolvers for more fine-tuned resolution of these objects. 6         //若传入的是一个闭包则直接通过闭包实例化类,这种闭包一般由provider类在laravel应用初始化阶段通过bind方法进行绑定。 7         if ($concrete instanceof Closure) { 8             return $concrete($this, $this->getLastParameterOverride; 9         }10         //制造一个类反射11         $reflector = new ReflectionClass($concrete);12 13         // If the type is not instantiable, the developer is attempting to resolve14         // an abstract type such as an Interface of Abstract Class and there is15         // no binding registered for the abstractions so we need to bail out.16         if (! $reflector->isInstantiable {17             return $this->notInstantiable($concrete);18         }19         //将当前所实例化的类存入栈20         $this->buildStack[] = $concrete;21         //获得该类构造方法22         $constructor = $reflector->getConstructor();23 24         // If there are no constructors, that means there are no dependencies then25         // we can just resolve the instances of the objects right away, without26         // resolving any other types or dependencies out of these containers.27         //构造函数没有参数则直接实例化28         if (is_null($constructor)) {29             array_pop($this->buildStack);30 31             return new $concrete;32         }33         //若有构造函数则获取其参数34         $dependencies = $constructor->getParameters();35 36         // Once we have all the constructor's parameters we can create each of the37         // dependency instances and then use the reflection instances to make a38         // new instance of this class, injecting the created dependencies in.39         //运行构造函数,并解决依赖40         $instances = $this->resolveDependencies(41             $dependencies42         );43         //解决完依赖,出栈44         array_pop($this->buildStack);45 46         return $reflector->newInstanceArgs($instances);47     }

View Code

    use IlluminateRoutingRouter;    public function __construct(Application $app, Router $router)    {        $this->app = $app;        //路由类实例,由容器自动加载依赖而来        $this->router = $router;        //系统中间件        $router->middlewarePriority = $this->middlewarePriority;        //中间件分组        foreach ($this->middlewareGroups as $key => $middleware) {            $router->middlewareGroup($key, $middleware);        }        //注册中间件别名        foreach ($this->routeMiddleware as $key => $middleware) {            $router->aliasMiddleware($key, $middleware);        }    }

可以看到,laravel在实例化出kernel对象的同时,通过kernel的构造函数加载了系统中间件,依赖了application与route两个对象。并将自身的$middlewareGroups、routeMiddleware数组解析进了route对象里,在路由进行调用的时候就会把路由方法上绑定的中间件名在这里解析出实例来调用了,其中routeMiddleware为别名所用。,随后在index.php文件中马上就利用kernel的handle方法,传入了一个request对象,来处理这次的网页url请求。

    public function handle($request)    {        try {            //启用http方法覆盖参数            $request->enableHttpMethodParameterOverride();            //通过路由发送请求            $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);        }        $this->app['events']->dispatch(            new EventsRequestHandled($request, $response)        );        return $response;    }    protected function sendRequestThroughRouter($request)    {        //将请求存入容器        $this->app->instance('request', $request);        //清除facade门面        Facade::clearResolvedInstance('request');        //初始化引导        $this->bootstrap();        //让请求进入中间件        return (new Pipeline($this->app))                    ->send($request)                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)                    ->then($this->dispatchToRouter;    }    //引导数组    protected $bootstrappers = [        IlluminateFoundationBootstrapLoadEnvironmentVariables::class,        IlluminateFoundationBootstrapLoadConfiguration::class,        IlluminateFoundationBootstrapHandleExceptions::class,        IlluminateFoundationBootstrapRegisterFacades::class,        IlluminateFoundationBootstrapRegisterProviders::class,        IlluminateFoundationBootstrapBootProviders::class,    ];

上面bootstrap中会分别执行每一个bootstrapper的bootstrap方法来引导启动应用程序的各个部分

  1. DetectEnvironment 检查环境
  2. LoadConfiguration 加载应用配置
  3. ConfigureLogging 配置日至
  4. HandleException 注册异常处理的Handler
  5. RegisterFacades 注册Facades
  6. RegisterProviders 注册Providers
  7. BootProviders 启动Providers

启动应用程序的最后两步就是注册服务提供者和启动提供者,先来看注册服务提供器,服务提供器的注册由类IlluminateFoundationBootstrapRegisterProviders::class负责,该类用于加载所有服务提供器的 register 函数,并保存延迟加载的服务的信息,以便实现延迟加载。

所有服务提供器都在配置文件 app.php 文件的 providers 数组中。类 ProviderRepository 负责所有的服务加载功能:
loadManifest()会加载服务提供器缓存文件services.php,如果框架是第一次启动时没有这个文件的,或者是缓存文件中的providers数组项与config/app.php里的providers数组项不一致都会编译生成services.php。

application的registerConfiguredProviders()方法对服务提供者进行了注册,通过框架的文件系统收集了配置文件中的各种provicers并转化成数组,在G:wamp64wwwtestlaravel55vendorlaravelframeworksrcIlluminateFoundationProviderRepository.php类的load方法中进行加载,但最终还是会在application类中的register()方法中通过字符串的方式new出对象,在执行provider中自带的register()方法

太多支线的细节不用深挖,重点在于让请求进入中间件这里,它用了一个管道模式,或者说装饰模式,通过函数调用栈的形式,对请求进行过滤(这个等到后面中间件的时候单独说)最终通过了所有中间件的请求会进入到IlluminateRoutingrouter类的dispatchToRoute方法

router类里的runRouteWithinStack方法通过管道的方式,运行了系统自带中间件。这些中间件里有一个laravelframeworksrcIlluminateRoutingMiddlewareSubstituteBindings.php中间件,用于处理路由上的绑定。其中调用了IlluminateRoutingrouter类中的substituteImplicitBindings方法对路由上的模型进行了绑定。在IlluminateRoutingRouteSignatureParameters.php中通过对路由route中的控制器字符串,或闭包函数,进行反射,获取到他们的参数名,与类型提示,并过滤出IlluminateContractsRoutingUrlRoutable类的子类,过滤后得到的便是模型的类型提示了。之后又在IlluminateRoutingImplicitRouteBinding.php类中通过容器的make方法将反射得到的类名实例化为对象,使用model中的resolveRouteBinding方法通过路由参数获取数据对象,而后在route类中赋值给route属性。IlluminateRoutingRoute类的runCallable方法里对路由进行了调用。控制器和方法是从路由文件中获取到的(通过symfony的request对象获取到pathinfo),依然是通过字符串解析为类名和方法名,随后通过ioc容器实例化类为对象,再调用控制器基类的某个方法执行传入的方法名

IlluminateRoutingControllerDispatcher类的dispatch方法为真正执行的部分,其中resolveClassMethodDependencies方法会对控制器的参数实行依赖注入。传入从路由中获取的参数,与从控制器反射中获取的方法参数。如果该方法所需的参数不是一个模型绑定,则会通过容器中的make方法获取对象实例。

 1     public function dispatch(Route $route, $controller, $method) 2     { 3         //解析类方法的依赖 4         $parameters = $this->resolveClassMethodDependencies( 5             $route->parametersWithoutNulls(), $controller, $method 6         ); 7         //若控制器中存在回调 8         if (method_exists($controller, 'callAction')) { 9             return $controller->callAction($method, $parameters);10         }11         //调用控制器方法12         return $controller->{$method}(...array_values($parameters));13     }

最后,控制器返回执行后的结果,被response类包装成响应对象返回至index.php,通过send方法发送至浏览器。

public function register()
{
    //
    $authProvider = new IlluminateAuthAuthServiceProvider($this->app);
    $authProvider->register();
    Auth::extend('loauth',function($app){});
}

同时在config/cache.php中配置Memcached配置:

既然auth还未注册,那么我们手动调用它的register方法帮它注册。

'MemcachedExtend' => [    'driver' => 'MemcachedExtend',    'servers' => [        [            'host' => env('MEMCACHED_EXTEND_HOST', '127.0.0.1'),            'port' => env('MEMCACHED_EXTEND_PORT', 11211),            'weight' => 100,        ],    ],    'options' => [        Memcached::OPT_BINARY_PROTOCOL => true,        Memcached::OPT_COMPRESSION => false    ]]

如果需要把Session也储存到我们扩展的缓存中还需要调用Session::extend来扩展我们的Session储存:

Session::extend('MemcachedExtend',function ($app){    $memcached = $this->createMemcached($app);    return new MemcachedSessionHandler($memcached);});

之后再.env中就可以配置我们扩展后的缓存了。完整代码如下:

图片 12图片 13

<?phpnamespace SmartWikiProviders;use IlluminateCacheRepository;use IlluminateCacheMemcachedStore;use IlluminateSupportServiceProvider;use Cache;use Session;use SymfonyComponentHttpFoundationSessionStorageHandlerMemcachedSessionHandler;use RuntimeException;class MemcachedExtendServiceProvider extends ServiceProvider{    /**     * Bootstrap the application services.     *     * @return void     */    public function boot()    {        Cache::extend('MemcachedExtend', function ($app) {            $memcached = $this->createMemcached($app);            // 从配置文件中读取缓存前缀            $prefix = $app['config']['cache.prefix'];            // 创建 MemcachedStore 对象            $store = new MemcachedStore($memcached, $prefix);            // 创建 Repository 对象,并返回            return new Repository($store);        });        Session::extend('MemcachedExtend',function ($app){            $memcached = $this->createMemcached($app);            return new MemcachedSessionHandler($memcached);        });    }    /**     * Register the application services.     *     * @return void     */    public function register()    {        //    }    /**     * 创建Memcached对象     * @param $app     * @return mixed     */    protected function createMemcached($app)    {        // 从配置文件中读取 Memcached 服务器配置        $servers = $app['config']['cache.stores.MemcachedExtend.servers'];        // 利用 IlluminateCacheMemcachedConnector 类来创建新的 Memcached 对象        $memcached = new Memcached;        foreach ($servers as $server) {            $memcached->addServer(                $server['host'], $server['port'], $server['weight']            );        }        // 如果服务器上的 PHP Memcached 扩展支持 SASL 认证        if (ini_get('memcached.use_sasl') && isset($app['config']['cache.storess.MemcachedExtend.memcached_user']) && isset($app['config']['cache.storess.MemcachedExtend.memcached_pass'])) {            // 从配置文件中读取 sasl 认证用户名            $user = $app['config']['cache.storess.MemcachedExtend.memcached_user'];            // 从配置文件中读取 sasl 认证密码            $pass = $app['config']['cache.storess.MemcachedExtend.memcached_pass'];            // 指定用于 sasl 认证的账号密码            $memcached->setSaslAuthData($user, $pass);        }        //扩展        if (isset($app['config']['cache.stores.MemcachedExtend.options'])) {            foreach ($app['config']['cache.stores.MemcachedExtend.options'] as $key => $option) {                $memcached->setOption($key, $option);            }        }        $memcachedStatus = $memcached->getVersion();        if (! is_array($memcachedStatus)) {            throw new RuntimeException('No Memcached servers added.');        }        if (in_array('255.255.255', $memcachedStatus) && count(array_unique($memcachedStatus)) === 1) {            throw new RuntimeException('Could not establish Memcached connection.');        }        return $memcached;    }}

SmartWikiCode

SmartWiki官网:

SmartWiki源码:

上一篇:通过使用PHP的特殊功能和函数 下一篇:没有了

更多阅读

动手写一个Auth扩展,laravel的项目入口文

前端 2020-04-02
昨天按照手册教程,动手写一个Auth扩展,按照包独立性的原则,我不希望将Auth::extend()这种方...
查看全文

通过使用PHP的特殊功能和函数

前端 2020-04-02
1、多阅读手册和源代码 没什么比阅读手册更值得强调的事了–仅仅通过阅读手册你就可以学习...
查看全文

项目开发规范》,就会使其中一个缓存失

前端 2020-04-01
1. 定义关联模型 在Laravel里面,我们可以通过定义以下Model来完成关联查询。 class MyPost extends...
查看全文

友情链接: 网站地图

Copyright © 2015-2019 http://www.koi-bumi.com. 韦德体育有限公司 版权所有