如何扩展Laravel

注册服务向容器中注册服务// 绑定服务$container->bind(’log’, function(){ return new Log();});// 绑定单例服务$container->singleton(’log’, function(){ return new Log();});扩展绑定扩展已有服务$container->extend(’log’, function(Log $log){ return new RedisLog($log);});ManagerManager实际上是一个工厂,它为服务提供了驱动管理功能。Laravel中的很多组件都使用了Manager,如:Auth、Cache、Log、Notification、Queue、Redis等等,每个组件都有一个xxxManager的管理器。我们可以通过这个管理器扩展服务。比如,如果我们想让Cache服务支持RedisCache驱动,那么我们可以给Cache服务扩展一个redis驱动:Cache::extend(‘redis’, function(){ return new RedisCache();});这时候,Cache服务就支持redis这个驱动了。现在,找到config/cache.php,把default选项的值改成redis。这时候我们再用Cache服务时,就会使用RedisCache驱动来使用缓存。Macro和Mixin有些情况下,我们需要给一个类动态增加几个方法,Macro或者Mixin很好的解决了这个问题。在Laravel底层,有一个名为Macroable的Trait,凡是引入了Macroable的类,都支持Macro和Mixin的方式扩展,比如Request、Response、SessionGuard、View、Translator等等。Macroable提供了两个方法,macro和mixin,macro方法可以给类增加一个方法,mixin是把一个类中的方法混合到Macroable类中。举个例子,比如我们要给Request类增加两个方法。使用macro方法时:Request::macro(‘getContentType’, function(){ // 函数内的$this会指向Request对象 return $this->headers->get(‘content-type’);});Request::macro(‘hasField’, function(){ return !is_null($this->get($name));});$contentType = Request::getContentstType();$hasPassword = Request::hasField(‘password’);使用mixin方法时:class MixinRequest{ public function getContentType(){ // 方法内必须返回一个函数 return function(){ return $this->headers->get(‘content-type’); }; } public function hasField(){ return function($name){ return !is_null($this->get($name)); }; }}Request::mixin(new MixinRequest());$contentType = Request::getContentType();$hasPassword = Request::hasField(‘password’);

April 14, 2019 · 1 min · jiezi

更过程式的let——vertical-let

作为一名自诩的non-trivial的Common Lisp程序员,在编码的时候经常会遇到令人不愉快的地方,其中一个便是LET。一段典型的LET的示例代码如下(let ((a 1)) a)大多数时候,LET不会只有一个绑定。并且,也不会只是绑定一个常量这么简单,而应当是下面这样的(let ((a (foo x y)) (b (bar z))) (function1 a b) (function2 a b))有时候我会想看看某一个绑定的值——最好是在它计算完毕后立即查看。如果要查看foo函数的返回值,可以这样写(let ((a (foo x y)) (b (bar z))) (print a) (function1 a b) (function2 a b))如果调用foo和bar都顺利的话上面的代码也就够了。比较棘手的情况是,如果a的值不符合预期,会导致b的计算过程出状况(尽管在上面的代码中看似不会)。这种情况多出现在LET的使用中,如下面所示(let ((a (foo x y)) (b (bar a))) (function1 a b) (function2 a b))如果错误的a会导致bar的调用出错,那么在调用function1之前才调用print打印a已经为时过晚了——毕竟调用bar的时候就抛出condition往调用链的上游走了。一种方法是写成下面这样子(let* ((a (let ((tmp (foo x y))) (print tmp) tmp)) (b (bar a))) (function1 a b) (function2 a b))这也太丑了!要不然写成下面这样子?(let ((a (foo x y))) (print a) (let ((b (bar a))) (function1 a b) (function2 a b)))本来一个LET就可以做到的事情,这下子用了两个,还导致缩进更深了一级。如果有十个变量需要打印,就会增加十个LET和十层缩进。如果心血来潮想查看一个变量的值,还要大幅调整代码。问题大概就出在LET和LET*的语法上。以LET为例,它由截然分开的bindings和forms组成,两者不能互相穿插。因此,如果想在bindings中求值一段表达式,只能将bindings切开,写成两个LET的形式。好像写一个新的宏可以解决这个问题?是的,vertical-let就是。vertical-let是一个我自己写的宏,源代码在此。其用法如下(vertical-let :with a = 1 a)它借鉴了LOOP中绑定变量的方式(即:with和=),绑定变量和用于求值的代码还可以交织在一起,如下(vertical-let :with a = 1 (print a) :with b = 2 (+ a b))vertical-let最终会展开为LET,比如上面的代码,会展开为如下的代码(LET ((A 1)) (PRINT A) (LET ((B 2)) (+ A B)))vertical-let的算法很简单。它遍历表达式列表,当遇到:with时就把接下来的三个元素分别视为变量名、等号,以及待求值的表达式,将三者打包进一个列表中,再压栈;当遇到其它值时,就视为待求值的表达式(将会组成LET的forms部分),也放进列表中再压栈(具体方法参见源代码)。将所有值都遍历并压栈后,接下来要遍历这个栈中的元素。先准备两个空的栈——一个存放bindings,一个存放forms。接着,对于每一个从栈中弹出的元素,分为如下两种情况:如果表示binding,则直接压入存放bindings的栈,否则;如果是待求值的表达式,并且上一个出栈的元素是binding,则说明已经有一段完整的LET的内容被集齐。因此,将目前在两个栈中的内容全部弹出,组合为一个LET表达式再压入存放forms的栈中。然后将方才弹出的表达式也压入forms。重复上述过程直至所有元素都被处理,最后将还在两个栈中的内容也组合为一个LET表达式便结束了。全文完 ...

March 14, 2019 · 1 min · jiezi