当前位置: 首页 > 后端技术 > PHP

从bin-swoft入手,阅读Swoft框架源码(八)--ConsoleProsser控制台处理器

时间:2023-03-30 01:06:22 PHP

processing方法中使用的router和cliApp都是bean处理器初始化时生成的bean对象。publicfunctionhandle():bool{if(!$this->application->beforeConsole()){返回false;}//获取路由bean对象/**@varRouter$router*/$router=bean('cliRouter');//注册路由对象//注册控制台路由CommandRegister::register($router);//打印注册信息CLog::info('Consolecommandrouteregistered(group%d,command%d)',$router->groupCount(),$router->count());//启动consoleapp,然后framework的工作就会交给consoleapp接管//Runconsoleapplicationif($this->application->isStartConsole()){bean('cliApp')->跑步();}//Endconsoleprocessorreturn$this->application->afterConsole();}虽然路由对象和consoleapp对象都是在beanprocessor中初始化的,但是还是要梳理一下这两个用到的bean的初始化过程本章中的对象。由于bean处理器是为bean初始化的,如果bean对象有init方法,就会调用init方法。因此,我们主要关心这两个对象初始化的两个方法:__constract和init。先看路由器对象Swoft\Console\Router\Router:因为这个类型没有上面两个方法,bean配置中也没有配置构造参数,所以可以判断这个对象是单纯的new和放入对象池。没有其他初始化动作。查看cliApp对象Swoft\Console\Application:publicfunction__construct(array$options=[]){//由于bean配置中没有这个bean的构造参数,所以这里的options是一个空数组//这个方法相当于没有做任何其他操作ObjectHelper::init($this,$options);}由于没有init方法,cliApp的初始化几乎什么都不做!很明显,我们在控制台输入的命令只能在cliApp对象的run方法中调用接下来我们看一下cliApp的run方法:publicfunctionrun():void{//很像的设计httpServer组件//用户的业务逻辑被try/catch包裹//如果发生错误,会交给Errorhandlingscheduler来处理try{//预处理,准备run方法需要的东西//准备运行$this->准备();//触发ConsoleEvent::RUN_BEFORE事件Swoft::trigger(ConsoleEvent::RUN_BEFORE,$this);//获取输入命令//通过输入对象获取并保存fetch命令if(!$inputCommand=$this->input->getCommand()){$this->filterSpecialOption();}else{//执行命令$this->doRun($inputCommand);}Swoft::trigger(ConsoleEvent::RUN_AFTER,$this,$inputCommand);}catch(Throwable$e){/**@varConsoleErrorDispatcher$errDispatcher*/$errDispatcher=BeanFactory::getSingleton(ConsoleErrorDispatcher::class);//处理请求错误$errDispatcher->run($e);}}预处理方法:protectedfunctionprepare():void{//将输入输出对象赋值给当前对象//这里需要看一下输入输出的初始化过程$this->input=Swoft::getBean('输入');$this->output=Swoft::getBean('输出');//loadbuiltincommentsvars//将输入对象上的pwd等信息与当前对象的commentsVars数组合并$this->setCommentsVars($this->commentsVars());}Swoft\Console\Input\Inputbeandefinition是@Bean("input"),所以是单例bean对象,再看构造方法:publicfunction__construct(array$args=null,bool$parsing=true){//由于没有inputBean构造参数定义,所以这里的$args必须为nullif(null===$args){//将保存在超全局数组$_SERVER中的命令行参数赋值给$args$args=(array)$_SERVER['argv'];}//将参数保存在当前对象的tokens属性中$this->tokens=$args;//参数的第一个值是执行的启动脚本$this->scriptFile=array_shift($args);//这里得到的是去掉启动脚本后剩下的参数$this->fullScript=implode('',$args);//找到commandname,其他是flags//从remainingparameterscommand中找到并设置本次需要处理的并设置到当前对象上,移除command后返回剩余参数数组//并保存当前对象的flags属性的其余参数$this->flags=$this->findCommand($args);//获取当前应用的执行目录//即用户执行swoft脚本的目录$this->pwd=$this->getPwd();if($parsing){//列表($this->args,$this->sOpts,$this->lOpts)=InputParser::fromArgv($args);//调用toolkit/cli-utils包将命令行参数解析成相应的参数,短选项和长选项,有兴趣的可以用这个包//这个包还有设置命令行输出颜色的功能[$this->args,$this->sOpts,$this->lOpts]=Flags::parseArgv($this->flags);}}设置命令方法:protectedfunctionfindCommand(array$flags):array{if(!isset($flags[0])){return$flags;}//不输入命令名if(strpos($flags[0]],'-')===0){return$flags;}$this->command=trim($flags[0]);//删除第一个元素,重置索引键。取消设置($标志[0)找到相似的命令if($similar=Arr::findSimilar($inputCmd,$names)){//控制台打印相似命令的提醒$output->writef("nMaybewhatyoumeanis:n%s",内爆(',',$similar));}else{//打印控制台应用程序帮助信息$this->showApplicationHelp(false);}返回;}//获取匹配的路由信息??$info=$result[1];//获取组名$group=$info['group'];//将组名设置为commentsVars数组$this->addCommentsVar('groupName',$group);//如果只输入组名,则显示该组的帮助信息//只输入一个组名,显示该组的帮助if($result[0]===Router::ONLY_GROUP){//有错误命令if($cmd=$info['cmd']){$output->error("Command'{$cmd}'isnotexistingroup:{$group}");}$this->showGroupHelp($group);返回;}//如果输入包含帮助选项,则显示命令的帮助//显示命令的帮助if($this->input->getSameOpt(['h','help'])){$this->showCommandHelp($信息);返回;}//解析默认选项和参数//解析默认选项和参数//根据路由中的选项,再次解析输入中剩余的args选项$this->input->parseFlags($info,true);//设置命令ID$this->input->setCommandId($info['cmdId']);//触发ConsoleEvent::DISPATCH_BEFORE事件Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE,$this,$info);//调用命令处理器/**@varConsoleDispatcher$dispatcher*///获取ConsoleDispatcher的bean对象$dispatcher=Swoft::getSingleton('cliDispatcher');//执行调度$dispatcher->dispatcher($info);//触发ConsoleEvent::DISPATCH_AFTER事件Swoft::triggerByArray(ConsoleEvent::DISPATCH_AFTER,$this,$info);}dispatchmethod:publicfunctiondispatch(array$route):void{//Handlerinfo//从中获取路由信息中处理程序的类名和方法[$className,$method]=$route['handler'];//绑定方法params//获取需要传递给方法的参数数组$params=$this->getBindParams($className,$method);//获取handler类的bean对象$object=Swoft::getSingleton($className);//阻塞运行//如果不是协程执行if(!$route['coroutine']){//调用预执行方法,内部触发ConsoleEvent::EXECUTE_BEFORE事件$this->before($method,$类名);//调用handler的执行方法,传入构造参数$object->$method(...$params);//调用后执行方法,内部触发了ConsoleEvent::EXECUTE_AFTER事件$this->after($method);//执行结束return;}//如果是协程执行//Hookphpio函数//启用一键协程Runtime::enableCoroutine();//Ifinunittestenvironment//Ifinunittestenv,hasbeenincoroutine.if(defined('PHPUNIT_COMPOSER_INSTALL')){$this->executeByCo($object,$method,$params);返回;}//否则启动协程执行//协程运行srun(function()use($object,$method,$params){$this->executeByCo($object,$method,$params);});}privatefunctiongetBindParams(string$class,string$method):array{//获取类的反射结构$classInfo=Swoft::getR反射($类);//如果类信息中没有被调用的方法信息,则返回空数组if(!isset($classInfo['methods'][$method])){return[];}//保存参数数组//绑定参数$bindParams=[];//获取方法参数数组$methodParams=$classInfo['methods'][$method]['params'];/***@varstring$name*@varReflectionType$paramType*@varmixed$devVal*///遍历参数列表,获取参数类型和默认值foreach($methodParamsas[,$paramType,$devVal]){//获取参数类型的名称//参数的定义类型$type=$paramType->getName();//将对应的输入或输出对象保存到参数数组中,其他类型参数保存为nullif($type===Output::class){$bindParams[]=Swoft::getBean('output');}elseif($type===Input::class){$bindParams[]=Swoft::getBean('input');}else{$bindParams[]=null;}}return$bindParams;}类反射信息池数据结构:/***反射信息池**@vararray**@example*[*'className'=>[//类名*'comments'=>'类文档注释',//类注释*'methods'=>[//方法数组*'methodName'=>[//方法名*'params'=>[//parameterarray*'argName',//like`name`parametername*'argType',//like`int`parametertype*null//like`$arg`defaultvalue*],*'comments'=>'methoddoccomments',//方法注释*'returnType'=>'returnType/null'//返回值类型*]*]*]*]*/协程执行处理类:publicfunctionexecuteByCo($object,string$method,array$bindParams):void{try{//创建控制台协程上下文Context::set($ctx=ConsoleContext::new());//在事件之前触发$this->before($method,get_class($object));//执行处理程序的处理方法$object->$method(...$bindParams);//触发后事件$this->after($method);}catch(Throwable$e){/**@varConsoleErrorDispatcher$errDispatcher*///如果执行失败,错误处理程序将接管$errDispatcher=Swoft::getSingleton(ConsoleErrorDispatcher::class);//处理请求错误$errDispatcher->run($e);}finally{//触发协程生命周期事件//DeferSwoft::trigger(SwoftEvent::COROUTINE_DEFER);//完成Swoft::trigger(SwoftEvent::COROUTINE_COMPLETE);}}总结:1.控制台处理器其实很简单。Router和cliApp,绑定路由并执行cliApp的run方法。2、run方法的流程:(1).预处理,控制台参数等数据处理后,分类保存。(2).匹配路由得到对应命令的hanler类和方法。(3).根据路由信息中返回的是否以协程方式执行,使用相应的阻塞执行或协程执行方式调用相应的方法处理bean对象。