abstract:本章通过手把手写框架的学习,对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>
效果如下:
Correcting teacher:天蓬老师Correction time:2019-02-18 17:21:01
Teacher's summary:不论再大的项目, 都是由这些基本的功能组成, 理解了开发流程, 就好办了, 这个小框架虽然不具备商业使用价值,但是用来学习框架开发入门,足够了,如果你能完整的写下来, 再不看教程的情况,自己独立完成一遍, 那么你的原生php开发水平, 基本上可以胜任一个程序员的日常工作了