php mvc框架
想了解PHP-MVC框架的原理和实现方式是什么?在这里,我们用PHP开发一个自己的MVC框架。小巧精悍。深入理解市面上PHP主流的MVC框架原理。唯一入口文件该干什么,如何自动载入。本章代码基于PHP5.3+,采用命名空间。
MVC是什么?http://baike.baidu.com/view/5432454.htm?fromtitle=mvc&fromid=85990&type=syn
源码地址:https://github.com/lixuancn/LaneSmartFW
开源协议:Do What The Fuck You Want To Public License
一、起名:
先给我们的PHP-MVC框架起个名字,叫宇宙无敌框架UniverseInvincibleFrameWork
二、实现功能
1、MVC分层
2、唯一入口
3、关键常量可配置
4、自动载入函数
5、路由分发
6、数据库工厂
7、多数据支持
8、多项目支持
三、详细分解如何PHP-MVC框架
1、MVC分层
1)、目录结构
框架根目录
项目一的目录
配置文件目录
框架核心文件目录
框架核心文件之数据库目录
2)、目录简介
(1)、Home、Admin是项目名,可以无限扩展
(2)、Config是配置文件所在目录,UniverseInvincibleFrameWork是框架核心文件所在目录
(3)、Index.php是唯一入口文件
(4)、Home目录下就是标准的Controller、Model、View,另外新增了Service
(5)、UniverseInvincibleFrameWork目录下是核心框架入口类、自动载入类、路由类已经数据库文件所在的DB目录
(6)、DB目录是数据库相关操作。比如数据库工厂类,接口规范类,CURD操作等。
2、唯一入口
1)、采用单一入口模式进行项目部署和访问,无论完成什么功能,一个项目都有一个统一的入口。
2)、只需要引入框架核心文件App.php,然后执行该类的方法
/** * 宇宙无敌框架UniverseInvincibleFrameWork * 唯一入口 * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午3:17 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ //引入框架核心文件 require_once 'UniverseInvincibleFrameWork/App.php'; //初始化框架 $obj = new UniverseInvincibleFrameWork\App(); $obj->init();3)、框架核心文件源码:
namespace UniverseInvincibleFrameWork; class App{ public function init(){ //设置头 - utf-8 $this->_setHeader(); //载入系统配置文件 $this->_loadSysFile(); //自动载入函数 $this->_setAutoload(); //设置路由 $this->_setRoute(); } /** * 载入系统配置文件 */ private function _loadSysFile(){ require_once dirname(ql_FILE).'/Function.php'; //1、 require_once dirname(ql_FILE).'/../config/config.php'; //2、$GLOBALS['config'] = config.php的所有内容 $GLOBALS['config'] = require_once dirname(ql_FILE).'/../config/config.php'; } /** * 头 */ private function _setHeader(){ header('Content-type: text/html; charset=UTF-8'); } /** * 自动载入函数 */ private function _setAutoload(){ //自动载入函数 require_once dirname(ql_FILE).'/../UniverseInvincibleFrameWork/Autoload.php'; $autoload = new Autoload(); $autoload->register(); } /** * 设置路由 */ private function _setRoute(){ $routeObj = new Route(); $routeObj->parse(); } }3、关键常量可配置
1)、谁也不会傻呼呼的到把数据库链接信息等配置信息写死到代码里,那么就必须有一个配置文件。它定义系统常量,包括但不限于项目名称、数据库账号密码,默认应用名称/控制器/方法名等
2)、配置文件还有个好处,定义生长环境、测试环境、开发环境等不同的参数,可以根据来访域名、所在机器IP等信息来使自动选择加载不同的系统和数据库配置。
/** * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午3:28 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ return array( //默认加载的项目 'DEFAULT_APP_NAME' => 'Home', //默认加载的控制器 'DEFAULT_CONTROLLER' => 'Index', //默认加载的方法 'DEFAULT_METHOD' => 'index', //默认数据库配置 'DB_CONFIG' => array( 'DB_TYPE' => 'mysql', 'DB_HOST' => 'localhost', 'DB_PORT' => '3306', 'DB_USERNAME' => 'root', 'DB_PASSWORD' =>’’, 'DB_NAME' => db1, ), //默认数据二配置 'DB_CONFIG2' => array( 'DB_TYPE' => 'mysql', 'DB_HOST' => 'localhost', 'DB_PORT' => '3306', 'DB_USERNAME' => 'root', 'DB_PASSWORD' =>’’, 'DB_NAME' => 'db2', ), );4、自动载入函数
1)、不用自动载入函数,难道要在代码里不断的去include其他的文件吗?
2)、我们用spl_autoload_register()。从PHP5.1.2引入。摒弃了__autoload()。它的优势是一个项目可以有多个spl_autoload_register()函数。使得项目框架、各种插件(LaneWeChat、PHPMailer、PHPEXCEL等)不会相互冲突
namespace UniverseInvincibleFrameWork; /** * 自动载入 * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午3:28 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class Autoload{ public function register(){ spl_autoload_register(array($this, 'autoload')); } public function autoload($className){ $pathArr = explode('\\', $className); $filename = array_pop($pathArr); $dir = implode(DIRECTORY_SEPARATOR, $pathArr); $filename = $dir . '/' . $filename . '.php'; if(file_exists($filename)){ require_once $filename; }else{ exit('Error:'.$className.' loading Failed'); } } }5、路由分发
1)、我们的URL规则:http://www.lanecn.com/index.php/项目名/类名/方法名
2)、所有的URL,都会去执行index.php。然后路由的作用的是根据不同的URL,来执行不同的Controller。
namespace UniverseInvincibleFrameWork; /** * 路由 * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午3:29 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class Route{ /** * 分析URL */ public function parse(){ $pathInfo = !empty($_SERVER['PATH_INFO']) ? explode('/', $_SERVER['PATH_INFO']) : array(); $appName = !empty($pathInfo[1]) ? $pathInfo[1] : getConfig('DEFAULT_APP_NAME'); $className = !empty($pathInfo[2]) ? $pathInfo[2] : getConfig('DEFAULT_CONTROLLER'); $methodName = !empty($pathInfo[3]) ? $pathInfo[3] : getConfig('DEFAULT_METHOD'); $c = $appName . '\Controller\\' . $className.'Controller'; $obj = new $c(); $obj->$methodName(); } }6、数据库工厂
1)、大型项目中,我们会用到Mysql、Redis等多种数据库。甚至前期是ACCESS,后期是Mysql/Oracle/SQL SERVER,在切换数据库的过程中,只需要修改一个常量而不需要修改代码。
2)、根据配置文件中定义的数据库类型,我们选在加载不同的数据库类
namespace UniverseInvincibleFrameWork\DB; /** * 数据工厂 * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午3:29 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class Db{ public static function factor($dbConfigKey='DB_CONFIG'){ //根据参数选择加载不同的数据库配置 $dbType = strtolower(getConfig($dbConfigKey)['DB_TYPE']); switch($dbType){ case 'mysql': $className = 'Mysql'; break; default: exit('Error:Database Type'); } $className = 'UniverseInvincibleFrameWork\DB\\'.$className; return new $className($dbConfigKey); } }3)、项目中的Model文件,继承Model类。该类定义了常用的数据库操作,是所有数据库的抽象类。如增删改查和自定义SQL等。该类使用数据工厂中返回的示例,来操作具体的数据库类。
namespace UniverseInvincibleFrameWork\DB; /** * 基础Model类,所有的Model文件均继承本类 * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午6:35 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class Model implements DbInterface { protected $dbConfigKey = null; private $_db = null; private function _getInstance(){ if(is_null($this->_db)){ if(is_null($this->dbConfigKey)){ $this->_db = DB::factor(); }else{ $this->_db = DB::factor($this->dbConfigKey); } } return $this->_db; } public function close(){ $this->_getInstance()->close(); } public function query($sql){ return $this->_getInstance()->query($sql); } public function fetchAssoc($resource){ return $this->_getInstance()->fetchAssoc($resource); } public function select($sql){ return $this->_getInstance()->select($sql); } }4)、最后该编码数据库实例类了。已Mysql为例
namespace UniverseInvincibleFrameWork\DB; /** * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午3:29 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class Mysql implements DbInterface{ private $_conn = null; public function __construct($dbConfigKey='DB_CONFIG'){ if(is_null($this->_conn)){ $this->_connect($dbConfigKey); } } private function _connect($dbConfigKey='DB_CONFIG'){ $dbConfig = getConfig($dbConfigKey); $this->_conn = mysqli_connect($dbConfig['DB_HOST'], $dbConfig['DB_USERNAME'], $dbConfig['DB_PASSWORD'], $dbConfig['DB_NAME'], $dbConfig['DB_PORT']); } public function close(){ mysqli_close($this->_getInstance()); } public function query($sql){ $result = mysqli_query($this->_conn, $sql); return $result; } public function fetchAssoc($resource){ $rowList = array(); while($row = mysqli_fetch_assoc($resource)){ $rowList[] = $row; } return $rowList; } public function select($sql){ $result = $this->query($sql); $rowList = $this->fetchAssoc($result); return $rowList; } }5)、补充一点。我们有一个接口类,为了约定各个数据库的实例类的规范,他们都要实现几个关键方法。
namespace UniverseInvincibleFrameWork\DB; /** * 数据库实例类的接口 * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午5:57 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ Interface DbInterface{ public function close(); public function query($sql); public function fetchAssoc($resource); public function select($sql); }7、多数据支持
1、项目中,常常会遇到既需要数据库A,又需要数据库B。那就需要多数据库支持。
2、在数据库A中获得用户ID列表,在数据库B中根据用户ID列表获得用户详细信息
8、多项目支持
1、框架实现了多项目支持。比如前台、后台、项目三、项目四
四、测试
1、我们在项目Home中,进行测试。编写两个Model文件。第一个是Home/Model/IndexModel.php 来查询默认数据库的Mysql 版本。第二个是Home/ Model / TestModel.php。来查询数据库2的所有表的名字。
namespace Home\Model; use UniverseInvincibleFrameWork\DB\Model; /** * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午4:36 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class IndexModel extends Model{ public function getVersion(){ $sql = 'SELECT VERSION() as `version`'; $result = $this->query($sql); $result = $this->fetchAssoc($result); return $result; } }namespace Home\Model; use UniverseInvincibleFrameWork\DB\Model; /** * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午4:36 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class TestModel extends Model{ public function __construct(){ $this->dbConfigKey = 'DB_CONFIG2'; } public function getTables(){ $sql = 'show tables'; $result = $this->query($sql); $result = $this->fetchAssoc($result); return $result; } }2、写一个Server文件。Home/Server/IndexServer.php来调用刚才写的两个Model文件并返回
namespace Home\Service; /** * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午4:35 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class IndexService{ public static function getVersion(){ $ret = array(); $ret['php_version'] = PHP_VERSION; $model = new \Home\Model\IndexModel();; $ret['mysql_version'] = $model->getVersion()[0]['version']; return $ret; } public static function getTables(){ $model = new \Home\Model\TestModel();; $ret = $model->getTables(); return $ret; } }3、Home/Controller/IndexController.php编写4个测试示例。
namespace Home\Controller; /** * Created by lixuan-it@360.cn * User: lane * Date: 15/8/27 * Time: 下午4:31 * E-mail: lixuan868686@163.com * WebSite: http://www.lanecn.com */ class IndexController{ public function index(){ echo 'hello world'; } public function test(){ echo 'hello 360'; } public function getVersion(){ $versionList = \Home\Service\IndexService::getVersion(); echo 'PHP版本:' . $versionList['php_version'].'。Mysql版本:' . $versionList['mysql_version']; } public function getTables(){ $tables = \Home\Service\IndexService::getTables(); var_dump($tables); } }4、结果:
1)、浏览器运行http://framework/index.php/Home/Index/index。正常输出:“hello world”
2)、浏览器运行http://framework/index.php/Home/Index/test。正常输出:“hello 360”
3)、浏览器运行http://framework/index.php/Home/Index/getVersion。正常输出:“PHP版本:5.5.27。Mysql版本:5.6.17”
4)、浏览器运行http://framework/index.php/Home/Index/getTables。正常输出数据库2的所有表名
相关文章
文章评论
-
-
-