摘要:本章通过手把手写框架的学习,对MVC的模式有了进一步的认识,通过实践,可以通过手写框架实现数据的增删改查。项目结构如下:本次框架主要包含三大目录及其他文件:基类目录。该目录命名为pong。pong目录下面包含core目录、基类文件、配置文件、路由文件,其中,core目录包含了controller、view、model三个基类。自己的应用项目。项目放在app目录下,app目录下设admin目录、ho
本章通过手把手写框架的学习,对MVC的模式有了进一步的认识,通过实践,可以通过手写框架实现数据的增删改查。项目结构如下:

本次框架主要包含三大目录及其他文件:
基类目录。该目录命名为pong。pong目录下面包含core目录、基类文件、配置文件、路由文件,其中,core目录包含了controller、view、model三个基类。
自己的应用项目。项目放在app目录下,app目录下设admin目录、home目录、Model目录,admin目录主要用于存放后台管理页面的数据,里面包含controller和view两个文件夹,分别存放具体页面对应的控制器文件和视图文件。home目录与admin目录的架构类型类似,主要用于存放前台的文件。Model目录,主要用于存放系统公用的model类,因为model类是可以给前台和后台公用,所以独立到一个文件夹中。
模板框架目录。该目录主要是通过composer下载的模板框架和数据库框架。
.htaccess文件。该文件主要用于页面的重定向。
composer.json和composer.lock文件。这两个文件主要是通过composer获取框架后自动生成的文件,其中composer.lock存放了框架的基础配置,如框架版本。
Index.php文件。该文件为入口文件,通过.htaccess文件配置的重定向,默认访问域名时,即进入该文件。
框架源码:
入口文件index.php
<?php
/**
* 入口文件
*/
//导入模板框架
require __DIR__.'/vendor/autoload.php';
//导入基础类
require __DIR__.'/pong/Base.php';
//获取配置文件信息
$config=require __DIR__.'/pong/Config.php';
//定义根目录路径
define('ROOT_PATH',__DIR__.'/');
//获取查询字符串
$queryStr=$_SERVER["QUERY_STRING"];
//启动框架
(new \pong\Base($config,$queryStr))->run();基础类Base.php:
<?php
/**
* 基类
*/
namespace pong;
class Base
{
//配置数组
protected $config=[];
//查询字符串
protected $queryStr=[];
public function __construct($config,$queryStr='')
{
$this->config=$config;
$this->queryStr=$queryStr;
}
//设置调试状态
public function setDebug()
{
//如果调试模式为true,则开启调试模式,否则关闭调试模式
if ($this->config['app']['debug']) {
error_reporting(E_ALL);
ini_set('display_errors','On');
} else {
ini_set('display_errors','Off');
ini_set('log_errors', 'On');
}
}
//设置自动加载器
public function loader($class)
{
//把参数格式进行转换
$path = ROOT_PATH . str_replace('\\','/',$class) . '.php';
//判断文件是否存在
if(!file_exists($path))
{
header('Location:/');//返回根目录
}
//加载文件
require_once $path;
}
//启动框架
public function run()
{
//设置调试模式
$this->setDebug();
//自动加载
spl_autoload_register([$this,'loader']);
//请求分发
$route=new Route($this->config['route']);
echo $route->parse($this->queryStr)->dispatch();
}
}配置基类config.php
<?php /** * 配置文件 */ return [ //应用配置 'app'=>[ //调试开关 'debug'=>true ], //路由配置 'route'=>[ //默认模块 'module'=>'admin', //默认控制器 'controller'=>'Index', //默认操作 'action'=>'index' ], 'db'=>[ // 数据库类型 'database_type' => 'mysql', // 默认数据库名称 'database_name' => 'test', // 默认主机名 'server' => '127.0.0.1', // 默认用户名 'username' => 'root', // 用户密码 'password' => 'root', // 编码 'charset' => 'utf8', //端口 'port' => 3306, ] ];
路由基类Route.php:
<?php
/**路由类*/
namespace pong;
class Route
{
protected $route=[];
protected $passInfo=[];
protected $params=[];
public function __construct($route)
{
$this->route=$route;
}
/**
* @param string $queryStr 链接参数
* @return $this 当前类的实例
*/
public function parse($queryStr='')
{
//去除字符串左右的/,并将参数全部转为小写
$queryStr=strtolower(trim($queryStr,'/'));
//通过/把字符串转成数组
$queryArr=explode('/',$queryStr);
//数组过滤掉空字符
$queryArr=array_filter($queryArr,function($value){
return trim($value)!='';
},ARRAY_FILTER_USE_BOTH);
//判断数组中有几个参数
switch (count($queryArr))
{
case 0:
$this->passInfo = $this->route;
break;
case 1:
$this->passInfo['module']=$queryArr[0];
break;
case 2:
$this->passInfo['module']=$queryArr[0];
$this->passInfo['controller']=$queryArr[1];
break;
case 3:
$this->passInfo['module']=$queryArr[0];
$this->passInfo['controller']=$queryArr[1];
$this->passInfo['action']=$queryArr[2];
break;
default:
$this->passInfo['module']=$queryArr[0];
$this->passInfo['controller']=$queryArr[1];
$this->passInfo['action']=$queryArr[2];
//从第四个元素开始遍历,即过滤掉路由的信息
for($i=3;$i<count($queryArr);$i+=2)
{
if(isset($queryArr[$i+1]))
{
$this->params[$queryArr[$i]]=$queryArr[$i+1];
}
}
break;
}
//返回当前对象
return $this;
}
public function dispatch()
{
//模块名
$module = $this->passInfo['module'];
//控制器名
$controller='\app\\' .$module . '\controller\\' . ucfirst($this->passInfo['controller']) ;
//操作
$action= $this->passInfo['action'];
// echo $controller , ' ' , $action;
// return;
//如果找不到,重新返回根目录
if(!method_exists($controller,$action))
{
header('location:/');
}
return call_user_func_array([new $controller,$action],$this->params);
}
}
//测试
//$config=require __DIR__.'/Config.php';
//$route=new Route($config['route']);
////测试请求分发
//require __DIR__ . '/../app/admin/controller/Index.php';
//print_r($route->parse($_SERVER['QUERY_STRING'])->dispatch()) ;控制器基类Controller.php
<?php
/**控制器基类*/
namespace pong\core;
class Controller
{
protected $view=null;
public function __construct()
{
$this->view=new namespace\View();
$this->config();
}
//配置方法
public function config()
{
//设置后台模板目录的命名空间
$this->view->addFolder('admin', ROOT_PATH.'/app/admin/view');
//设置后台模板目录的命名空间
$this->view->addFolder('home', ROOT_PATH.'/app/home/view');
}
//模板赋值
public function assign($name, $value)
{
$this->data[$name] = $value;
}
//渲染模板
public function fetch($file)
{
//1.将容器中的关联数据转为变量:键名->变量名,元素值->变量值,返回变量个数
extract($this->data);
//2. 将模板加载到当前控制器方法对应的脚本本
require $file;
}
}视图基类view.php
<?php
/*视图基类**/
namespace pong\core;
use League\Plates\Engine;
class View extends Engine
{
public function __construct($directory = null, $fileExtension = 'php')
{
parent::__construct($directory, $fileExtension);
}
}模型基类Controller.php
<?php
/**模型基类*/
namespace pong\core;
use Medoo\Medoo;
class Model extends Medoo
{
public function __construct($option=null)
{
$config=require __DIR__.'/../Config.php';
$option=$config['db'];
parent::__construct($option);
}
}样式文件style.css
table {
border-collapse: collapse;
width: 60%;
text-align: center;
margin: 30px auto;
}
table, th, td {
border: 1px solid black;
}
table tr th {
background-color: lightblue
}
table tr:hover {
background-color: antiquewhite;
color: green;
}
table caption {
font-size: 1.5em;
font-weight: bolder;
margin-bottom: 10px;
}
a {
text-decoration-line: none;
color: green;
}
table tr td:last-child a:last-child {
color: red;
}
p {
text-align: center;
}
form {
text-align: center
}以下为项目主目录app文件:
admin/controller/Index.php
<?php
namespace app\admin\controller;
use app\model\User;
use pong\core\Controller;
class Index extends Controller
{
public function __construct()
{
parent::__construct();
}
public function test()
{
$name='1pong';
$this->assign('name',$name);
$this->fetch(__DIR__ . '/../view/index/test.php');
}
//管理员登录
public function login()
{
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
//验证用户
$res = (new User)->get('admin', ['name'],[
'AND' => [
'email' => $_POST['email'],
'psword' => $_POST['psword'],
]
]);
if (empty($res)) {
echo "<script>alert('邮箱或密码不正确');location.href='/';</script>";
} else {
$_SESSION['name'] = $res['name'];
echo "<script>alert('登陆成功');location.href='/';</script>";
}
}
}
//管理员退出登录
public function logout()
{
session_destroy();
echo "<script>alert('退出成功');location.href='/';</script>";
}
public function index()
{
// exit('aaa');
$rows=(new User())->select('user',['uid','name','phone','weight','height','add_time'],[
//搜索条件
'name[~]'=> isset($_POST['name']) ? $_POST['name'] : null
]);
//print_r($rows);
return $this->view->render('admin::index/index',[
'rows'=>$rows,
'title'=>'用户管理',
'loginUrl' => '/admin/index/login',
'logoutUrl' => '/admin/index/logout',
'indexUrl' => '/admin/index/index',
'insUrl' => '/admin/index/insert',
'editUrl' => '/admin/index/edit',
'delUrl' => '/admin/index/delete',
]);
}
//添加数据
public function insert()
{
return $this->view->render('admin::index/insert', [
'title' => '添加用户',
'url' => '/admin/index/add'
]);
}
//执行添加操作
public function add()
{
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
//执行添加操作
(new User())->insert('user', [
'name'=>$_POST['name'],
'phone'=>$_POST['phone'],
'weight'=>$_POST['weight'],
'height'=>$_POST['height'],
'add_time'=>time(),
]);
echo "<script>alert('添加成功');location.href='/';</script>";
}
}
//填充需编辑的数据
public function edit($id='')
{
$row = (new User())->get('user',['uid','name','phone','weight','height'],['uid'=>$id]);
return $this->view->render('admin::index/edit', [
'title' => '编辑用户',
'url' => '/admin/index/save',
'row' => $row,
]);
}
//保存操作
public function save($id)
{
//判断请求类型,post提交的才执行
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
//执行更新操作
(new User())->update('user', [
'name' => $_POST['name'],
'phone' => $_POST['phone'],
'weight'=>$_POST['weight'],
'height'=>$_POST['height'],
], ['uid' => $id]);
//提示用户信息
echo "<script>alert('更新成功');location.href='/';</script>";
}
}
//执行删除操作
public function delete($uid)
{
//执行删除操作
(new User())->delete('user',['uid'=>$uid]);
//提示用户信息
echo "<script>alert('删除成功');location.href='/';</script>";
}
}admin/controller/View/index.php:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/static/css/style.css">
<title><?=$this->e($title); ?></title>
</head>
<body>
<?php session_start();?>
<?php isset($_SESSION['name'])?$_SESSION['name']:'' ?>
<?php if(isset($_SESSION['name']) && $_SESSION['name'] =='admin'): ?>
<p>欢迎: <?=$this->e($_SESSION['name'])?> | <a href="<?=$this->e($logoutUrl)?>">退出</a> </p>
<?php else: ?>
<form action="<?=$this->e($loginUrl)?>" method="post">
邮箱: <input type="email" name="email" required>
密码: <input type="password" name="psword" required>
<button>登录</button>
</form>
<?php endif; ?>
<table>
<caption>日本女明星展示表
<?php if(isset($_SESSION['name']) && $_SESSION['name'] =='admin'): ?>
<small><a href="<?=$this->e($insUrl)?>">添加</a></small>
<?php endif; ?>
</caption>
<tr><th>ID</th><th>姓名</th><th>手机号</th><th>胸围</th><th>身高</th><th>注册时间</th>
<?php if(isset($_SESSION['name']) && $_SESSION['name'] =='admin'): ?>
<th>操作</th>
<?php endif; ?>
</tr>
<?php foreach ($rows as $row) : ?>
<tr>
<td><?=$this->escape($row['uid'])?></td>
<td><?php echo $this->escape($row['name']); ?></td>
<td><?php echo $this->e($row['phone']); ?></td>
<td><?php echo $this->e($row['weight']); ?></td>
<td><?=$this->e($row['height'])?></td>
<td><?=$this->e(date('Y-m-d H:i:s',$row['add_time']))?></td>
<?php if(isset($_SESSION['name']) && $_SESSION['name'] =='admin'): ?>
<td><a href="<?=$this->e($editUrl)?>/uid/<?=$this->e($row['uid'])?>">编辑</a> |
<a href="javascirpt:;" onclick="del(<?=$this->e($row['uid'])?>);return false">删除</a>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</table>
<!-- 搜索功能-->
<form action="<?=$this->e($indexUrl)?>" method="post">
女星名称: <input type="text" name="name" placeholder="输入女星关键字">
<button>搜索</button>
</form>
<!--删除操作-->
<script>
function del(uid) {
if (confirm('是否删除 id='+uid+' 的记录?')) {
location.href = '/admin/index/delete/uid/'+uid
}
}
</script>
</body>
</html>admin/controller/View/edit.php:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><?=$this->e($title) ?></title>
</head>
<body>
<style>
table {
border:1px solid lightgrey;
background-color: lightcyan;
border-radius: 3%;
box-shadow: 2px 2px 2px #888;
padding: 20px;
width: 300px;
height: 250px;
margin:30px auto;
}
table caption {
font-size: 1.3em;
margin-bottom: 10px;
}
table tr td:first-child {text-align: center}
table tr td button {
width: 100px;
height: 30px;
background-color: green;
color: white;
border: none;
}
table tr td button:hover {cursor: pointer;background-color: lightcoral}
</style>
<form action="<?=$this->e($url)?>/uid/<?=$this->e($row['uid'])?>" method="post">
<table>
<caption>编辑女明星</caption>
<tr>
<td><label for="name"></label>女星名称:</td>
<td><input type="text" name="name" id="name" value="<?=$this->e($row['name'])?>"></td>
</tr>
<tr>
<td><label for="phone"></label>手机号:</td>
<td><input type="text" name="phone" id="phone" value="<?=$this->e($row['phone'])?>"></td>
</tr>
<tr>
<td><label for="weight"></label>胸围:</td>
<td><input type="text" name="weight" id="weight" value="<?=$this->e($row['weight'])?>"></td>
</tr>
<tr>
<td><label for="height"></label>身高:</td>
<td><input type="text" name="height" id="height" value="<?=$this->e($row['height'])?>" ></td>
</tr>
<tr>
<td colspan="2" align="center"><button>提交</button></td>
</tr>
</table>
</form>
</body>
</html>admin/controller/View/insert.php:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><?=$this->e($title) ?></title>
</head>
<body>
<style>
table {
border:1px solid lightgrey;
background-color: lightcyan;
border-radius: 3%;
box-shadow: 2px 2px 2px #888;
padding: 20px;
width: 300px;
height: 250px;
margin:30px auto;
}
table caption {
font-size: 1.3em;
margin-bottom: 10px;
}
table tr td:first-child {text-align: center}
table tr td button {
width: 100px;
height: 30px;
background-color: green;
color: white;
border: none;
}
table tr td button:hover {cursor: pointer;background-color: lightcoral}
</style>
<form action="<?=$this->e($url)?>" method="post">
<table>
<caption>添加女明星</caption>
<tr>
<td><label for="name"></label>女星名称:</td>
<td><input type="text" name="name" id="name" required></td>
</tr>
<tr>
<td><label for="phone"></label>手机号:</td>
<td><input type="text" name="phone" id="phone"></td>
</tr>
<tr>
<td><label for="weight"></label>胸围:</td>
<td><input type="text" name="weight" id="weight" ></td>
</tr>
<tr>
<td><label for="height"></label>身高:</td>
<td><input type="text" name="height" id="height"></td>
</tr>
<tr>
<td colspan="2" align="center"><button>提交</button></td>
</tr>
</table>
</form>
</body>
</html>效果如下:

批改老师:天蓬老师批改时间:2019-02-18 17:21:01
老师总结:不论再大的项目, 都是由这些基本的功能组成, 理解了开发流程, 就好办了, 这个小框架虽然不具备商业使用价值,但是用来学习框架开发入门,足够了,如果你能完整的写下来, 再不看教程的情况,自己独立完成一遍, 那么你的原生php开发水平, 基本上可以胜任一个程序员的日常工作了