Blogger Information
Blog 128
fans 9
comment 5
visits 241253
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
【自撸框架】PHP实战利用composer自撸一个MVC小框架
 一纸荒凉* Armani
Original
1039 people have browsed it

自撸 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

Correction status:Uncorrected

Teacher's comments:
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post