ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# :-: 一、容器与门面 >[info] 容器: 类与类实例的集合, 至少包括类实例的绑定与创建二个功能 * 设计模式中的容器的原理与应用场景 1. 容器可以简单的理解为类与类实例的集合 2. 本例中,我们将模型与视图的实例放在一个容器中进行统一管理 3. 容器本身也是一个类, 类中至少要提供二个方法: 创建实例和取出实例 >[info] 容器 >> Controller.php 文件 ```php // 加载'模型类' require 'Model.php'; // 加载'视图类' require 'View.php'; /****************************************************************************/ // 创建容器类,容器中可保存很多类型,这里仅以类实例方法来演示 class Container{ // 创建容器属性,保存着类实例的创建方法 protected $instance = []; // $alias: 类实例别名, $process: 类的实例化过程/函数/Closure闭包类型 public function bind($alias, Closure $process){ // 将类实例方法存入到容器中 $this->instance[$alias] = $process; } // 创建类的实例: 执行容器中的类的实例方法,$alias参数是容器中的类实例别名 public function make($alias, $params=[]) { return call_user_func_array($this->instance[$alias], $params); } } // 将要用到的类实例(Mode/View)绑定到容器中 $container = new Container(); // 将Model类实例的方法绑定到容器中,标识为'model' $container->bind('model', function () { return new Model(); }); // 将View类实例的方法绑定到容器中,标识为'view' $container->bind('view', function() { return new View(); }); /****************************************************************************/ // 控制器 class Controller { // 将容器对象注入到控制器方法中 public function index(Container $container){ //获取数据: 容器对象的make()方法 $data = $container->make('model')->getData(); //渲染模板 return $container->make('view')->fetch($data); } } //客户端调用 $controller = new Controller(); //以容器对象$container为实参调用 echo $controller->index($container); ``` * 将类的实例过程使用容器进行包装,是一个非常不错的解决方案 * 但是我还是觉得在控制器中充斥着大量的make()方法,吃相太难看,能不能再进行简化呢? * 当然可以,现在有请"Facade门面模式"闪亮登场 >[info] 门面: 给容器中的类方法调用,提供一个统一的静态访问接口 >> Controller.php 文件 ```php // 加载'模型类' require 'Model.php'; // 加载'视图类' require 'View.php'; /********************************************************************************/ // 创建容器类,容器中可保存很多类型,这里仅以类实例方法来演示 class Container { // 创建容器属性,保存着类实例的创建方法 protected $instance = []; // $alias: 类实例别名, $process: 类的实例化过程/函数/Closure闭包类型 public function bind($alias, Closure $process){ // 将类实例方法存入到容器中 $this->instance[$alias] = $process; } // 创建类的实例: 执行容器中的类的实例方法,$alias参数是容器中的类实例别名 public function make($alias, $params=[]){ return call_user_func_array($this->instance[$alias], []); } } // 将要用到的类实例(Mode/View)绑定到容器中 $container = new Container(); // 将Model类实例的方法绑定到容器中,标识为'model' $container->bind('model', function () { return new Model(); }); // 将View类实例的方法绑定到容器中,标识为'view' $container->bind('view', function() { return new View(); }); /****************************************************************************/ //声明Facade类,来接管对容器中类实例方法的调用 class Facade { // 控制器中调用到了Model类中的getData()方法, View类中的view()方法 // 我们就为这二个访问创建一个统一访问的静态接口 // 调用方法使用的关键字static: 可以实现在静态继承上下文环境中,实现静态成员调用者的动态设置 // 后面的Product 就是 Facade类的子类,如果不使用static::就无法动态调用子类中的成员 // 如果你用不到子类,这里可以不用static后期静态绑定,可以使用self::替代 // 但是使用static , 会使代码更具健壮性,能用性 protected static $container = null; protected static $data = []; public static function initialize(Container $container) { static::$container = $container; // static::是后期静态绑定的语法 } // 设置获取数据的静态代理方法:getDate() public static function getData(){ // static::$container 是当前引用类'Model'的实例 static::$data = static::$container->make('model')->getData(); } // 设置渲染模板的静态代理方法:fetch() public static function fetch(){ // static::$container 是当前引用类'View'的实例 return static::$container->make('view')->fetch(static::$data); } } // 声明一个商品类 class Product extends Facade { // 自定义的业务逻辑 } /***************************************************************************/ // 控制器 class Controller { //在构造方式中调用Facade中初始化方式,完成容器的实例化: 依赖注入 public function __construct(Container $container) { Product::initialize($container); } // 将容器对象注入到控制器方法中 public function index(){ Product::getData(); // 获取数据 return Product::fetch(); // 渲染模板 } } //客户端调用 $controller = new Controller($container); //以容器对象$container为实参调用 echo $controller->index(); ``` * 门面Facade模式,使类方法调用更加的优雅,全部采用静态方式统一调用 * 相当于给类中的方法戴了一个静态面具/静态代理 * 在Larval、ThinkPHP6.0 大量的使用到了Facade模式,请一定要掌握,否则会成为框架学习的最大障碍 ***** # :-: 二、路由的原理与实现 ### 1、从pathinfo地址切割为独立的数组单元 * pathinfo: 用目录分隔符组成一个URL地址, 注意?后面的查询字符串不属于它 * 以请求地址: http://www.ouyangke.com/route.php/admin/index/hello 为例 * 可能大家对模块的概念不是很清楚, 模板可以理解为一个应用, 例如一个网站会有一个前台模块, 一个后台模块, 各司其责 ```php $uri = $_SERVER['REQUEST_URI']; $request = explode(DIRECTORY_SEPARATOR, $uri); // 框架的基本原理,就是根据HTTP请求的URL地址, 从中解析出对应的函数进行处理 // 而操作函数有二个载体, 一是函数本身, 二是类方法 // 路由到函数, 其实这个函数就是所谓的闭包, 所谓路由到闭包, 就是由一个函数来执行HTTP请求 // 路由到类方法最为常用, 这个类, 就是控制器, 方法就是控制器类中的某个操作方法(本质仍是函数) // 大家现在是否发现, 路由就是一个请求分发器, 要么将HTTP请求交给一个函数处理, 要么交给一个类方法来处理 echo '<pre>' . print_r($request,true); ``` ### 2、从pathinfo数组中解析出:模块,控制器和操作 ```php $uri = $_SERVER['REQUEST_URI']; $request = explode(DIRECTORY_SEPARATOR, $uri); // 我们所关心的只是模块,控制器和方法,所以从数组的第3项开始来获取数据 $route = array_slice($request,2,3); // 查看获取到的module/controller/action echo '<pre>' . print_r($route,true); // list()语法结构, 可以从将一个索引数组中每个元素,设置一个字符串键名,并转为独立变量使用 // list()并非函数, 因为它可以用到等号左边, 接受赋值操作, 而函数是绝对不允许也不可能用在等号左边的 list($module, $controller, $action) = $route; echo "模块:{$module}<br>控制器: {$controller}<br>操作: {$action}<br>"; // 将pathinfo内容解析到一个数组中保存 $pathinfo['module'] = $module; $pathinfo['controller'] = $controller; $pathinfo['action'] = $action; // 使用数组函数: compace()简化, 该函数可以将多个变量转为关联数组 $pathinfo = compact('module','controller', 'action'); echo '<pre>' . print_r($pathinfo,true); ``` >[info] 因为路由最终会指向一函数或类方法, 该函数/类方法是允许有参数的,并且可能不止一个。所以,路由必须要具备从URL中解析出正确的参数键值对的能力 ### 3、从pathinfo数组中解析出:参数键值对 * 以请求地址: http://www.ouyangke.com/route.php/admin/index/hello/name/peter/position/lecture 为例 ```php $uri = $_SERVER['REQUEST_URI']; $request = explode(DIRECTORY_SEPARATOR, $uri); $values = array_slice($request,5); for ($i=0; $i<count($values); $i+=2) { // 将键值对中的第一个做为键,第二个做为值,该操作仅在第二个值存在的情况下执行 if( isset($values[$i+1])) { $params[$values[$i]] = $values[$i+1]; } } // 查看解析出来的参数 echo '<pre>' . print_r($params,true); ``` ### 4、创建与pathinfo中的`控制器/操作/参数键值对`对应的类和方法 ```php $uri = $_SERVER['REQUEST_URI']; $request = explode(DIRECTORY_SEPARATOR, $uri); $route = array_slice($request,2,3); list($module, $controller, $action) = $route; $pathinfo = compact('module','controller', 'action'); $values = array_slice($request,5); for ($i=0; $i<count($values); $i+=2) { // 将键值对中的第一个做为键,第二个做为值,该操作仅在第二个值存在的情况下执行 if( isset($values[$i+1])) { $params[$values[$i]] = $values[$i+1]; } } class Index{ public function hello($id,$name){ return "Index控制器hello()操作: $id= {$id}, $name= {$name}"; } public function demo($a, $b, $c){ return "Index控制器demo()操作,它的参数是: $a = {$a}, $b={$b}, $c={$c}"; } } echo call_user_func_array([(new $pathinfo['controller']()), $pathinfo['action']], $params); ``` ***** # :-: 三、模板引擎的原理与实现 * 模板引擎应该具备的基本功能: * 存储一组变量 * 读取模板文件 * 组成前二者生成输出 >[info] index.php文件 ```php // 模板引擎的调用脚本 // 加载模板引擎类 require 'template.php'; // 实例化模板引擎类 $template = new template('hello.php'); // 模板赋值: $hello = 'php模板引擎很简单', 变量不存在则显示默认值 $template->set('hello','php模板引擎很简单'); // 输出模板 echo $template->display(); // 浏览器访问: index.php, 输出: php模板引擎很简单 ``` >[info] hello.php文件 ```php <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>模板文件</title> </head> <body> <h2> <?php echo isset($hello) ? $hello : '欢迎访问' ?> </h2> </body> </html> ``` >[info] template.php文件 ```php // 模板引擎 class template{ private $data = []; private $file; // 获取模板文件名 public function __construct($file){ $this->file = $file; } // 设置模板变量 public function set($name,$value){ $this->data[$name] = $value; } // 生成并输出模板 public function display(){ // 提取模板变量: 关联数组转变量名值对 extract($this->data); // 开启输出缓冲, 只生成数组,并不会立即输出到浏览器 ob_start(); // 加载模板文件 include $this->file; // 返回缓冲区中的内容 $string = ob_get_contents(); // 清空缓冲区并关闭输出缓冲 ob_end_clean(); // 返回模板HTML return $string; } } ```