结合namespace自己搭建MVC框架
自定义MVC框架
-
MVC基本介绍
MVC是这3个单词的缩写:
Model:模型,专门用来处理数据的
View:视图,专门用来显示内容(保存一些html文件)
Controller:控制器(中控器),用来分发任务
(命令模型处理数据、命令视图显示数据)
-
MVC的目录结构划分原则
- 将来使用mvc框架的时候,有一些代码是和具体项目的业务相关的,所以我们将这些代码封装到项目的目录里面,通常取名为application,该名称可以自己修改(jd、taobao),除此之外,还有一些代码是公共的(Controller.class.php,Model.class.php等等),这些代码不管做什么项目都可能会用到,我们就将其封装刀片框架目录里面,取名为framework
- 由于随着框架的不断完善,里面会出现很多有用的工具类,所以为了有效的管理,我们分目录存储
像smarty这样别人提供,我们就封装到vendor目录,表示别人赞助的
像Captcha、Page等等,我们自己写的,一些工具,我们就保存到tools目录
像DAOPDO、I_DAO这些类、接口,和数据访问相关的,保存到dao目录
…
-
我们再看应用程序的代码:
由于应用程序(项目),分为前台、后台;前台是用户浏览使用的,后台是管理员进行内容管理的,所以我们项目的目录结构也就划分为前台home、后台admin:
由于前台、后台为了提高开发效率,也都会使用mvc进行代码的管理 -
入口文件(分发控制器)
入口文件又称之为分发控制器,就像公司的前台,根据访客的需求,将其引导到对应的办公室。在我们的框架里面,入口文件会根据用户的需求,引导到对应的文件
那么我们需要在入口文件传递什么参数呢?也就是需要告诉前台哪些信息呢?
(1)去前台还是后台?admin?home
(2)去哪个控制器?controller?
(3)访问控制器的哪个方法?
代码实现:
注意:
由于mvc框架里面的类文件,不会直接执行,而是这些类文件最终被index.php加载,也就是说这些类文件再加载的话,都会从index.php这里出发访问一下:
错误就是说:在Controller.class.php的第10行,没有找到smarty
原因:Controller这个类也是被CategoryConreoller继承的,最终也是会去index.php报到,所以里面加载文件的时候,出发点也是index.php
项目中还有很多类需要require加载,这是一个很庞大的工作,而且很繁琐,所以我们使用自动加载机制完成这个工作 -
自动加载介绍
-
介绍:
当我们使用一个类,但是这个类不存在时,就会触发自动加载机制,自动加载机制提供了最后一次机会,可以把类加载过来
具体的场景:
New 类,不存在
类::静态方法时,类不存在
类 extends 类2,类2不存在
类 implements 接口,接口不存在
使用自动加载机制的话,之前所有的require_once就没用了,所以需要把所有的require_once统统删掉大家自行把所有类里面的require_once都删掉,smarty除外
在测试的时候,就会报这样一个错误信息:
思考:仅仅通过需要的类名,能够找到这个类吗?
找不到,因为不知道去哪个目录里面找,可能admin、home;里面都有CategoryController这个类如何解决这个问题?
如果类的前面有所在的位置信息就好了,给每个增加上命名空间,而且让命名空间和该类所在的位置有一定的关联
-
5.2结合命名空间
说明:
(1)入口文件就不用加命名空间了。因为入口文件用来加载其他类,不定义类
(2)每个类的命名空间包含当前类所在的路径
例如:框架的成员,命名空间定义为:framework\core,项目的类命名空间就不要携带application了,因为application是项目的名称,后期可能会变化,所以项目的类的命名空间:admin\controller或home\controller等
(3)第三方的类,我们不用增加命名空间,我们做一个特例处理(手动require)给每个类都加上命名空间:
注意:在使用这些类时,需要告诉PHP程序,使用的是哪一个空间的类
例如:在入口文件中,实例化控制器类的时候,就需要告诉我们实例化的是哪个空间的类
再来测试的时候,就会提示:
这样我们就可以获得类所在位置了 -
完成自动加载
思路:
就是根据提示的需要的类名,解析出他所在的路径
(1)先根据\
,将字符串分割成数组
由于我们在定义命名空间的时候,
项目的目录相关的:admin\controller、admin\model、home\controller、
和框架的路径相关:framework\core、framework\dao
我们在加载时,也无非就是去这两个目录中找,所以我们判断一下,如果第一个元素是framwork的话,直接去framework目录找,否则去application目录中找spl_autoload_register("autoloader"); function autoloader($className) { echo '我们需要:'.$className.'<br>'; //针对第三方的类,做一个特例处理 if($className=='Smarty'){ require_once './framework/vendor/smarty/Smarty.class.php'; return; } //1. 先将带有命名空间的类,分隔开 $arr = explode('\\', $className); //2. 根据第一个元素确定加载的根目录 if($arr[0] == 'framework'){ $basic_path = './'; }else{ $basic_path = './application/'; } //3. 确定application、framwork里面的子目录 $sub_path = str_replace('\\', '/', $className); //4. 确定文件名 //确定后缀:类文件的后缀:.class.php,接口文件的后缀是:.interface.php //framework\dao\I_DAO,判断最后元素是否是I_开头 if(substr($arr[count($arr)-1], 0,2)=='I_'){ //说明是接口文件 $fix = '.interface.php'; }else{ $fix = '.class.php'; } $class_file = $basic_path.$sub_path.$fix; //5. 加载类 require_once $class_file; // echo '<pre>'; // var_dump($class_file); }
测试一把:
我们是在Controller基础控制器中初始化的Smarty
由于会在当前空间找Smarty类,所以提示的错误:framework\core\Smarty
解决之道:smarty属于全局空间,加上\前缀
再来测试:
说明:smarty_internal_data是smarty自己加载的,也不是我们定义的类,所以我们不需要加载
最终的入口文件自动加载的代码:index.php<?php /* * 入口文件(分发控制器) * 用来接收用户请求时携带的参数 */ spl_autoload_register("autoloader"); function autoloader($className) { echo '我们需要:'.$className.'<br>'; //针对第三方的类,做一个特例处理 if($className=='Smarty'){ require_once './framework/vendor/smarty/Smarty.class.php'; return; } //1. 先将带有命名空间的类,分隔开 $arr = explode('\\', $className); //2. 根据第一个元素确定加载的根目录 if($arr[0] == 'framework'){ $basic_path = './'; }else{ $basic_path = './application/'; } //3. 确定application、framwork里面的子目录 $sub_path = str_replace('\\', '/', $className); //4. 确定文件名 //确定后缀:类文件的后缀:.class.php,接口文件的后缀是:.interface.php //framework\dao\I_DAO,判断最后元素是否是I_开头 if(substr($arr[count($arr)-1], 0,2)=='I_'){ //说明是接口文件 $fix = '.interface.php'; }else{ $fix = '.class.php'; } $class_file = $basic_path.$sub_path.$fix; //5. 加载类 //如果不是按照我们的命名空间的规则定义的,说明不是我们需要加载的类,不用加载 if(file_exists($class_file)){ require_once $class_file; } } //前台还是后台? $m = isset($_GET['m'])?$_GET['m']:'home'; //访问哪个控制器 $c = isset($_GET['c'])?$_GET['c']:'Index'; //访问控制器的哪个操作 $a = isset($_GET['a'])?$_GET['a']:'indexAction'; $controller_name = 'admin\controller\\'.$c.'Controller'; //先加载控制器类,再实例化对象 $controller = new $controller_name; //调用控制器的方法 $controller -> $a();
-
-
5.4封装入口文件
说明:
入口文件有什么好封装的呢?
因为我们采用的OOP(面向对象的思想)封装的框架,框架里面的代码都应该是类,然而现在的入口文件index.php是完全面向过程的写法,所以我们要将其封装到类中具体代码实现:
-
配置系统
说明:
配置系统是什么?
配置文件就是用来保存一些有固定格式、多文件之间的公共的数据
我们在学习smarty的时候,使用过配置文件来保存图片的信息,这样,如果将来图片的路径变化了,我们只需修改配置文件即可,从而提升了维护的效率
我们的mvc框架中,怎么增加配置文件呢?框架应该有自己的基本的信息,例如,框架的版本、模板引擎的模板目录、定界符等将来都可能修改,所以框架应该有自己的配置文件
我们的应用程序也会有一些将来可能修改的数据:数据库的信息,所以应用程序也应该有自己的配置文件
为了灵活起见,我们给前台、后台也增加一些配置文件,目前可能用不到,但是将来可能会用到,所以我们未雨绸缪
如果同时存在3个配置文件,优先级:
框架的配置 < 公共的配置 < 前台、后台各自的配置
- 具体实现:
先创建各自的配置文件:
如何实现优先级高的 覆盖优先级低的配置文件中的相同的配置项,还有一点需要注意:不相同配置项要合并
既然配置文件返回的是数组,那么我就使用数组函数:array_merge()合并数组的
有这样一个机制:相同的会被覆盖、不相同的合并
举例演示:
- 具体实现:
首先,在Framework.class.php中增加3个方法,加载配置文件
因为该文件就相当于入口文件,在这里定义之后,可以在任何地方使用
拿到配置文件的信息之后,替换、合并
-
具体应用配置文件:
在初始化MCA的时候,需要设置默认的模块、控制器、方法
数据库连接部分使用我们配置文件中的配置项进行维护、管理
数据库的连接、初始化是在Model.class.php基础模型类中初始化的
-
路径常量
说明:
路径常量是什么?
框架中,有一些涉及到路径的地方,我们写死的,例如:
如果项目名称后期改变了,我们就需要找到smarty的配置初始化的代码进行修改,我们就可以将项目的名称、路径保存到常量中
将使用到项目、框架的路径部分,使用常量代替:
最后,附带源码
附带源码url