💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 模板编译流程,大概是: 1. 先获取到View类实例(依赖注入也好,通过助手函数也好) 2. 使用View编译方法fetch或display。都会通过engine方法获取到当前的模板驱动 3. 把模板以及要编译的数据传入到驱动中对应的display方法或fetch方法 4. display和fetch差不多(个人感觉),这俩的大致流程都是 5. 判断缓存文件失效没,如果失效重新编译放入缓存目录。如果没失效则直接读取输出或返回 ### 1、app\\controller\\Index->Index() 控制器方法 ``` public function index(View $view) { //定义一个data变量,是个关联数组 $data = [ 'data'=>'我是变量data', 'msg'=>'我是变量msg' ]; //直接调用View实例中的fetch方法进行渲染。并把data传入进去 return $view->fetch('index',$data); //第二种方式 $strings = "{$data}-{$msg}"; return $view->assign($data)->display($strings,$data); } ``` 上面代码所示,tp的渲染模板。默认有两种方式,一种是调用fetch直接进行渲染。第二种就是先调用assign方法传入要渲染的数据。然后再调用display方法进行渲染。(还有助手函数view().这里就不过多描述了) ### 2、接下来就讲讲tp是如何将数据渲染到模板上。并且输出①第一种方式首先会调用\\Think\\View视图类中的fetch方法 ``` public function fetch(string $template = '', array $vars = []): string { //调用类中的getContent方法,并将模板文件、渲染的数据 //封装成匿名函数传入到getContent方法中 return $this->getContent(function () use ($vars, $template) { $this->engine()->fetch($template, array_merge($this->data, $vars)); }); } ``` ### 接下来看下getContent方法 ``` //这个方法,主要就是把传入过来的匿名函数给运行一下 //通过ob_系列函数捕捉到缓冲区的输出 //然后将捕捉到的缓冲区内容返回 protected function getContent($callback): string { //开启缓存 ob_start(); //取消缓存输出到页面上 ob_implicit_flush(0); //开始渲染 try { //执行闭包 $callback(); } catch (\Exception $e) { //如果闭包函数执行失败则清空缓存 ob_end_clean(); //然后弹出异常 throw $e; } //获取并清空缓存 $content = ob_get_clean(); //如果filter有闭包。一般为替换当前页面的什么东西。执行一下 if ($this->filter) { $content = call_user_func_array($this->filter, [$content]); } //最后直接返回 return $content; } ``` ### 传入的闭包内容 ``` //大概就是获取视图驱动,然后传入模板和数据,渲染模板 $this->engine()->fetch($template, array_merge($this->data, $vars)); ``` ``` ### 追踪到engine方法//这个就不多解释什么了,主要就是获取视图驱动的实例 public function engine(string $type = null) { return $this->driver($type); } ``` ### 3、再追踪到tp默认视图驱动中的fetch方法 ``` public function fetch(string $template, array $vars = []): void //判断是否有数据如果有则合并this->data if ($vars) { $this->data = array_merge($this->data, $vars); } //判断有没有缓存,如果有。则直接输出缓存 if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) { // 读取渲染缓存 if ($this->cache->has($this->config['cache_id'])) { echo $this->cache->get($this->config['cache_id']); return; } } //解析传入过来的模板名 $template = $this->parseTemplateFile($template); if ($template) { //这里生成一下缓存路径+模板 $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); //判断缓存文件是否还有效,或者存在 if (!$this->checkCache($cacheFile)) { // 缓存无效 重新模板编译 //如果不存在,读取模板。并调用compiler重新编译 $content = file_get_contents($template); $this->compiler($content, $cacheFile); } //开启页面缓存 ob_start(); //禁止直接输出 ob_implicit_flush(0); // 读取编译存储 $this->storage->read($cacheFile, $this->data); // 获取并清空缓存 $content = ob_get_clean(); if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) { // 缓存页面输出 $this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']); } //输出编译后的内容 echo $content; } } ``` ### 这里主要是编译模板的方法 compiler。主要就是把模板内的文件都替换掉 ``` private function compiler(string &$content, string $cacheFile): void { // 判断是否启用布局 if ($this->config['layout_on']) { if (false !== strpos($content, '{__NOLAYOUT__}')) { // 可以单独定义不使用布局 $content = str_replace('{__NOLAYOUT__}', '', $content); } else { // 读取布局模板 $layoutFile = $this->parseTemplateFile($this->config['layout_name']); if ($layoutFile) { // 替换布局的主体内容 $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); } } } else { $content = str_replace('{__NOLAYOUT__}', '', $content); } //这里是替换模板内的tp语法例如 {$data} = echo $data //{if (1==1))} == <?php if (1==1): ?> //{/endif} == <?php endif;?> $this->parse($content); if ($this->config['strip_space']) { /* 去除html空格与换行 */ $find = ['~>\s+<~', '~>(\s+\n|\r)~']; $replace = ['><', '>']; $content = preg_replace($find, $replace, $content); } // 优化生成的php代码 $content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content); // 模板过滤输出 $replace = $this->config['tpl_replace_string']; $content = str_replace(array_keys($replace), array_values($replace), $content); // 添加安全代码及模板引用记录 $content = '<?php /*' . serialize($this->includeFile) . '*/ ?>' . "\n" . $content; //写入缓存 $this->storage->write($cacheFile, $content); $this->includeFile = []; } ``` ### 接着回到fetch方法中 ``` / 读取编译存储 $this->storage->read($cacheFile, $this->data); ``` ### 这里是获取到\\Think\\template\\driver\\File类实例并且将缓存文件和数据传入到read方法中 ``` public function read(string $cacheFile, array $vars = []): void { $this->cacheFile = $cacheFile; if (!empty($vars) && is_array($vars)) { //通过extract方法,将数据写到符号表内 extract($vars, EXTR_OVERWRITE); } //载入模版缓存文件 include $this->cacheFile; } ``` # 到这里,第一种方式流程就算是完了 第二种、display方法去渲染 ``` //这个和第一种流程第一步一样,不多解释 public function display(string $content, array $vars = []): string { return $this->getContent(function () use ($vars, $content) { //调用template中的display方法,并将content和数据传入进去 $this->engine()->display($content, array_merge($this->data, $vars)); }); } ``` ### 追踪到Template类中的display方法 ``` public function display(string $content, array $vars = []): void { //判断有没有要编译的数据/如果有就和this->data叔祖1合并 if ($vars) { $this->data = array_merge($this->data, $vars); } //获取到编译后的缓存模板 $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); //调用checkCahce把缓存模板传入进去,查看是否过期 //或不存在 if (!$this->checkCache($cacheFile)) { //如果模板无效或不存在,就调用 //compiler方法重写缓存 $this->compiler($content, $cacheFile); } // 读取编译存储 $this->storage->read($cacheFile, $this->data); } ```