微信号:phpdaily

介绍:PHP在线专注于PHP编程语言学习,PHP开发经验分享,工作问题解决以及PHP在线技能测评等多功能为一体的服务系统,希望给工作学习中的PHPER带来些帮助。

PHP开发一个属于自己MVC框架

2018-07-10 22:46 Lane

微信公众号:PHP在线

想了解PHP-MVC框架的原理和实现方式是什么?在这里,我们用PHP开发一个自己的MVC框架。小巧精悍。深入理解市面上PHP主流的MVC框架原理。唯一入口文件该干什么,如何自动载入。本章代码基于PHP5.3+,采用命名空间。

PHP框架是什么?

http://baike.baidu.com/link?url=3IBWTY1jzqQiHAfVWRn6BzXvkUxkWTDffhe7_J4o7Eqs2Hxp06PMnAXsK82vGdnQEfEYuueszQ3_EFdxMUOvEa

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,然后执行该类的方法

<?php

/**

* 宇宙无敌框架UniverseInvincibleFrameWork

* 唯一入口

* Created by lixuan-it@360.cn

* User: lane

* E-mail: lixuan868686@163.com

* WebSite: http://www.lanecn.com

*/


//引入框架核心文件

require_once 'UniverseInvincibleFrameWork/App.php';

//初始化框架

$obj = new UniverseInvincibleFrameWork\App();

$obj->init();

3)、框架核心文件源码:

<?php

namespace UniverseInvincibleFrameWork;





class App{

   public function init(){

       //设置头 - utf-8

       $this->_setHeader();

       //载入系统配置文件

       $this->_loadSysFile();

       //自动载入函数

       $this->_setAutoload();

       //设置路由

       $this->_setRoute();

   }



   /**

    * 载入系统配置文件

    */


   private function _loadSysFile(){

       require_once dirname(__FILE__).'/Function.php';

       //1、 require_once dirname(__FILE__).'/../config/config.php';

       //2、$GLOBALS['config'] = config.php的所有内容

       $GLOBALS['config'] = require_once dirname(__FILE__).'/../config/config.php';

   }



   /**

    * 头

    */


   private function _setHeader(){

       header('Content-type: text/html; charset=UTF-8');

   }



   /**

    * 自动载入函数

    */


   private function _setAutoload(){

       //自动载入函数

       require_once dirname(__FILE__).'/../UniverseInvincibleFrameWork/Autoload.php';

       $autoload = new Autoload();

       $autoload->register();

   }



   /**

    * 设置路由

    */


   private function _setRoute(){

       $routeObj = new Route();

       $routeObj->parse();

   }

}
3、关键常量可配置

1)、谁也不会傻呼呼的到把数据库链接信息等配置信息写死到代码里,那么就必须有一个配置文件。它定义系统常量,包括但不限于项目名称、数据库账号密码,默认应用名称/控制器/方法名等

2)、配置文件还有个好处,定义生长环境、测试环境、开发环境等不同的参数,可以根据来访域名、所在机器IP等信息来使自动选择加载不同的系统和数据库配置。

<?php

/**

* Created by lixuan-it@360.cn

* User: lane

* 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',

   ),

);

在Function.php增加一个函数,用来读取配置文件。

<?php

/**

* Created by lixuan-it@360.cn

* User: lane

* E-mail: lixuan868686@163.com

* WebSite: http://www.lanecn.com

*/


function getConfig($name){

   return $GLOBALS['config'][$name] ? : '';

}
4、自动载入函数

1)、不用自动载入函数,难道要在代码里不断的去include其他的文件吗?

2)、我们用spl_autoload_register()。从PHP5.1.2引入。摒弃了__autoload()。它的优势是一个项目可以有多个spl_autoload_register()函数。使得项目框架、各种插件(LaneWeChat、PHPMailer、PHPEXCEL等)不会相互冲突

<?php

namespace UniverseInvincibleFrameWork;

/**

* 自动载入

* Created by lixuan-it@360.cn

* User: lane

* 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。

<?php

namespace UniverseInvincibleFrameWork;

/**

* 路由

* Created by lixuan-it@360.cn

* User: lan

* 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)、根据配置文件中定义的数据库类型,我们选在加载不同的数据库类

<?php

namespace UniverseInvincibleFrameWork\DB;

/**

* 数据工厂

* Created by lixuan-it@360.cn

* User: lane

* 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等。该类使用数据工厂中返回的示例,来操作具体的数据库类。

<?php

namespace UniverseInvincibleFrameWork\DB;

/**

* 基础Model类,所有的Model文件均继承本类

* Created by lixuan-it@360.cn

* User: lane

* 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为例

<?php

namespace UniverseInvincibleFrameWork\DB;



/**

* Created by lixuan-it@360.cn

* User: lane

* 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)、补充一点。我们有一个接口类,为了约定各个数据库的实例类的规范,他们都要实现几个关键方法。

<?php

namespace UniverseInvincibleFrameWork\DB;

/**

* 数据库实例类的接口

* Created by lixuan-it@360.cn

* User: lane

* 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的所有表的名字。

<?php

namespace Home\Model;

use UniverseInvincibleFrameWork\DB\Model;



/**

* Created by lixuan-it@360.cn

* User: lane

* 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;

   }

}
<?php

namespace Home\Model;

use UniverseInvincibleFrameWork\DB\Model;



/**

* Created by lixuan-it@360.cn

* User: lane

* 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文件并返回

<?php

namespace Home\Service;

/**

* Created by lixuan-it@360.cn

* User: lane

* 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个测试示例。

<?php

namespace Home\Controller;

/**

* Created by lixuan-it@360.cn

* User: lane

* 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.访问域名+Home/Index/index。正常输出:“hello world”

2.访问域名+Home/Index/test。正常输出:“hello 360”

3.访问域名+Home/Index/getVersion。正常输出:“PHP版本:5.5.27。Mysql版本:5.6.17”

4.访问域名+Home/Index/getTables。正常输出数据库2的所有表名


 
PHP在线 更多文章 PHP面试题 Redis数据结构详解,五种数据结构分分钟掌握 这些GIT经验够你用一年了 如何发挥出PHP7的高性能 API接口设计
猜您喜欢 [CV] 通俗理解『卷积』——从傅里叶变换到滤波器 这些酷炫的3D打印机,让你轻松成为设计师! 招聘面试程序员的一些心得 为什么打断 15 分钟的代价是 1 个小时 一份数学小白也能读懂的「马尔可夫链蒙特卡洛方法」入门指南