mvc(model-view-controller)是软件工程的一种软件架构模式。
在mvc模式设计下,软件系统被分来三个模块:模型(model)、视图(view)、控制器(controller)。
php下的mvc模式又称为web mvc,自上世纪70年代进化而来。
使用mvc模式的目的是:实现一种动态的程序设计,便于后续对程序的修改和拓展,且使得程序的某一部分的重复利用成为可能。
mvc各模块的职能:
模型model:管理大部分的业务逻辑和所有的数据库逻辑。模型抽象简化了连接和操作数据库的操作。控制器controller:负责响应用户请求、准备数据,决定如何展示数据。视图view:负责数据渲染,通过html方式呈现给用户。一个典型的web mvc 处理流程:
controller接受到用户发来的请求;controller调用model完成对状态的读写操作;controller把数据传递给view;view渲染出html页面并展示给用户。为了做以mvc模式开发的各类cms的代码审计。
建站软件:phpstudy2018
ide:phpstorm2018.1
php版本:5.4.45-nts
apache&mysql
我给该web mvc框架取名为:myphp
该项目目录为:myphpframe1
整个项目的目录结构如下:
myphpframe1 web框架部署根目录├─application 应用目录│ ├─controllers 控制器目录│ ├─models 模块目录 │ └─views 视图目录├─config 配置文件目录├─myphp 框架核心目录├─runtime 运行临时目录├─static 静态文件目录├─.htaccess apache目录配置└─index.php 入口文件
myphpframe1位于apache站点根目录之下。通过访问 http://localhost/myphpframe1 ,可以访问到该项目。
3.2展示的目录中.htaccess文件,是apache服务器的目录级别的分布式配置文件,可以针对特定目录改变apache配置。
.htaccess 可以帮我们实现:重写url、网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
apache服务器通过启用allowoverride all实现对应目录下的配置可重写。
本框架下.htaccess文件内容为:
<ifmodule mod_rewrite.c> #打开rerite功能 rewriteengine on # 如果请求的是真实存在的文件f或目录d,直接访问 rewritecond %{request_filename} !-f rewritecond %{request_filename} !-d #重定向所有请求到index.php?url=原路径 rewriterule ^(.*)$ index.php?url=$1 [pt,l] #[pt] passthrough,使得rewriterule的结果重写加入到url的匹配中 #[l] last,使得mod_rewrite 停止处理规则集</ifmodule>
这里使用该.htaccess的原因是:
1、 静态文件可以直接访问,比如css文件、js文件都可以直接访问。
(如果是非index.php的php文件,可以访问,不过由于框架特性,类之间需要extends,可能会报错。如果是目录,也可以访问,如果apache开启了目录列表,则可以看到index of目录,否则返回403。)
2、 程序有了单一的入口,就是index.php。
当请求地址不是真实存在的文件或目录时,请求就会传给index.php。
例如,访问地址:http://localhost/myphpframe1/item/index,文件系统中并不存在这样的文件或目录。则apache会把重写这个地址为:http://localhost/myphpframe1/index.php?url=item/index。这样在php中用$_get[‘url’]就可以拿到 item/index了。
代码规范如下:
mysql的表名:使用小写字母与下划线(_)命名,如:item、bus_infomodel模块名:使用大驼峰法(首字母大写),并在名称后加上model,如:itemmodel、busmodelcontroller控制器名:使用大驼峰法(首字母大写),并在名称后加上controller,如:itemcontroller、buscontrolleraction方法名:使用小驼峰法(首字母小写),如:index、lectallview视图 部署结构为:控制器名/行为名,如:item/index.php、item/manage.php使用代码规范的目的:使得程序能更好地相互调用。
index.php为整个项目的入口文件,位于项目根目录/下。
文件内容为:
<?phpheader("content-type: text/html; chart=utf-8");//设置返回包编码方式,避免页面乱码//初始化常量define('app_path',__dir__.'/');//网站根目录define('config_path',app_path.'config/');//网站配置目录define('app_debug',fal);//开启调试模式define('app_url','http://localhost/myphpframe1/');//网站urldefine('runtime_path',app_path.'runtime/');//网站临时目录//加载配置文件require config_path.'/config.php';//加载框架核心文件require app_path.'myphp/myphp.php';//实例化框架类,并执行run()方法$myphp=new myphp();$myphp->run();
可以看到,上面的php代码并没有使用php结束符 ?>。
纯php代码中php结束符是可选的,提倡不写php结束符。如果这个是一个被别人require的php文件,没有这个结束符,可以避免多余输出(也就是?>之后的任何数据,包括空格、换行符等)导致header, tcookie, ssion_start函数执行的失败(这几个函数执行前,不允许展示任何数据)。
config.php是项目的配置文件。位于config/目录下。
config.php的作用是:定义数据库连接参数,配置默认控制器名和默认动作名。
config.php文件内容为:
<?php//数据库连接参数define('db_name','myphpdb');define('db_ur','root');define('db_password','root');define('db_host','localhost');//默认控制器名和默认方法名define('default_controller','item');define('default_action','index');
myphp.php是myphp框架的核心类文件。位于myphp/目录下。
在入口文件中,对框架类做了两步操作:实例化、调用run()方法。
run()方法调用了框架类自身方法,完成以下操作:
类自动重载环境设置清理转义字符移除全局变量处理路由myphp.php文件内容为:
<?php/** * myphp核心框架类 *///初始化常量defined('app_path') or define('app_path',__dir__.'\\');defined('app_url')or define('app_url','http://localhost/myphpframe1');defined('app_debug') or define('app_debug',fal);defined('config_path') or define('config_path',app_path.'config\\');defined('runtime_path') or define('runtime_path',app_path.'runtime/');defined('default_controller') or define('default_controller','item');defined('default_action') or define('default_action','index');class myphp{ /** *运行程序 */ function run() { spl_autoload_register(array($this,'loadclass')); //spl_autoload_register — 注册给定的函数作为 __autoload 的实现 //__autoload — 尝试加载未定义的类。当我们实例化一个未定义的类时,就会触发此函数 $this->treporting(); $this->removemagicquotes(); $this->unregisterglobals(); $this->route(); } /** *路由处理 *abc.com/controllername/actionname/querystring * eg: * 访问url:localhost/item/show/name/1 * 进入到route方法后,分割url,获得: * $controller:item * action:show * querystring:array(name,1) * 然后,实例化一个新控制器:itemcontroller,并调用itemcontroller->show()方法 */ function route() { $controllername=default_controller; $actionname=default_action; if(!empty($_get['url'])) { $url=$_get['url'];//http://localhost/ $urlarray=explode('/',$url);//explode 把字符串打散为数组 //获取控制器名 $controllername=ucfirst($urlarray[0]); //ucfirst 首字母转换为大写 //获取动作名 array_shift($urlarray);//array_shift 删除数组中的第一个元素,并返回被删除元素的值 $actionname=empty($urlarray[0])?$actionname:$urlarray[0]; //获取url参数 array_shift($urlarray); $querystring=empty($urlarray[0])?array():$urlarray; } //url数据为空时 $querystring=empty($querystring)?array():$querystring; //判断控制器、方法 是否存在 $controller=$controllername.'controller'; if(!class_exists($controller))//class_exists — 检查类是否已定义 { exit($controller.'控制器不存在'); } elif(!method_exists($controller,$actionname)) { exit($actionname.'方法不存在'); } //实例化控制器,因为控制器对象里面 //还会用到控制器名和操作名,所以实 //例化的时候把他们俩的名称也传入。查看controller基类就明白。 $dispatch=new $controller($controllername,$actionname); //$dispatch保存控制器实例化后的对象,我们就可以调用它的方法,也可以向方法中传入参数 //call_ur_func_array 调用回调函数,并把一个数组参数作为回调函数的参数 //以下等同于:$dispatch->$action($querystring) call_ur_func_array(array($dispatch,$actionname),$querystring); } /* * 设置开发环境 * */ function treporting() { if(app_debug===true) { error_reporting(e_all); // 报告所有错误 ini_t('display_errors','on'); //ini_t 设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。 }el{ error_reporting(e_all); ini_t('display_errors','off'); ini_t('log_errors','on'); ini_t('error_log',runtime_path.'logs/error.log'); } } /* * 删除多余的反斜杠 */ function stripslashesdeep($value) { $value=is_array($value)?array_map('stripslashesdeep',$value):stripslashes($value); // 递归调用 // stripslashes — 返回一个去除转义反斜线后的字符串(\' 转换为 ' 等等)。双反斜线(\\)被转换为单个反斜线(\) //array_map — 为数组的每个元素应用回调函数 return $value; } /* * 检测转义后的字符并清除反斜杠 */ function removemagicquotes() { //get_magic_quotes_gpc 获得php配置magic_quotes_gpc的bool值 //如果开启magic_quotes_gpc,则对get、post、cookie 数据自动运行addslashes() //addslashes 在预定义字符之前添加反斜杠。预定义字符:单引号',双引号",反斜杠\,null //magic_quotes_gpc特性已自 php 5.3.0 起废弃并将自 php 5.4.0 起移除。所以在5.4版军师联盟剧情介绍本以后php配置文件是找不到魔术引号的配置信息的 //php 5.4之后,get_magic_quotes_gpc统一返回fal if(get_magic_quotes_gpc()) { $_get=$this->stripslashesdeep($_get); $_post=$this->stripslashesdeep($_post); $_cookie=$this->stripslashesdeep($_cookie); $_ssion=$this->stripslashesdeep($_ssion); } } /* * 检测自定义全局变量(register globals)并移除,模拟register_globals=off */ function unregisterglobals() { /* * register_globals的意思就是注册为全局变量,5.4之后已被弃用。当register_globals=on时, * 局部变量的在脚本的全局域也可用(eg:$_get['a']也将以$a的形式存在) * 这样写是不好的实现,会影响代码中的其他变量 */ if(ini_get('register_globals')) { $array=array('_ssion','_post','_get','_cookie','_request','_rver','_env','_files'); foreach ($array as $value){ echo $value; foreach($globals[$value]as $key=>$var)//处理每个内置数组中每个键值对 { if($var===$globals[$key]){//如果变量值等于全局变量中对应同名的变量值 unt($globals[$key]);//销毁对应的全局变量 } } } } } /* * 自动加载控制器和模型类 */ static function loadclass($class) { //echo '执行loadclass('.$class.')<br />'; $frameworks=__dir__ . '\\'.$class.'.class.php'; $controllers=app_path.'application\\controllers\\'.$class.'.php'; $models=app_path.'application\\models\\'.$class.'.php'; //echo $frameworks.'<br/>'; //echo $controllers.'<br/>'; //echo $models.'<br/>'; if(file_exists($frameworks)){ //加载核心框架类 //echo '开始加载 框架核心类:'.$frameworks.'<br />'; include $frameworks; //echo '成功加载 框架核心类:'.$frameworks.'<br />'; } elif (file_exists($controllers)) { //echo '开始加载 应用控制器类:'.$controllers.'<br />'; //加载应用控制器类el include $controllers; //echo '成功加载 应用控制器类:'.$controllers.'<br />'; } elif (file_exists($models)) { //echo '开始加载 应用模型类:'.$models.'<br />'; //加载应用模型类 include $models; //echo '成功加载 应用模型类:'.$models.'<br />'; } el{ //加载失败代码 exit('加载核心类文件失败!'); } //echo 'loadclass('.$class.')结束<br />'; }}
讲解2个方法:loadclass()、route()
localclass()作用是:加载未定义的类时,导入对应的类文件。
首先构造对应类的可能的文件路径:如果对应类是核心框架类,则类文件路径应该为$frameworks;如果对应类是应用控制器类,则类文件路径应该为$controllers;如果对应类是应用模型类,则类文件路径应该为$models。
接着,对每个可能存在类文件路径,进行file_exists判定,存在则include。
则无本框架下任意类都可以完成自动加载。
route()作用是:通过url,解析出控制器名、方法名和url参数,然后实例化对应的控制器,执行对应的方法,并传入对应的url参数。
假设浏览器访问的url为:yourhost.com/controllername/actionname/querystring
首先,apache会根据.htaccess重写url,重写后的url为:yourhost.com/index.php?url=controllername/actionname/querystring
route()从全局变量$_get[‘url’]中获得字符串 controllername/actionname/querystring
然后,route()会将字符串转换为数组,通过对数组的操作获得3部分:controllername、actionname、querystring。
最后,route()会实例化对应控制器,并调用对应方法。
例如,url链接为:yourhost.com/item/manage/6,经过route()处理后:
$controllername为:item$actionname为:manage $urlarray为:array(6)处理完成后,route()会实例化控制器itemcontroller,并调用它的manage(array(6))
接下来,就是在myphp框架中创建mvc基类,包括控制器、模型、视图三个基类。
在myphp/目录下,新建一个控制器基类,文件名为controller.class.php,主要功能就是对整个程序进行调度,文件内容为:
<?php/** * 控制器基类 */class controller{ protected $_controller; //控制器名 protected $_action; //动作名 protected $_view; //视图对象 //构造函数:初始化属性,并实例化对应视图模型 function __construct($controller,$action) { $this->_controller=$controller; $this->_action=$action; $this->_view=new view($controller,$action); } //分配变量 //controller 类用assign()方法实现把变量保存到view对象中。 //这样,应用controller调用父类controller的 $this-&g铜雀台剧情t;render()后,视图文件就可以显示这些变量。 function assign($name,$value) { $this->_view->assign($name,$value); } //渲染视图 function render() { // todo: implement __destruct() method. $this->_view->render(); }}
controller类通过 assign()方法 实现了变量从controller对象到view对象的传递(view类的assign就是将数据保存到自己数组中)。
这样,controller类在调用$this->render()后,视图对象就可以渲染展示这些变量了。
在myphp/目录下,新建一个模型基类,文件名为model.class.php,文件内容为:
<?php/** * 模型基类 */class model extends sql{ protected $_model; protected $_table; function __construct() { //连接数据库 $this->connect(db_host,db_ur,db_password,db_name); //获取模型类名称 $this->_model=get_class($this); $this->_model=rtrim($this->_model,'model');//rtrim 从字符串右侧移指定字符 //模型类名称与数据库中的表名一致 $this->_table=strtolower($this->_model); } function __destruct() { // todo: implement __destruct() method. }}
可以看到,model基类继承了sql类。
因为数据操作比较复杂,所以我为这部分操作单独创建了一个sql类。
在myphp/目录下,新建一个sql类,文件名为sql.class.php,文件内容为:
<?php/** * 数据库操作类 */class sql{ protected $_dbhandle; protected $_result; //连接数据库 public function connect($host,$ur,$pass,$dbname) { try{ $dsn=sprintf("mysql:host=%s;dbname=%s;chart=utf8",$host,$dbname);//sprintf 把百分号(%)符号替换成一个作为参数进行传递的变量: $options=array(pdo::attr_default_fetch_mode=>pdo::fetch_assoc); //pdo::fetch_assoc:返回一个索引为结果集列名的数组 $this->_dbhandle=new pdo($dsn,$ur,$pass,$options); } catch(pdoexception $e) { exit('错误:'.$e->getmessage()); } } //查询所有数据 public function lectall() { $sql=sprintf("lect * from `%s`",$this->_table); $sth=$this->_dbhandle->prepare($sql); $sth->execute(); return $sth->fetchall(); } //根据条件(id)查询 public function lect($id) { $sql=sprintf("lect * from `%s` where `id`='%s'",$this->_table,$id); $sth=$this->_dbhandle->prepare($sql); $sth->execute(); return $sth->fetch(); } //根据条件(id)删除 public function delete($id) { $sql=sprintf("delete from `%s` where `id`='%s'",$this->_table,$id); $sth=$this->_dbhandle->prepare(); $sth->execute(); return $sth->rowcount(今天是清明节吗); } //自定义sql查询语句,返回影响的行数 public function query($sql) { $sth=$this->_dbhandle->prepare($sql); $sth->execute($sql); return $sth->rowcount(); } //新增数据 public function add($data) { $sql=sprintf("inrt into `%s` %s",$this->_table,$this->formatinrt($data)); 工科生return $this->query($sql); } //修改数据 public function update($id,$data) { $sql=sprintf("update `%s` t %s where `id`='%s'",$this->_table,$this->formatupdate($data),$id); return $this->query($sql); } //将数组转换为inrt语句中的数据格式 /* $array=array("id"=>1,"name"=>"jack","age"=>19); formatinrt($array)返回字符串: (`id`,`name`,`age`) values ('1','jack','19') */ private function formatinrt($data) { $fields=array(); $values=array(); foreach($data as $key=>$value) { $fields[]=sprintf("`%s`",$key); $values[]=sprintf("'%s'",$value); } $filed=implode(',',$fields);//implode 把数组元素组合为字符串: $value=implode(',',$values); return sprintf("(%s) values (%s)",$filed,$value); } //将数组转换为update语句中的数据格式 /* $array=array("name"=>"jack","age"=>19); formatupdate($array)返回字符串: `name`='1',`jack`='19' */ private function formatupdate($data) { $fields=array(); foreach ($data as $key=>$value) { $fields[]=sprintf("`%s`='%s'",$key,$value); } return implode(',',$fields); }}
在myphp/目录下,新建一个视图基类,文件名为view.class.php,文件内容为:
<?php/** * 视图基类 */class view{ protected $variables=array(); protected $_controller; protected $_action; function __construct($controller,$action) { $this->_controller=$controller; $this->_action=$action; } //导入变量 function assign($name,$value) { $this->variables[$name]=$value; } //渲染显示 function render() { extract($this->variables); //extract - 用来将一个数组分解成多个变量直接使用。 $defaultheader=app_path.'application/views/header.php'; $defaultfooter=app_path.'application/views/footer.php'; $controllerheader=app_path.'application/views/'.$this->_controller.'/header.php'; $controllerfooter=app_path.'application/views/'.$this->_controller.'/footer.php'; //页头文件 if(file_exists($controllerheader)) { include ($controllerheader); } el { include ($defaultheader); } //页内容文件 include (app_path.'application/views/'.$this->_controller.'/'.$this->_action.'.php'); //页脚文件 if(file_exists($controllerfooter)) { include ($controllerfooter); } el { include ($defaultfooter); } }}
至此,核心的php mvc框架核心就搭建完成了。
下面,我要编写基于框架的应用代码来测试这个框架的功能。
在sql中新建一个数据库 myphpdb,增加一个item表,并插入表中2个记录,sql命令如下:
create databa `myphpdb` default character t utf8 collate utf8_general_ci;u法律类专业 `myphpdb`;create table `item`( `id` int(11) not null auto_increment, `item_name` varchar(255) not null, primary key (`id`))engine=innodb auto_increment=1 default chart=utf8;inrt into `item` values(1,'hello world.');inrt into `item` values(2,'let\'s go!');
在application/models/目录下,创建一个itemmodel.php文件,主要功能是增加了检索数据的业务逻辑,文件内容为:
<?php/** * 用户model */class itemmodel extends model{ /** * 自定义当前模型操作的数据库表名称 * 如果不指定,则默认为类名称的小写字符串, * 此处为item 表 * */ public $_table='item'; /** * 搜索功能,以为sql父类中,并没有现成的like搜索 * 所以需要自己写sql语句,对数据库的操作应该都放 * 在model中,然后提供给controller直接调用 */ public function arch($keyword) { $sql=sprintf("lect * from `%s` where `item_name` like '%%%s%%'",$this->_table,$keyword); $sth=$this->_dbhandle->prepare($sql); $sth->execute(); return $sth->fetchall(); }}
因为 item 模型继承了 model基类,所以它拥有 model 基类的所有功能。
在application/controllers/目录下,创建一个itemcontroller.php文件,主要功能是准备数据、调用对应的视图,文件内容为:
<?php/** * item控制器类 */class itemcontroller extends controller{ //首页文件,测试myphp框架自定义的sql查询 public function index() { $keyword=ist($_get['keyword'])?$_get['keyword']:''; if ($keyword) { $items=(new itemmodel())->arch($keyword); } el{ $items=(new itemmodel())->lectall(); } //传入视图数据 $this->assign('title','全部条目'); $this->assign('keyword',$keyword); $this->assign('items',$items); //渲染试图 $this->render(); } //添加记录,测试myphp框架的sql查询-create public function add() { $data['item_name']=$_post['value']; $count=(new itemmodel)->add($data); $this->assign('title','添加成功'); $this->assign('count',$count); //渲染试图 $this->render(); } //操作管理 public function manage($id=null) { $item = array(); $posturl=app_url.'/item/add'; if($id) { $item=(new itemmodel)->lect($id); $posturl=app_url.'/item/update'; } $this->assign('title','管理条目'); $this->assign('item',$item); $this->assign('posturl',$posturl); //渲染试图 $this->render(); } //更新记录,测试框架的sql查询-update public function update() { $data=array('id'=>$_post['id'],'item_name'=>$_post['value']); $count=(new itemmodel)->update($data['id'],$data); $this->assign('title','修改成功'); $this->assign('count',$count); //渲染试图 $this->render(); } //删除记录,测试框架的sql查询-delete public function delete($id=null) { $count=(new itemmodel)->delete($id); $this->assign('title','删除成功'); $this->assign('count',$count); //渲染试图 $this->render(); }}
在 application/views/目录下新建 header.php 和 footer.php 两个页头页脚模板文件,文件内容为:
header.php 内容:
<html><head> <meta http-equiv="content-type" content="text/html; charrt=utf-8"/> <title><?php echo $title; ?></title> <link rel="stylesheet" href="/static/css/main.css" type="text/css" /></head><body><h1> <?php echo $title; ?></h1>
footer.php 内容:
</body></html>
页头文件使用了main.css文件,内容:
html,body{ margin: 0; padding: 10px; font-size: 20px;}input{ color:black; font-family: georgia, times; font-size:24px; font-weight:normal; line-height: 1.2em;}a{ color:blue; font-family: georgia,times; font-size: 20px; font-weight: normal; line-height: 1.2em; text-decoration: none;}a:hover{ text-decoration: underline;}h1{ color: #000000; font-size: 41px; letter-spacing: -2px; line-height: 1em; font-family: helvetica,arial,sans-rif; border-bottom: 1px dotted #cccccc;}td{ padding: 1px 30px 1px 0;}
现在,在application/view/item/目录下,创建以下几个视图文件。
index.php,作用是展示数据库中item表的所有记录、检索记录、删除记录,文件内容为:
<form action="" method="get"> <input type="text" value="<?php echo $keyword;?>" name="keyword"> <input type="submit" value="搜索"></form><p> <a href="<?php echo app_url; ?>item/manage">新建</a></p><table> <tr> <th>id</th> <th>内容</th> <th>操作</th> </tr> <?php foreach ($items as $item):?> <tr> <td><?php echo $item['id']; ?></td> <td><?php echo $item['item_name']; ?></td> <td> <a href="<?php echo app_url; ?>item/manage/<?php echo $item['id']; ?>">编辑</a> <a href="<?php echo app_url; ?>item/delete/<?php echo $item['id']; ?>">删除</a> </td> </tr> <?php endforeach;?></table>
manage.php,作用是编辑记录,文件内容为:
<form action="<?php echo $posturl; ?>" method="post"> <?php if(ist($item['id'])): ?> <input type="hidden" name="id" value="<?php echo $item['id']; ?>"> <?php endif; ?> <input type="text" name="value" value="<?php echo ist($item['item_name'])?$item['item_name']:''; ?>"> <input type="submit" value="提交"></form><a class="big" href="<?php echo app_url; ?>item/index">返回</a>
add.php,作用是提示 已添加记录,文件内容为:
<a class="big" href="<?php echo app_url; ?>item/index"> 成功添加<?php echo $count; ?>条记录,点击返回</a>
update.php,作用是提示 已修改记录,文件内容为:
<a class="big" href="<?php echo app_url; ?>item/index"> 成功修改<?php echo $count; ?>项,点击返回</a>
delete.php,作用是提示 已删除记录,文件内容为:
<a href="<?php echo app_url; ?>item/index"> 成功删除<?php echo $count; ?>项,点击返回</a>
至此,所有的应用代码已经编写完成。
在浏览器中访问 http://localhost/myphpframe1/ ,成功!
严重参考:
https://www.awaimai.com/128.html
/d/file/titlepic/5914175.html style="text-align: left">感谢他们的分享!!
本文发布于:2023-04-07 19:23:33,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/7a8743828466daa2199cadb4167781d9.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:PHP MVC框架【Myphp】的编写.doc
本文 PDF 下载地址:PHP MVC框架【Myphp】的编写.pdf
留言与评论(共有 0 条评论) |