1.独立完成一个迷你的MVC框架
目录概览:
1. config目录里dbconfig.php
<?php
$db_params = array(
'charset' => 'utf8',
'port' => 3306,
'type' => 'mysql',
'host' => '127.0.0.1',
'user' => 'root',
'pwd' => 'root',
'dbname' => 'SqlTest'
);
return $db;
此目录里专门放一些配置文件
2.1 model目录里db.php
namespace homework\model;
use PDO;
# 单例模式
class Db{
# 实例
private static $instance;
# pdo连接
private $pdo = null;
# 数据表名
private $table = '';
# 字段列表
private $field = '';
# 查询条件
private $where = '';
# 排序条件
private $order = '';
# 新增/修改条件
private $data = '';
# execute条件
private $exe = [];
# 显示数量
private $limit = 0;
private function __construct( $db ){
$dsn = "mysql:host={$db['host']};dbname={$db['dbname']};charset={$db['charset']}";
try {
$this->pdo = new PDO( $dsn, $db['user'], $db['pwd'] );
} catch (\PDOException $e) {
die('数据库错误:'.$e->getMessage());
}
}
# 获取实例
public static function getInstance( $db ){
# 因这里是单例,没有必要用后期绑定static
if( !(self::$instance instanceof self) ){
self::$instance = new self( $db );
}
return self::$instance;
}
# 禁用克隆方法
private function __clone(){
}
# 获取表名
public function table( $table ){
$this->table = '`' .$table. '`';
# 返回当前对象,便于链式调用该对象的其它方法
return $this;
}
# 设查询字段
public function field( $fields ){
# 若参数是数组,['id','user_name'],拆开拼接
# 若是字符串,'id, user_name' 直接拼接
if( is_array($fields) ){
foreach( $fields as $field ){
$this->field .= '`' .$field. '`, ';
}
# 去掉多余逗号
$this->field = rtrim( trim($this->field),',' );
}
else{
$this->field = $fields;
}
return $this;
}
# 设查询条件 (参数个数不定,将参数压到 $where 里)
public function where( ...$where ){
$w = $this->conditionHandler( $where, $condition =' AND ');
if( empty($this->where) ){
$this->where = ltrim( trim($w),$condition );
}
else{
$this->where .= $w;
}
return $this;
}
# 设查询条件 (参数个数不定,将参数压到 $where 里)
public function whereOr( ...$where ){
$w = $this->conditionHandler( $where, $condition =' OR ');
if( empty($this->where) ){
$this->where = ltrim( trim($w),$condition );
}
else{
$this->where .= $w;
}
return $this;
}
# 设排序条件 (参数个数不定,将参数压到 $order 里)
public function order( ...$order ){
# [ 'id','desc' ]
# [ 'id' ] / [ 'id desc' ]
if( count($order)>1 ){
$this->order = '`' .$order[0]. '` '.$order[1];
}
else{
$arr = explode( ' ',trim($order[0]));
if( count( $arr )==1 ){
$this->order = '`' .$order[0]. '` asc';
}
else{
$this->order = '`' .$arr[0]. '` '.$arr[1];
}
}
return $this;
}
# 设显示数量
public function limit( $limit ){
$this->limit = intval( $limit );
return $this;
}
/**
* @param $where 条件: [ ['id'=>1] ] 或 [ 'id', 1 ]
* @param $condition 拼接关键词: and 或 or
* @return string
*/
private function conditionHandler( $where, $condition ){
$w = '';
# 传的数组 (多用于同时多个条件),分2种,[ 'id'=>1 ] / [ 'id'=>['>',1 ] ]
# 传的不是数组 (多用于一个条件),'id', 1 / 'id', '>', 1
if( is_array($where[0]) ){
foreach( $where[0] as $k => $v ){
$w .= $condition;
if( is_array($v) ){
$w .= '`' .$k. '`' .$v[0]. ':' .$k;
$this->exe[ $k ] = $v[1];
}
else{
$w .= '`' .$k. '`=:' .$k;
$this->exe[ $k ] = $v;
}
}
}
elseif( is_string($where[0]) ){
$w .= $condition;
if( count($where)==3 ){
$w .= '`' .$where[0]. '`' .$where[1]. ':' .$where[0];
$this->exe[ $where[0] ] = $where[2];
}
elseif( count($where)==2 ){
$w .= '`' .$where[0]. '`=:' .$where[0];
$this->exe[ $where[0] ] = $where[1];
}
}
return $w;
}
# 新增/修改数据条件
private function data( $where ){
$w = '';
foreach( $where as $k => $v ){
$w .= ', `' .$k. '`=:' .$k;
$this->exe[ $k ] = $v;
}
if( empty($this->data) ){
$this->data = ltrim( trim($w), ',' );
}
else{
$this->data .= $w;
}
return $this->data;
}
# 查询结果
public function find( $multi=false ){
$fields = empty($this->field) ? '*' : $this->field;
$where = empty($this->where) ? '' : ' WHERE '.$this->where;
$order = empty($this->order) ? '' : ' ORDER BY '.$this->order;
$limit = empty($this->limit) ? '' : ' LIMIT '.$this->limit;
# 接装SQL语句
$sql = 'SELECT '.$fields.' FROM '.$this->table. $where .$order. $limit;
# 预处理查询
$stmt = $this->pdo->prepare($sql);
$stmt->execute($this->exe);
if( $multi ){
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else{
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}
# 查询结果
public function select(){
return $this->find(true);
}
public function get( $obj,$multi=false ){
$fields = empty($this->field) ? '*' : $this->field;
$where = empty($this->where) ? '' : ' WHERE '.$this->where;
$order = empty($this->order) ? '' : ' ORDER BY '.$this->order;
$limit = empty($this->limit) ? '' : ' LIMIT '.$this->limit;
# 接装SQL语句
$sql = 'SELECT '.$fields.' FROM '.$this->table. $where .$order. $limit;
# 预处理查询
$stmt = $this->pdo->prepare($sql);
$stmt->setFetchMode(PDO::FETCH_CLASS, $obj );
$stmt->execute($this->exe);
if( $multi ){
return $stmt->fetchAll();
}
else{
return $stmt->fetch();
}
}
public function getAll( $obj ){
return $this->get( $obj,true);
}
# 插入数据
public function insert( $where ){
$this->data = $this->data( $where );
$data = ' SET '.$this->data;
# 接装SQL语句
$sql = 'INSERT INTO ' .$this->table. $data;
# 预处理查询
$stmt = $this->pdo->prepare($sql);
$stmt->execute($this->exe);
if( $stmt->rowCount()>0 ){
echo '插入成功';
}
}
# 更新数据
public function update( $where ){
$this->data = $this->data( $where );
$data = ' SET '.$this->data;
$where = empty($this->where) ? '' : ' WHERE '.$this->where;
# 接装SQL语句
$sql = 'UPDATE ' .$this->table. $data . $where;
# 预处理查询
$stmt = $this->pdo->prepare($sql);
$stmt->execute($this->exe);
if( $stmt->rowCount()>0 ){
echo '更新成功';
}
}
# 更新数据
public function delete(){
$where = empty($this->where) ? '' : ' WHERE '.$this->where;
# 接装SQL语句
$sql = 'DELETE FROM ' .$this->table. $where;
# 预处理查询
$stmt = $this->pdo->prepare($sql);
$stmt->execute($this->exe);
if( $stmt->rowCount()>0 ){
echo '删除成功';
}
}
# 数据数量
public function count(){
$where = empty($this->where) ? '' : ' WHERE '.$this->where;
# 接装SQL语句
$sql = 'SELECT count(*) as count FROM ' .$this->table. $where;
# 预处理查询
$stmt = $this->pdo->prepare($sql);
$stmt->execute($this->exe);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row['count'];
}
}
这里使用单例,查询获取时采用链式调用,主要进行数据库的增删改查
2.2 model目录里ZsgcModel.php
namespace homework\model;
class ZsgcModel{
private $id;
private $user_name;
private $pwd;
private $email;
private $sex;
private $des;
private $hobby;
private $create_time;
private $update_time;
private $status;
# 属性重载
public function __get( $name ){
# 自行需要进行一些验证
return $this->$name;
}
public function __set($name, $value)
{
$this->$name = $value;
}
public function __construct()
{
# 设置属性值的自动转换
$this->create_time = $this->create_time ? date('Y/m/d', $this->create_time): null;
$this->update_time = $this->update_time ? date('Y/m/d', $this->update_time): null;
$this->sex = $this->sex ? ((intval($this->sex)-1) ? '女':'男') : '未知';
$this->status = $this->status ? '正常' : '异常';
}
}
这里是模型与数据表之间的映射,自动转换处理一些特定字段
3.1 view目录里iView.php
namespace homework\view;
# 视图接口
interface iView{
public function fetch( ...$params );
}
这里是视图接口,后续继承此接口的实例,都需实现fetch方法
3.2 view目录里ZsgcView.php
namespace homework\view;
class ZsgcView implements iView {
public function fetch( ...$params ){
$fields = [];
# 是否传第二个参数$fields
if( count($params)>1 ) {
# 数组格式还是string格式
if (is_array($params[1])) {
$fields = $params[1];
}
elseif (is_string($params[1])) {
$arr = explode(',', trim($params[1]));
for ($i = 0; $i < count($arr); $i++) {
array_push($fields, trim($arr[$i]));
}
}
}
$head = '<table>';
$head .= '<caption>Zsgc用户表</caption>';
$body = '';
$flag = true; # 是否进入标题switch
# 拼接 table
if( $fields ){
foreach( $params[0] as $user ){
$body .= '<tr>';
foreach( $fields as $v ){
if( $v =='pwd' ) continue; # 不允许显示pwd
if( $flag ){
switch( $v ){
case 'id': $head .= '<th>ID</th>';break;
case 'user_name': $head .= '<th>用户名</th>'; break;
case 'email': $head .= '<th>邮箱</th>'; break;
case 'hobby': $head .= '<th>爱好</th>'; break;
case 'des': $head .= '<th>描述</th>'; break;
case 'sex': $head .= '<th>性别</th>'; break;
case 'status': $head .= '<th>状态</th>'; break;
case 'create_time': $head .= '<th>添加时间</th>'; break;
case 'update_time': $head .= '<th>更新时间</th>'; break;
}
}
if( is_object($user) ){
$body .= '<td>' .$user->$v. '</td>';
}
else{
$body .= '<td>' .$user[$v]. '</td>';
}
}
$body .= '</tr>';
$flag = false; # 第一遍循环后就可以关掉了
}
}
else{
$head .= '<tr><th>ID</th><th>用户名</th><th>邮箱</th><th>爱好</th><th>描述</th>';
$head .= '<th>性别</th><th>状态</th><th>添加时间</th><th>更新时间</th></tr>';
if( is_object( $params[0][0] ) ){
foreach( $params[0] as $user ) {
$body .= '<tr>';
$body .= '<td>' .$user->id. '</td>';
$body .= '<td>' .$user->user_name. '</td>';
$body .= '<td>' .$user->email. '</td>';
$body .= '<td>' .$user->hobby. '</td>';
$body .= '<td>' .$user->des. '</td>';
$body .= '<td>' .$user->sex. '</td>';
$body .= '<td>' .$user->status. '</td>';
$body .= '<td>' .$user->create_time. '</td>';
$body .= '<td>' .$user->update_time. '</td>';
$body .= '</tr>';
}
}
else{
foreach( $params[0] as $user ) {
$body .= '<tr>';
$body .= '<td>' .$user['id']. '</td>';
$body .= '<td>' .$user['user_name']. '</td>';
$body .= '<td>' .$user['email']. '</td>';
$body .= '<td>' .$user['hobby']. '</td>';
$body .= '<td>' .$user['des']. '</td>';
$body .= '<td>' .$user['sex']. '</td>';
$body .= '<td>' .$user['status']. '</td>';
$body .= '<td>' .$user['create_time']. '</td>';
$body .= '<td>' .$user['update_time']. '</td>';
$body .= '</tr>';
}
}
}
$body .= '</table>';
return $head.$body;
}
}
echo '<style>
table {border-collapse: collapse; border: 1px solid; width: auto; height: 150px; }
caption {font-size: 1.2rem; margin-bottom: 10px; }
tr:first-of-type {background-color: lightblue; }
td,th {border: 1px solid}
td:first-of-type {text-align: center}
</style>';
1)这里参数,第一个为数据,第二个为列fields [可选]
2)若第二个参数没传,默认展示所有列数据,若传了,则展示fields里指定的列数据
3)第一个参数为数组,其第二层数据分为数组和object两种:
若为数组:是PDO获取时没有setFetchMode,直接返回数据库里原样数据
若为Object,是PDO获取时设置了setFetchMode为ZsgcModel类,会自动转换一些字段
4 controller目录里ZsgcController.php
namespace homework\controller;
use homework\model\Db;
use homework\view\iView;
use homework\model\ZsgcModel;
class ZsgcController{
private $db;
private $view;
public function __construct(Db $db, iView $view){
$this->db = $db;
$this->view = $view;
}
public function index(){
# 两种表达方式都可以
//$fields = ['id','user_name','email','hobby'];
$fields = ' id , user_name , email,hobby';
# 获取的是数组
$data = $this->db->table('zsgc')
->field( $fields )
->where('id','>=',35)
->whereOr('email','!=','')
->select();
return $this->view->fetch( $data, $fields );
// $data = $this->db->table('zsgc')
// ->where('id','>=',35)
// ->whereOr('email','!=','')
// ->select();
//
// return $this->view->fetch( $data );
# 获取的是object
// $data = $this->db->table('zsgc')
// ->field( $fields )
// ->where('id','>=',35)
// ->whereOr('email','!=','')
// ->getAll(ZsgcModel::class);
//
// return $this->view->fetch( $data, $fields );
// $data = $this->db->table('zsgc')
// ->where('id','>=',35)
// ->whereOr('email','!=','')
// ->getAll(ZsgcModel::class);
//
// return $this->view->fetch( $data );
}
}
1)这里仅举例了查询类的几种情况,可自行在此控制器里增加其他方法,写入新增/更新/删除等情况,客户端再指定调用即可
2)这里控制器需注入两对象,一个是Db对象,用来获取数据;一个是接口对象即视图对象,用来展示数据
5 自动加载autoload.php
spl_autoload_register( function ($className){
$path = str_replace('\\', DIRECTORY_SEPARATOR, $className);
require dirname(__DIR__) . DIRECTORY_SEPARATOR . $path . '.php';
});
自动加载 是让命名空间的路径和文件所在路径一致
6 容器层Container.php
namespace homework;
use Closure;
require __DIR__ . '/autoload.php';
# 服务容器
class Container{
# 实例数组
public $instance = [];
# 闭包数组
public $binds = [];
# 将实例/闭包绑定到对应的数组中
# $alias 别名,$concrete 实例/闭包
public function bind( $alias, $concrete ){
# 编码约定,它的构造方法第一个参数必须是容器
if( $concrete instanceof Closure){
$this->binds[$alias] = $concrete;
}
else{
$this->instance[$alias] = $concrete;
}
}
# 将实例/闭包从容器中取出来
# $alias 别名,$parameters 依赖对象
public function make($alias,$parameters = [] ){
if( isset($this->instance[$alias]) ){
return $this->instance[$alias];
}
# 用容器来创建所依赖的对象/类实例
# $this 是当前容器对象, 将$this插入到$parameters里最前面
array_unshift( $parameters, $this );
# 执行闭包,自动生成所依赖的外部对象/类实例
# 执行 $this->binds[$alias]数组里对应的闭包,并传参数 $parameters
return call_user_func_array( $this->binds[$alias], $parameters );
}
}
1)这里使用容器绑定,所有实例通过容器统一处理
2)先通过bind方法将对应实例/闭包方法放到实例或闭包数组中,且指定key为自定义别名
3)待执行make方法时,通过回调方法call_user_func_array,执行闭包数组里指定key的闭包,并传入参数
4)若为依赖对象,参数只有一个为容器对象;若为调用类,除容器对象,还会有依赖对象参数
7 客户端index.php
use homework\model\Db;
use homework\view\ZsgcView;
use homework\controller\ZsgcController;
use homework\Container;
require __DIR__. '/config/dbconfig.php';
require __DIR__. '/autoload.php';
# 客户端调用
# 实例化容器类
$container = new Container();
# 闭包里没用到$container,也可以不传$container
# 这里用到配置文件里的参数,用use传入
$container->bind('Db', function (Container $container) use ($db_params) {
return Db::getInstance( $db_params );
});
$container->bind('ZsgcView', function (Container $container) {
return new ZsgcView();
});
# 调用类,肯定会传依赖对象,这里有两个依赖对象 $db, $view
# 实例化ZsgcController,需注入两参数,而该两参数,也由make方法获取 (通过回调方法执行对应的闭包)
$container->bind( 'ZsgcController', function( Container $container, $db, $view ){
return new ZsgcController( $container->make( $db ), $container->make( $view ) );
} );
$zsgc = $container->make('ZsgcController', ['Db','ZsgcView']);
echo $zsgc->index();
这里使用容器的控制反转,除了绑定依赖对象,调用类也绑定到其中,统一处理
8 运行( 控制器里只写了查询方法,所以展示的都是查询小案例, 代码在Zsgc控制器里 )
case1:数据库原样数据,指定列
case2:数据库原样数据,不指定列
case3:通过模型转换过一些字段数据,指定列
case4:通过模型转换过一些字段数据,不指定列
小结:老师给的要求很宽泛,知识点单个拎出来也能明白,就是感觉这也可能组合,那也可能组合,反而不知到从何处下手,拖拖拉拉好几天,就是不想动手,拖到最后没办法了,硬着头皮做,开始思绪还是比较杂的,后面理着理着就顺了,mvc小框架就先到这吧,还是赶紧去补剩下的作业了