博客列表 >【自撸框架】PHP实战利用composer自撸一个MVC小框架

【自撸框架】PHP实战利用composer自撸一个MVC小框架

 一纸荒凉* Armani
 一纸荒凉* Armani原创
2021年05月24日 10:18:441038浏览

自撸 PHP 发开框架

1.MVC三层架构

  • M :model, 使用第三方包实现
  • V :view, 使用第三方包实现
  • C :controller :业务逻辑是写在控制器中

2.下载第三方包

  • Model : composer require catfan/medoo
  • View : composer require league/plates
  • 分别执行以上命令下载第三方模型和视图库文件包

  1. {
  2. "require": {
  3. "catfan/medoo": "^2.0",
  4. "league/plates": "^3.4"
  5. },
  6. "autoload": {
  7. "psr-4": {
  8. "app\\controllers\\": "app/controllers",
  9. "app\\models\\": "app/models",
  10. "app\\views\\": "app/views",
  11. "core\\": "core"
  12. }
  13. }
  14. }

https://packagist.org/packages/catfan/medoo#v2.0.0

https://packagist.org/packages/league/plates#v3.4.0

3. 构建使用流程

  • 创建自己的框架核心代码,MODEL, VIEW,分别继承第三方的包
  • 创建自己的应用, 按MVC架构模式,创建属于自己的models, views, controllers

core/Model.php

  1. <?php
  2. namespace core;
  3. use Medoo\Medoo;
  4. /**
  5. * 继承第三方模型 atfan/medoo
  6. */
  7. //框架核心模型类文件
  8. class Model extends Medoo
  9. {
  10. public function __construct()
  11. {
  12. //mysql连接参数
  13. $config = [
  14. 'database_type' => 'mysql',
  15. 'server' => 'localhost',
  16. 'database_name' => 'mydb',
  17. 'username' => 'root',
  18. 'password' => 'root'
  19. ];
  20. // 给父类的构造方法传递参数
  21. parent::__construct($config);
  22. }
  23. }
  24. ?>

core/View.php

  1. <?php
  2. namespace core;
  3. use League\Plates\Engine;
  4. /**
  5. * 继承第三方视图 league/plates
  6. */
  7. //框架核心 视图类
  8. class View extends Engine
  9. {
  10. //不能起名叫directory, 会重写父类属性$directory;
  11. public $template; // 所渲染模板的目录
  12. // 可以传入第二个参数,指定渲染模板的文件后缀
  13. public function __construct($path,$fileExtension='html')
  14. {
  15. $this->template = parent::__construct($path);
  16. }
  17. }
  18. ?>

一.MVC框架核心组成:

1.MVC框架大体可分为model、view、controller,这三块主要都是类、对象的应用和扩展
2.关于composer组件是对PHP代码模块化的一种形式,代码封装,再利用;
3.MVC框架整体构架没有变化,只是在MVC的架构中添加了composer组件管理器,来方便添加组件功能;
4.对于框架可以整体理解为三部分:MVC框架部分{app和core}和组件部分{vendor和composer.json、composer.lock}以及入口文件;
5.MVC框架重点理解:命名空间和自动加载之间的关系;往往出问题都在自动加载这块,自动加载出问题:往往跟命名空间、类名称和文件路径不一致导致;注意:\/的使用场景

二. 实战案例MVC框架

(利用我们继承过来的模型catfan/medoo和视图league/plates写一个个MVC小框架)

  1. 创建的目录文件结构如下:

  2. composer.json文件中自动加载配置项

  1. {
  2. "name": "ldy/frame",
  3. "description": "MVC小框架",
  4. "require": {
  5. "catfan/medoo": "^1.7",
  6. "league/plates": "^3.4"
  7. },
  8. "autoload": {
  9. "psr-4": {
  10. "app\\controllers\\": "app/controllers",
  11. "app\\models\\": "app/models",
  12. "app\\views\\": "app/views",
  13. "core\\": "core"
  14. }
  15. }
  16. }

执行命令:composer dump-autoload

  1. 控制类代码 app\controllers\UsersController.php
  1. <?php
  2. namespace app\controllers;
  3. class UsersController
  4. {
  5. private $model=null;
  6. private $view=null;
  7. public function __construct($model,$view)
  8. {
  9. $this->model = $model;
  10. $this->view = $view;
  11. }
  12. public function index(){
  13. // return __METHOD__;
  14. return $this->view->render('users/index',['name'=>'zhang']);
  15. }
  16. }
  17. ?>
  1. 模型类代码 app\models\UsersModel.php
  1. <?php
  2. // 业务层模型类
  3. namespace app\models;
  4. // 引入并继承核心模型文件
  5. use core\Model;
  6. class UsersModel extends Model
  7. {
  8. public function __construct(){
  9. parent::__construct();
  10. }
  11. }
  12. ?>
  1. 视图类代码 app\views\users\index.html
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>hello</title>
  6. </head>
  7. <body>
  8. <h1>hello world! </h1>
  9. <h2>My name is <?=$name?></h2>
  10. </body>
  11. </html>
  1. 入口文件代码 public\index.php
  1. <?php
  2. // composer 自动加载器
  3. require __DIR__.'\\..\\vendor\\autoload.php';
  4. use app\controllers\UsersController;
  5. use app\models\UsersModel;
  6. use core\View;
  7. // 测试业务层模型类 UsersModel
  8. // var_dump(new UsersModel);
  9. // 测试核心层 视图类 view
  10. // var_dump(new View("../../app/views"));
  11. // 测试控制器 UsersController
  12. // echo (new UsersController($model,$view))->index();
  13. $model = new UsersModel;
  14. $view= new View("../app/views");
  15. $controller = new UsersController($model,$view);
  16. echo $controller->index();
  17. ?>

浏览地址:http://zhang.com/0520/zhang/public/index.php

可以通过设置网站根目录为0520/zhang/public这样预览地址:http://zhang.com/index.php

效果如下:

在入口文件中添加 .htaccess文件

  1. <IfModule mod_rewrite.c>
  2. Options +FollowSymlinks -Multiviews
  3. RewriteEngine On
  4. RewriteBase /
  5. RewriteRule ^index.php$ - [L]
  6. RewriteCond %{REQUEST_FILENAME} !-f
  7. RewriteCond %{REQUEST_FILENAME} !-d
  8. RewriteRule . /index.php [L]
  9. </IfModule>

这样浏览地址中就可以省略index.php 直接通过http://zhang.com/即可访问

三.封装动态分页案例

我们将之前用传统方式编写的动态分页案例改为MVC三层架构模式

模型层 models/StudentsModel.php

  1. <?php
  2. // 业务层模型类
  3. namespace app\models;
  4. // 引入并继承核心模型文件
  5. use core\Model;
  6. class StudentsModel extends Model
  7. {
  8. public function __construct(){
  9. parent::__construct();
  10. }
  11. }
  12. ?>

视图层 views/students/index.php

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>学生信息表</title>
  8. <link rel="stylesheet" href="/static/css/style.css">
  9. </head>
  10. <body>
  11. <table>
  12. <caption>学生信息表</caption>
  13. <thead>
  14. <tr>
  15. <td>学号</td>
  16. <td>姓名</td>
  17. <td>性别</td>
  18. <td>操作</td>
  19. </tr>
  20. </thead>
  21. <tbody>
  22. <?php foreach($students as $user):?>
  23. <tr>
  24. <td><?= $user['sno']?></td>
  25. <td><?= $user['sname']?></td>
  26. <td><?= $user['ssex']?></td>
  27. <td><button>删除</button><button>编辑</button></td>
  28. </tr>
  29. <?php endforeach;?>
  30. </tbody>
  31. </table>
  32. <?php
  33. //讨论省略点分页 前提: 两边的省略点都出现的时候
  34. //分页条显示的页数
  35. $showPages = 5;
  36. //分页条的开始页码值
  37. $startPage = 1;
  38. //分页条的结束页码值
  39. $endPage = $pages;
  40. //分页条的终止页码相对于当前页码的偏移量:
  41. $offset = ($showPages-1)/2;
  42. if($showPages < $pages)
  43. {
  44. if($page > $offset+1)
  45. {
  46. $startOmit = '...';
  47. $startPage = $page-$offset;
  48. $endPage = $page+$offset;
  49. if($endPage > $pages){$endPage=$pages;}
  50. }else{
  51. $startPage = 1;
  52. $endPage = $showPages;
  53. }
  54. if($showPages<$pages && $page + $offset < $pages)
  55. $endOmit = '...';
  56. }
  57. ?>
  58. <!-- 动态生成分页条 跳转地址 当前页码的高亮显示 -->
  59. <p class="page">
  60. <!-- 首页 上一页 -->
  61. <?php $prev=$page-1; if($page == 1) $prev = 1;?>
  62. <a href="<?=$page != 1?'?p=1':'javascript:;'?>" style="<?=$page==1?'cursor: no-drop;':'' ?>">首页</a>
  63. <a href="<?=$page != 1?'?p='.$prev:'javascript:;'?>" style="<?=$page==1?'cursor: no-drop;':'' ?>">上一页</a>
  64. <?php if(isset($startOmit)):?>
  65. <a href="javascript:;"><?=$startOmit?></a>
  66. <?endif?>
  67. <!-- 1 2 3 4 5 分页跳转页数字 -->
  68. <?php for ($i=$startPage; $i <=$endPage ; $i++) :?>
  69. <a class="<?=$i==$page?'active':''?>" href="<?='?p='.$i?>"><?=$i?></a>
  70. <? endfor;?>
  71. <?php if(isset($endOmit)):?>
  72. <a href="javascript:;">...</a>
  73. <?endif?>
  74. <!-- 下一页 尾页 -->
  75. <?php $next=$page+1; if($page == $pages) $next=$page;?>
  76. <a href="<?=$page != $pages?'?p='.$next:'javascript:;'?>" style="<?=$page==$pages?'cursor: no-drop;':'' ?>">下一页</a>
  77. <a href="<?=$page != $pages?'?p='.$pages:'javascript:;' ?>" style="<?=$page==$pages?'cursor: no-drop;':'' ?>">尾页</a>
  78. </p>
  79. </body>
  80. </html>

控制层 controllers/StudentsController.php

  1. <?php
  2. namespace app\controllers;
  3. class StudentsController
  4. {
  5. private $model=null;
  6. private $view=null;
  7. public function __construct($model,$view)
  8. {
  9. $this->model = $model;
  10. $this->view = $view;
  11. }
  12. public function index(){
  13. $num = 5;
  14. $page = empty($_GET['p'])?1:$_GET['p'];
  15. // 获取数据
  16. $offset = ($page-1)*$num;
  17. $students = $this->model->select('student',['sno','sname','ssex'],['LIMIT'=>[$offset,$num]]);
  18. // 获取总页数
  19. $total = $this->model->count('student');
  20. $pages = ceil($total/$num);
  21. return $this->view->render('students/index',['page'=>$page,'students'=>$students,'pages'=>$pages]);
  22. }
  23. }
  24. ?>

入口文件 public/index.html

  1. <?php
  2. // composer 自动加载器
  3. require __DIR__.'\\..\\vendor\\autoload.php';
  4. use app\controllers\StudentsController;
  5. use app\models\StudentsModel;
  6. use core\View;
  7. $model = new StudentsModel;
  8. $view= new View("../app/views");
  9. $controller = new StudentsController($model,$view);
  10. echo $controller->index();
  11. ?>

http://zhang.com/?p=6 这样显然不太合适,我们需要将参数改为http://zhang.com/p/6这种形式

控制器 controllers/StudentsController.php

  1. <?php
  2. namespace app\controllers;
  3. class StudentsController
  4. {
  5. private $model=null;
  6. private $view=null;
  7. public function __construct($model,$view)
  8. {
  9. $this->model = $model;
  10. $this->view = $view;
  11. }
  12. public function index($page=1,$num=3){
  13. // $num = 5;
  14. // 不在通过get参数?p=11这种方式使用page/11
  15. // $page = empty($_GET['p'])?1:$_GET['p'];
  16. // 获取数据
  17. $offset = ($page-1)*$num;
  18. $students = $this->model->select('student',['sno','sname','ssex'],['LIMIT'=>[$offset,$num]]);
  19. // 获取总页数
  20. $total = $this->model->count('student');
  21. $pages = ceil($total/$num);
  22. return $this->view->render('students/index',['page'=>$page,'students'=>$students,'pages'=>$pages]);
  23. }
  24. }
  25. ?>

我们在视图层 views/students/index.php 中也要修改对应的翻页按钮的href属性

举个例子 (?p=1)改为(/page/1)

  1. <a href="<?=$page!=1?'/page/1':'javascript:;'?>">首页</a>
  2. <a href="<?=$page!=1?'/page/'.$prev:'javascript:;'?>">上一页</a>

入口文件 public/index.php

  1. <?php
  2. // composer 自动加载器
  3. require __DIR__.'\\..\\vendor\\autoload.php';
  4. use app\controllers\StudentsController;
  5. use app\models\StudentsModel;
  6. use core\View;
  7. $model = new StudentsModel;
  8. $view= new View("../app/views");
  9. $controller = new StudentsController($model,$view);
  10. $params = [];
  11. if(isset($_SERVER['REDIRECT_URL'])){
  12. $res = array_values(array_filter(explode("/",$_SERVER['REDIRECT_URL'])));
  13. // $res = ['0'=>page,'1'=>3]
  14. $params[array_shift($res)] = array_shift($res);
  15. // $params = ['page'=>3];
  16. // var_dump($params);
  17. }
  18. $params['num'] = 5;
  19. print_r(call_user_func_array([$controller,'index'],$params));

Apache的重写规则

启用 Apache 的 URL 重写功能, 需要开启mod_rewrite模块. 然后在服务器配置文件或.htaccess中修改服务配置:

  1. AllowOverride all
  2. Options FollowSysLinks

服务器配置文件和.htaccess文件中都可以配置 URL 重写. 前者是服务器级别, 后者是目录级别.

更多重写规则:https://www.jianshu.com/p/6c103e426759

在 .htaccess 文件中配置重写规则

.htaccess文件中使用重写功能时, RewriteRule 负责匹配的 URI 是相对.htaccess所在的目录而言的.

例如访问 http://example.com/subdir1/subdir2/subdir3:

  • 如果.htaccess在网站根目录下, 那么RewriteRule捕获的 URI 是subdir1/subdir2/subdir3.
  • 如果.htaccess在 subdir1 目录下, RewriteRule捕获的 URI 是subdir2/subdir3.

RewriteRule重写 URI 后的基准目录也是以.htaccess所在的目录为准. 例如: 访问 http://example.com/foo

  1. RewriteRule ^foo$ bar.php [L]

如果.htaccess在根目录下, 重写后访问 http://example.com/bar.php. 如果在 subdir1 目录下, 重写后访问 http://example.com/subdir1/bar.php.

  1. <IfModule mod_rewrite.c>
  2. # 启用rewrite引擎
  3. RewriteEngine On
  4. # 重写规则: 匹配任意以htm后缀的文件, 将htm替换成php. ^(.*)\.htm$ 是一个正则表达式, 表示需要重写的部分, 此处指以任意字符开头, 以.htm结尾的部分. $1.php 是一个重写规则, $1 表示匹配到正则表达式中第一个子模式的字符串. [NC]: 表示重写规则如何应用, 该处表示不区分大小写. 整条规则即重写以任意字符开头, 以.htm结尾的部分, 重写为由匹配到的第一个子模式字符串和.php拼接成的字符串.
  5. RewriteRule ^(.*)\.htm$ $1.php [NC]
  6. </IfModule>
  1. <IfModule mod_rewrite.c>
  2. RewriteEngine On
  3. # 设置目录级重写的基准URI
  4. RewriteBase /subdir1/
  5. RewriteRule ^(.*)\.htm$ $1.php [NC,L,R]
  6. </IfModule>
  • RewriteBase设置了重写的基准目录. 如果上例中.htaccess位于网站根目录下, 访问的 http://example.com/foo.htm, 原本重写后的基准目录是网站根目录/, 设置了RewriteBase后变为/subdir1/, 重写后实际访问 http://example.com/subdir1/foo.php.
  • 规则标志L: 表示如果可以匹配本条规则, 则不再继续往下匹配.
  • 规则标志R: 表示临时重定向, 即 302, 相当于[R=302].
  1. <IfModule mod_rewrite.c>
  2. Options +FollowSymlinks -Multiviews
  3. RewriteEngine On
  4. RewriteBase /
  5. RewriteRule ^index.php$ - [L]
  6. RewriteCond %{REQUEST_FILENAME} !-f
  7. RewriteCond %{REQUEST_FILENAME} !-d
  8. RewriteRule . /index.php [L]
  9. </IfModule>

隐藏入口文件,重写后地址为http://zhang.com实际访问的地址为http://zhang.com/index.php

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议