首页
网站导航
关于
Search
1
解决Typecho Joe主题访问fastly.jsdelivr.net速度慢的方案 | 快速优化技巧
2,191 阅读
2
解决WSL2内存不释放问题的最佳指南
1,401 阅读
3
如何在 Typecho Joe 主题的文章中增加目录
1,171 阅读
4
GO语言环境的搭建教程 - 完全指南
1,005 阅读
5
如何解决Win11电脑桌面上方显示横线问题 | 窗口11教程
1,005 阅读
默认分类
编程语言
GO语言
PHP
Node
javascript
html
rust
java
Css
Python
资源分享
chrome插件
阅读思考
运维架构
redis
Nginx
linux
memcached
mongodb
mysql
windows
docker
k8s
Mq
apache
CI
Git
swoole
elk
系统设计
thinkPhp
beego
登录
Search
标签搜索
重要
go基础
git 命令
go包
phpstorm
sublime
thinkphp6
mysql问题
软件分享
redis命令
php基础
thinkphp3.2
php第三扩展包
小蚯蚓博客
累计撰写
333
篇文章
累计收到
48
条评论
首页
栏目
默认分类
编程语言
GO语言
PHP
Node
javascript
html
rust
java
Css
Python
资源分享
chrome插件
阅读思考
运维架构
redis
Nginx
linux
memcached
mongodb
mysql
windows
docker
k8s
Mq
apache
CI
Git
swoole
elk
系统设计
thinkPhp
beego
页面
网站导航
关于
搜索到
9
篇与
的结果
2022-10-08
深入理解并掌握ThinkPHP6中间件
概述 thinkphp6中,中间件是针对http组件来说的,在http组件入库方法runWithRequest里 // 加载全局中间件 $this->loadMiddleware(); // 执行全局中间件 return $this->app->middleware->pipeline()// pipeline方法里将中间件转为闭包 // 初始化 ->send($request) // 执行 ->then(function ($request) { // 最后执行路由分发 return $this->dispatchToRoute($request); }); // 加载基础目录下的中间件 protected function loadMiddleware(): void { if (is_file($this->app->getBasePath() . 'middleware.php')) { $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php'); } } 核心实现逻辑 通过中间件组件来实现,队列queue里有全局类型的中间件队列、route类型的队列、controller类型的队列 class Middleware { // 队列 protected $queue = []; /** *******添加中间件******** */ public function import(array $middlewares = [], string $type = 'global'): void { foreach ($middlewares as $middleware) { $this->add($middleware, $type); } } public function add($middleware, string $type = 'global'): void { $middleware = $this->buildMiddleware($middleware, $type); if (!empty($middleware)) // 队列里添加中间件 $this->queue[$type][] = $middleware; // 队列去重 $this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR); } } // 中间件可以是数组、闭包、字符串 protected function buildMiddleware($middleware, string $type): array { if (is_array($middleware)) { [$middleware, $params] = $middleware; } if ($middleware instanceof Closure) { // 闭包+参数 return [$middleware, $params ?? []]; } if (!is_string($middleware)) { throw new InvalidArgumentException('The middleware is invalid'); } //中间件别名检查 $alias = $this->app->config->get('middleware.alias', []); if (isset($alias[$middleware])) { $middleware = $alias[$middleware]; } if (is_array($middleware)) { $this->import($middleware, $type); return []; } // 类名+handle方法+参数,如果没有传入方法名称,默认是handle方法 return [[$middleware, 'handle'], $params ?? []]; } public function route($middleware): void { // route类型的队列 $this->add($middleware, 'route'); } public function controller($middleware): void { // controller类型的队列 $this->add($middleware, 'controller'); } /** *******管道执行中间件******** */ public function pipeline(string $type = 'global') { return (new Pipeline()) // through是把中间转为闭包,方便后续执行 ->through(array_map(function ($middleware) { // 返回中间件闭包数组,也就把中间件转为闭包 return function ($request, $next) use ($middleware) { // 调用中间件 [$call, $params] = $middleware; if (is_array($call) && is_string($call[0])) { $call = [$this->app->make($call[0]), $call[1]]; } // 必须有request、next参数、外加自己传入的参数 // next参数是下一个要执行的中间件闭包,所以在回调函数里必须被调用,不然后续无法执行 $response = call_user_func($call, $request, $next, ...$params); // 返回http响应类,因为最后一个执行的是dispatchToRoute if (!$response instanceof Response) { throw new LogicException('The middleware must return Response instance'); } return $response; }; }, $this->sortMiddleware($this->queue[$type] ?? [])))// 排序中间件,并且传给array_map,用于把中间件转为闭包 // 设置如果有异常的时候,处理异常的方法名称 ->whenException([$this, 'handleException']); } public function handleException($passable, Throwable $e) { // 系统异常类 $handler = $this->app->make(Handle::class); $handler->report($e); return $handler->render($passable, $e); } } 管道类 class Pipeline { // 闭包形式的中间件存在这里 protected $pipes = []; // 这种闭包管道 public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; } // 执行管道 public function then(Closure $destination) { $pipeline = array_reduce( // 倒叙 array_reverse($this->pipes), $this->carry(), function ($passable) use ($destination) { try { return $destination($passable); } catch (Throwable | Exception $e) { return $this->handleException($passable, $e); } }); return $pipeline($this->passable); } protected function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { try { return $pipe($passable, $stack); } catch (Throwable | Exception $e) { return $this->handleException($passable, $e); } }; }; } } 管道的核心逻辑 简化下上述的管道逻辑,如下所示执行闭包: $middleware = [ function ($request1, $next) { echo 'request1闭包'; // $next为request2闭包 return $next($request1); }, function ($request2, $next) { echo 'request2闭包'; // $next为request3闭包 return $next($request2); }, function ($request3, $next) { echo 'request3闭包'; // $next为last闭包 return $next($request3); } ]; // 需要倒叙,因为闭包执行是按相反的顺序进行的 $middleware = array_reverse($middleware); $pipe = array_reduce($middleware, function ($stack, $pipe) { // $stack永远指向上一个闭包 return function ($passable) use ($stack, $pipe) { // 第一次执行:$stack=last闭包 // 第二次执行:$stack=request1闭包 // 第三次执行:$stack=request2闭包,此时$pipe为request3闭包 return $pipe($passable, $stack); }; }, function ($requestLast) { echo 'last闭包'; }); $pipe('request');
2022年10月08日
203 阅读
0 评论
0 点赞
2022-10-08
Thinkphp6的Facade门面详解 | Guide and Tutorial
概述 门面主要是为了方便调用类方法,可以像调用静态方法的方式调用非静态方法。例如 class Test { public function hello($name) { return 'hello,' . $name; } } // 这个类名不一定要和Test类一致,但通常为了便于管理,建议保持名称统一 class Test extends Facade { protected static function getFacadeClass() { return 'app\common\Test'; } } // 无需进行实例化 直接以静态方法方式调用hello \app\facade\Test::hello('thinkphp'); 核心实现逻辑 主要是利用魔术方法、容器技术实现 class Facade { // 用于配置是否单例模式 protected static $alwaysNewInstance; // 魔术方法 public static function __callStatic($method, $params) { // 调用对象的方法 return call_user_func_array([static::createFacade(), $method], $params); } // 可以为其它类增加门面功能 protected static function getFacadeClass() {} // protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false) { // 默认只需要继承这个类的话,本类就有门面功能 $class = $class ?: static::class; // 可以为其它类设置门面功能 $facadeClass = static::getFacadeClass(); if ($facadeClass) { $class = $facadeClass; } // 是否单例模式 if (static::$alwaysNewInstance) { $newInstance = true; } // 容器单例 return Container::getInstance()->make($class, $args, $newInstance); } }
2022年10月08日
228 阅读
0 评论
0 点赞
2022-09-30
深度解析Thinkphp6的事件机制 | PHP开发教程
概述 tp6的事件,是通过观察者模式设计的,事件的基本原理是,首先在系统初始化的时候注册一些监听事件,然后观察事件的触发,只要事件触发的时候就就能执行这些观察者。 事件机制再不修改框架代码的情况下,提升了框架的扩展性。 源码解析 别名功能 事件的别名,触发的时候可以直接触发这个别名,系统自定义的基本事件别名已经写死在事件类的属性里了。 class Event { // 事件的别名,触发的时候可以直接触发这个别名 protected $bind = [ 'AppInit' => event\AppInit::class, ]; // 批量注册别名 public function bind(array $events) { $this->bind = array_merge($this->bind, $events); return $this; } } 事件配置,因为事件相关的配置,最好放在一个配置文件里,所以通过key来分开 return [ 'bind' => [ 'UserLogin' => 'app\event\UserLogin', ], ]; 然后系统初始化加载配置的时候,会批量加载别名,配置文件如下 $this->event->bind($event['bind']); 绑定别名后,就可以通过别名触发事件 $this->event->trigger('AppInit'); 当然这个别名不设置也是可以的,只要触发的时候,直接触发事件类名称就好了,不要触发别名,下面触发事件的方式和上面别名触发的方式效果是一样的 $this->event->trigger(AppInit::class); 需要注意的是,这个别名最好是大写开头,因为事件自动订阅模式,截取on开头后,第一个字符是大写,详情见下面的事件订阅 事件的监听 事件的监听就是观察者,是事件的核心 class Event { // 监听类的类名 protected $listener = []; // 批量注册监听 public function listenEvents(array $events) { foreach ($events as $event => $listeners) { if (isset($this->bind[$event])) { $event = $this->bind[$event]; } $this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners); } return $this; } // 单个监听 public function listen(string $event, $listener) { if (isset($this->bind[$event])) { $event = $this->bind[$event]; } $this->listener[$event][] = $listener; return $this; } } 由上我门可以知道,监听基本的参数是事件的名称、监听类(到时候会执行监听类里的handle方法)或者闭包,例如批量监听的时候,是通过系统配置的方式 return [ 'listen' => [ 'UserLogin' => ['app\listener\UserLogin'], ], ]; 这个配置文件,会在系统初始化的是被批量注册 $this->event->listenEvents($event['listen']); 当然,我们还可以注册单个 // 类名称,触发的时候会执行UserLogin里的handle方法 Event::listen('UserLogin', UserLogin::class); // 闭包 Event::listen('UserLogin', function($user) { // }); // 对象的具体方法 $this->listen('UserLogin', [$userLoginObject, 'onUserLogin']); 订阅功能 订阅的概念跟关注差不多,类似一次性可以关注多个明星,因此我们也可以一次性订阅多个事件,例如用户相关的事件,可以放在用户订阅类里,这样好管理事件的监听 // 事件类 class Event { // 批量订阅,可以传入字符串或者对象 public function subscribe($subscriber) { // 如果不是数组的,转成数组,支持订阅单个 $subscribers = (array) $subscriber; foreach ($subscribers as $subscriber) { if (is_string($subscriber)) { $subscriber = $this->app->make($subscriber); } // 如果订阅类里有subscribe方法,代表需要手动订阅 if (method_exists($subscriber, 'subscribe')) { // 手动订阅,在该方法里,手工动态监听事件 $subscriber->subscribe($this); } else { // 智能订阅,根据规则,自动注册事件监听 $this->observe($subscriber); } } return $this; } // 根据方法on+事件别名名称的方法,自动注册对应事件监听 public function observe($observer, string $prefix = '') { if (is_string($observer)) { $observer = $this->app->make($observer); } $reflect = new ReflectionClass($observer); $methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC); if (empty($prefix) && $reflect->hasProperty('eventPrefix')) { $reflectProperty = $reflect->getProperty('eventPrefix'); $reflectProperty->setAccessible(true); $prefix = $reflectProperty->getValue($observer); } foreach ($methods as $method) { $name = $method->getName(); if (0 === strpos($name, 'on')) { $this->listen($prefix . substr($name, 2), [$observer, $name]); } } return $this; } } 同样,我们可以同配置文件,批量订阅 return [ 'subscribe' => [ 'app\subscribe\User', ], ]; 系统初始化的时候,会加载配置文件 if (isset($event['subscribe'])) { $this->event->subscribe($event['subscribe']); } 当然,我们也可以动态订阅 Event::subscribe('app\subscribe\User'); 订阅类有subscribe方法,手动监听到指定的方法 class User { public function onUserLogin($user) { // UserLogin事件响应处理 } public function subscribe(Event $event) { $event->listen('UserLogin', [$this,'onUserLogin']); } } 订阅类没有subscribe方法,自动监听 class User { // UserLogin必须在事件别名 public function onUserLogin($user) { // UserLogin事件响应处理 } } 事件的触发 我们一般在可能需要扩展的地方触发响应的事件,触发逻辑如下所示 class Event { public function trigger($event, $params = null) { // 由此可知,触发可以传入事件类的对象 if (is_object($event)) { // 对象的话,把对象作为参数传入观察者 $params = $event; $event = get_class($event); } // 由此可知,触发可以传入事件类的别名 if (isset($this->bind[$event])) { $event = $this->bind[$event]; } $result = []; $listeners = $this->listener[$event] ?? []; $listeners = array_unique($listeners, SORT_REGULAR); // 执行事件的所有监听 foreach ($listeners as $key => $listener) { // 触发可以传入参数 $result[$key] = $this->dispatch($listener, $params); } // 可以返回每个监听者的返回值 return $result; } // 执行监听者 protected function dispatch($event, $params = null) { if (!is_string($event)) { // 闭包类型的监听者 $call = $event; } elseif (strpos($event, '::')) { // 静态方法类型的监听者 $call = $event; } else { // 类名称类型的监听者 $obj = $this->app->make($event); $call = [$obj, 'handle']; } return $this->app->invoke($call, [$params]); } } 可以通过别名触发 $this->event->trigger("AppInit"); 可以通过,监听类名称触发 $this->event->trigger(AppInit::class); 可以通过对象触发 $this->event->trigger(new UserLogin([ 'login_time'=>time()// 事件对象可以设置一些参数,如果事件对象有这个属性的话 ])); 可以通过静态方法触发,并且传入参数 $this->event->trigger("events\UserLogin::OnLogin",[$userId]);
2022年09月30日
362 阅读
0 评论
0 点赞
2022-09-19
深入理解ThinkPHP6中的文件缓存
场景 我们经常会将excel导入模板文件保存在一个目录,然后提供一个下载按钮,去下载这个模板文件,但是默认情况下thinkphp会在文件响应的时候,会让浏览器缓存一段时间,导致模板文件修改没有立即得到生效 // 下载模板 public function downloadExcelTpl($fileName) { $file = ErpFacade::getContainerAssetsPath() . DIRECTORY_SEPARATOR . $fileName; return (new File($file))->header([ 'Access-Control-Expose-Headers' => 'filename', 'filename' => $fileName, ]); } File类的默认缓存时间如下所示: 解决方法 我们可以手动设置文件缓存的时间为0,修改为如下代码即可 // 下载模板 public function downloadExcelTpl($fileName) { $file = ErpFacade::getContainerAssetsPath() . DIRECTORY_SEPARATOR . $fileName; return (new File($file))->header([ 'Access-Control-Expose-Headers' => 'filename', 'filename' => $fileName, ])->expire(0);// 设置缓存时间为0 }
2022年09月19日
457 阅读
0 评论
0 点赞
2022-09-16
深入理解ThinkPHP6的APP类核心逻辑 | PHP框架教程
APP类 一般来说,在index入口文件在加载autoLoad文件后,紧接着就是实例化一个App类,需要注意的是,这个App是可以扩展的,我们可以自定义一个类来继承它 class Erp extends App { protected $bind = [ 'app' => Erp::class,// 通过继承,替换掉系统的默认的类 'cache' => Cache::class, // 等等...,有很多默认设置好的绑定,这里列出几个举例子 ] public function __construct(string $rootPath = '') { parent::__construct($rootPath); if (is_file($this->getBootstrapPath() . 'provider.php')) { $this->bind(include $this->getBootstrapPath() . 'provider.php'); } } } 然后创建一个boostrap目录,用于放一些启动文件,常见的有一下几个,不过可以自己增加其它的 boostrap/app.php,用于实例化自定义类,然后直接在入口文件index.php里引入这个文件$app = require_once __DIR__.'/../bootstrap/app.php'; $app = new \ship\foundation\Erp( realpath(__DIR__.'/../') ); return $app; 1. boostrap/provider.php,用于设置容器的绑定,修改request类为自定义的类,这个就是容器的强大扩展之处,可以不修改框架代码,实现自定义扩展 ```php return [ 'think\exception\Handle' => \ship\foundation\exceptions\HandleException::class, 'think\Request' => \ship\foundation\AppRequest::class, 'think\Validate' => \ship\foundation\validate\Validate::class, 'console' => \ship\parents\console\AppConsole::class, 'export' => \ship\foundation\Export::class, 'event' => \ship\foundation\events\Event::class, ]; boostrap/service.php,用于设置启动服务 return [ \ship\services\ShipService::class, ]; 然后实例化app类的时候,会执行app类里的构造方法 ```php class App extends Container { protected $bind = [ 'app' => App::class,// 别名=>类明的形式,调用时候方便,$this->别名 'cache' => Cache::class, // 等等...,有很多默认设置好的绑定,这里列出几个举例子 ] public function __construct(string $rootPath = '') { $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath(); $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR; $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; if (is_file($this->appPath . 'provider.php')) { // 绑定自定义的类,或者覆盖系统默认的类,实现扩展 $this->bind(include $this->appPath . 'provider.php'); } static::setInstance($this); $this->instance('app', $this); $this->instance('think\Container', $this); } } 由此构造方法可知,app类需要传入一个整个应用的根目录,然后所有其它的目录都是相对于这个根目录的,接着是是加载绑定文件,批量绑定。 这里的bind方法属于容器的方法,用于容器实例的代码定义的,可以绑定到类名、闭包、对象等,通过这个定义我们可以得到容器的实例,就好比docker容器里的容器shell脚本,通过这个脚本pull的时候,可以得到可以允许的容器实例。 接着是设置app容器实例,这个实例,可以最终被调用使用。 至此app的初始化应该完成了。 Container容器管理类 说到容器,我们首先想到的是docker里的容器,docker里有dockerFile文件,通过build这个dockerFile文件,可以得到镜像实例,然后这个镜像实例就可以运行了。这里的容器类也是实现类似的功能的,很多概念是互通的。 组件实例化 主要思路是,首先绑定一些组件别名到类、或者闭包、或者具体的对象实例;然后调用的时候,通过魔术方法make一个实例出来,如果实例已经存在,就直接返回实例。 make实例的时候: 如果发现组件是闭包,就直接调用,不过这个感觉没啥用,还不如直接定义一个函数调用下。 否则如果类名称,就去实例化这个类,不过好像传不了参数进去 class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable { // 类似docker里的docker file的功能 protected $bind = []; // 类型docker里的镜像的功能,组件的实例,组件可以是单例模式 protected $instances = []; // 存下本身的实例,也就是容器本身也可以是单例模式 protected static $instance; // app类在构造函数的时候static::setInstance($this); // 之后就可以这样使用了:Container::getInstance()->make($class, $args, $newInstance) public static function setInstance($instance): void { static::$instance = $instance; } // 单例模式 public static function getInstance() { if (is_null(static::$instance)) { static::$instance = new static; } if (static::$instance instanceof Closure) { // 闭包一般代表可以自定义 return (static::$instance)(); } return static::$instance; } // 返回别名对应的东西,比如类名、对象、或者闭包 public function getAlias(string $abstract): string { if (isset($this->bind[$abstract])) { $bind = $this->bind[$abstract]; if (is_string($bind)) { // 这里表示不同的别名可以对应同一个类,好像这也没啥用 return $this->getAlias($bind); } } return $abstract; } // 绑定别名到类名、闭包、对象 public function bind($abstract, $concrete = null) { if (is_array($abstract)) { foreach ($abstract as $key => $val) { $this->bind($key, $val); } } elseif ($concrete instanceof Closure) { $this->bind[$abstract] = $concrete; } elseif (is_object($concrete)) { $this->instance($abstract, $concrete); } else { $abstract = $this->getAlias($abstract); $this->bind[$abstract] = $concrete; } return $this; } // public function __get($name) { return $this->make($name); } // 类似docker里的build命令,用于将bind里定义的东西构建成实例 public function make(string $abstract, array $vars = [], bool $newInstance = false) { $abstract = $this->getAlias($abstract); if (isset($this->instances[$abstract]) && !$newInstance) { // 单例模式,对于依赖注入的话,就方便的获取到已经实例化的组件 return $this->instances[$abstract]; } if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) { // 闭包直接调用 $object = $this->invokeFunction($this->bind[$abstract], $vars); } else { // 对象或者类名 $object = $this->invokeClass($abstract, $vars); } if (!$newInstance) { $this->instances[$abstract] = $object; } return $object; } } 组件执行 主要是通过反射类,来执行组件: 如果绑定的是函数或者闭包,使用的是new ReflectionFunction($function);; // 调用闭包、或者全局函数,这个好像没啥用 // invokeFunction('fucntionName',[参数1]); // invokeFunction(function(){},[参数1]); public function invokeFunction($function, array $vars = []) { try { $reflect = new ReflectionFunction($function); } catch (ReflectionException $e) { throw new FuncNotFoundException("function not exists: {$function}()", $function, $e); } // 返回的参数顺序函数的参数顺序 $args = $this->bindParams($reflect, $vars); return $function(...$args); } 如果绑定的是类名称,使用的是new ReflectionClass($class);; // 类实例化 public function invokeClass(string $class, array $vars = []) { try { $reflect = new ReflectionClass($class); } catch (ReflectionException $e) { throw new ClassNotFoundException('class not exists: ' . $class, $class, $e); } // 如果有__make方法,就不调用构造函数 if ($reflect->hasMethod('__make')) { $method = $reflect->getMethod('__make'); if ($method->isPublic() && $method->isStatic()) { $args = $this->bindParams($method, $vars); return $method->invokeArgs(null, $args); } } $constructor = $reflect->getConstructor(); $args = $constructor ? $this->bindParams($constructor, $vars) : []; $object = $reflect->newInstanceArgs($args); $this->invokeAfter($class, $object); return $object; } 参数绑定,这个很灵活,可以通过索引数组的方式设置参数,也可以通过关联数组的方式设置,这样的话就和顺序没有关系了,不过要注意参数是不是对象强类型,还可以依赖注入 protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array { if ($reflect->getNumberOfParameters() == 0) { return []; } // 判断数组类型 数字数组时按顺序绑定参数 reset($vars); $type = key($vars) === 0 ? 1 : 0;// 判断参数数组是否是关联数组0是,1不是 $params = $reflect->getParameters(); $args = []; // 按方法参数顺序绑定 foreach ($params as $param) { $name = $param->getName(); $lowerName = Str::snake($name);// 驼峰参数会自动转下划线 $class = $param->getClass(); if ($class) { // 参数是类,如果此时$vars里的第一个是该类的实例则用这个,否则就实例化类 $args[] = $this->getObjectParam($class->getName(), $vars); } elseif (1 == $type && !empty($vars)) { // 参数是索引数组,按顺序绑定 $args[] = array_shift($vars); } elseif (0 == $type && isset($vars[$name])) { // 参数是关联数组,如果参数名称存在则绑定 // 如果变量名称一样的话,会绑定到同一个参数 $args[] = $vars[$name]; } elseif (0 == $type && isset($vars[$lowerName])) { // 参数是关联数组,如果参数下划线的形式存在则绑定 // 因为参数经常是驼峰,数组key经常是下划线 $args[] = $vars[$lowerName]; } elseif ($param->isDefaultValueAvailable()) {// 都没用的话用默认参数 // 没有的话就取默认值 $args[] = $param->getDefaultValue(); } else { throw new InvalidArgumentException('method param miss:' . $name); } } return $args; } // 由这个可知,如果参数(类名称 $object)形式的话,顺序不能随便改变 // 因为判断第一个变量不是该类的对象的话就不会使用了 // 如果想要自动依赖注入的话,这个参数可以不传,也就是$vars的数量可以比参数的数量少 // 所以,如果不希望依赖注入,参数名称前面最好不要加入类名称,不然变量是关联数组参数的是,容易出现问题 protected function getObjectParam(string $className, array &$vars) { $array = $vars; $value = array_shift($array); if ($value instanceof $className) { $result = $value; array_shift($vars); } else { // 依赖注入 $result = $this->make($className); } return $result; } 最后系统还提供了一个快捷的invoke方法,通过这个方法,可以参数绑定,调用的方法不存在时候,不会出现语法错误 public function invoke($callable, array $vars = [], bool $accessible = false) { if ($callable instanceof Closure) { return $this->invokeFunction($callable, $vars); } elseif (is_string($callable) && false === strpos($callable, '::')) { return $this->invokeFunction($callable, $vars); } else { return $this->invokeMethod($callable, $vars, $accessible); } } // 这个方法好像跟组件没啥关系,组件实例化的时候用不到,只用invoke的时候才用到 // 不过这个方法,通过参数accessible,好像可以调用私有的方法,这点还不错 public function invokeMethod($method, array $vars = [], bool $accessible = false) { if (is_array($method)) { [$class, $method] = $method; $class = is_object($class) ? $class : $this->invokeClass($class); } else { // 静态方法 [$class, $method] = explode('::', $method); } try { $reflect = new ReflectionMethod($class, $method); } catch (ReflectionException $e) { $class = is_object($class) ? get_class($class) : $class; throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e); } $args = $this->bindParams($reflect, $vars); if ($accessible) { $reflect->setAccessible($accessible); } return $reflect->invokeArgs(is_object($class) ? $class : null, $args); } 该方法经常用于参数绑定 // 调用对象的handle方法 $this->app->invoke([$obj, 'handle'], [$params]); // 静态方法调用 $this->app->invoke("UserLogin::handle", [$params]); // 闭包调用 $this->app->invoke(function($params){}, [$params]); // 函数名称调用 $this->app->invoke('handle', [$params]); 服务 这个在lavarel里也叫做服务提供者,主要是提供一系列的服务,例如短信服务,支付服务等,提供一些和系统业务无关的一些基础服务。 protected $initializers = [ Error::class,//异常类 RegisterService::class,// 注册服务 BootService::class,// 启动服务 ]; // APP类->initialize方法里初始化系统的时候,会注册服务 foreach ($this->initializers as $initializer) { $this->make($initializer)->init($this); } public function register($service, bool $force = false) { $registered = $this->getService($service); if ($registered && !$force) { return $registered; } // 注册服务,并且调用register方法,该方法主要是设置一些容器绑定 if (is_string($service)) { $service = new $service($this); } if (method_exists($service, 'register')) { $service->register(); } if (property_exists($service, 'bind')) { $this->bind($service->bind); } $this->services[] = $service; } 然后在初始化,BootService::class启动服务 public function bootService($service) { if (method_exists($service, 'boot')) { return $this->invoke([$service, 'boot']); } } boot方法里,主要是对服务组件的一些配置,为了提高性能,一般不实例化组件,等到用的时候再实例化,所以配置一般是配置到静态变量上 // 分页服务 class ValidateService extends Service { public function register() { if (!$this->app->bound(Validate::class)) { $this->app->bind(Validate::class, Validate::class); } } public function boot() { Validate::maker(function (Validate $validate) { $validate->setLang($this->app->lang); $validate->setDb($this->app->db); $validate->setRequest($this->app->request); }); } } class Validate { protected static $maker; public function __construct(){ if (!empty(static::$maker)) { foreach (static::$maker as $maker) { call_user_func($maker, $this); } } } public static function maker(Closure $resolver) { static::$maker = $resolver; } }
2022年09月16日
294 阅读
0 评论
0 点赞
2022-09-16
ThinkPHP6框架底层原理探讨
首先,在文件组织上,框架的所有文件都是放在think开头的命名空间下 然后,在入口文件上,有两个入口: 一个是console入口,在项目的根目录下,用于执行脚本和定时任务 另一个是http入口,在项目的public目录下,用于提供http服务 由于上图可知,这两个文件的工作流思路也比较简单,只有三个步骤 第一、引入autoload文件,用于管理第三方扩展包,已经提供自动加载的功能 第二、实例化一个全局的App应用,用于管理应用,加载配置文件、提供容器的支持等 第三、因为有app类的支持,可以使用对应的组件来执行相关的功能,例如http组件用于提供http服务,console组件用于命令行的支持
2022年09月16日
311 阅读
0 评论
0 点赞
2022-07-18
thinkphp3.2 模板引擎
模板语法 if标签 if,里面是php代码,必须使用gt等 <if condition="$info.status eq 2">checked</if> <if condition="($name eq 1) OR ($name gt 100) "> value1 <elseif condition="$name eq 2"/>value2 <else /> value3 </if> volist标签 默认key变量是$k <volist name="list" id="vo"> {$k}.{$vo.name} </volist> foreach 和volist一样,只是比较简单,默认key <foreach name="list" item="vo" > {$key}|{$vo} </foreach> eq标签 <eq name="name" value="value">value</eq> 显示日期 <td>{$vo.return_commit_time|date="Y-m-d H:i:s",###}</td>
2022年07月18日
205 阅读
0 评论
0 点赞
2022-07-08
ThinkPHP6中如何使用Redis队列 - 全面指南和技巧
thinkphp6中的缓存是可以使用redis的,所以可以通过获取缓存的redis驱动,然后就可以使用redis相关的方法了: $redis = Cache::store('redis')->handler(); $redis->lpush("list_key_name", "小明"); $redis->lpush('list_key_name', "小张"); echo $redis->rpop('list_key_name');// 输出小明 echo $redis->rpop('list_key_name');// 输出小张
2022年07月08日
358 阅读
0 评论
0 点赞
2022-06-30
ThinkPHP6如何获取最后的SQL:详细教程与技巧
通过以下方法,即可获取: app()->db->getLastSql();
2022年06月30日
413 阅读
0 评论
0 点赞