模型(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法,负责项目中的"数据+业务逻辑"。通过模型可以避免代码重复并实现更好的扩展性。
创建模型
模型存放在模块下的model目录下,模型为一个类,一个模型类就是一个php文件。
命名规范
- 模型类名:模型名称Model(首字母大写)
- 模型文件名:模型名称Model.php(首字母大写)
- 命名空间:app\模块\model
- 所有模型都继承于基础模型类:startmvc\core\Model
基本结构
// app/home/model/UserModel.php
namespace app\home\model;
use startmvc\core\Model;
class UserModel extends Model
{
// 定义数据表名
protected $table = 'users';
// 定义主键(可选,默认为id)
protected $pk = 'id';
// 自定义业务方法
public function getActiveUsers()
{
return $this->findAll('status = 1');
}
}实例化模型
在控制器中,可以通过多种方式实例化模型:
通过model()方法载入
namespace app\home\controller;
use startmvc\core\Controller;
class IndexController extends Controller
{
public function indexAction()
{
// 实例化当前模块的模型
$userModel = $this->model('User');
// 实例化指定模块的模型
$adminModel = $this->model('User', 'admin');
// 链式调用
$users = $this->model('User')->findAll();
}
}直接实例化
use app\home\model\UserModel;
$userModel = new UserModel();使用静态方法
use app\home\model\UserModel;
$userModel = UserModel::model();
// 临时设置表名
$logModel = UserModel::model('logs');
数据查询
查询单条记录
// 通过ID查询
$user = $userModel->find(5);
// 通过字符串条件查询
$user = $userModel->find('name = "张三"');
// 通过数组条件查询
$user = $userModel->find(['status' => 1, 'type' => 'vip']);
// 指定查询字段
$user = $userModel->find(5, 'id,name,email');查询多条记录
// 查询全部记录
$users = $userModel->findAll();
// 通过字符串条件查询
$users = $userModel->findAll('age > 18');
// 通过数组条件查询
$users = $userModel->findAll(['department' => '技术部']);
// 带排序
$users = $userModel->findAll([], '*', 'created_at DESC');
// 限制返回条数
$users = $userModel->findAll([], '*', '', 10);
// 带偏移量的分页
$users = $userModel->findAll([], '*', 'id DESC', '0,10'); // 从0开始,取10条条件查询示例
// 基础条件查询
$users = $userModel->findAll('status = 1');
// 使用比较运算符
$users = $userModel->findAll('age > 18 AND score >= 60');
// 使用IN条件
$users = $userModel->findAll('department IN ("技术部", "市场部")');
// 使用LIKE条件
$users = $userModel->findAll('name LIKE "%张%"');
// 使用BETWEEN条件
$users = $userModel->findAll('created_at BETWEEN "2023-01-01" AND "2023-12-31"');
// 复杂条件
$users = $userModel->findAll('(status = 1 AND age > 20) OR is_vip = 1');
// 涉及用户输入时,请使用参数绑定(? 占位符),严禁拼接到字符串条件中
$users = $userModel->where('age > ? AND status = ?', [intval($minAge), 1])->get();
安全提示:字符串条件属于原生SQL直通入口,仅适用于程序内写死的固定条件,严禁拼接用户输入(如 "age > {$min}")。涉及用户数据时请使用数组条件、参数绑定 where('age > ?', [$min]) 或链式 where('age', '>', $min)。
分页查询
use startmvc\core\Request;
// 设置分页参数
$page = intval(Request::get('page')) ?: 1;
$pageSize = 10;
// 执行分页查询
$result = $userModel->paginate($pageSize, $page, ['status' => 1], 'created_at DESC');
// 获取数据和分页信息
$users = $result['data'];
$pagination = $result['pagination'];
// 在视图中使用
// 数据列表:$users
// 总记录数:$pagination['total']
// 当前页:$pagination['current_page']
// 总页数:$pagination['total_pages']
// 是否有下一页:$pagination['has_more']数据操作
插入数据
// 单条插入(启用 $timestamps 后,created_at/updated_at 自动写入,无需手动赋值)
$userId = $userModel->insert([
'name' => '张三',
'email' => 'zhangsan@example.com',
'status' => 1,
]);
// 通过data方法设置数据并插入
$userId = $userModel->data([
'name' => '李四',
'email' => 'lisi@example.com'
])->insert();
// 批量插入(启用 $timestamps 后逐行自动补充时间戳)
$userModel->insert([
['name' => '张三', 'status' => 1],
['name' => '李四', 'status' => 1]
]);更新数据
// 通过ID更新
$result = $userModel->update(['name' => '张三(已修改)'], 5);
// 通过条件更新
$result = $userModel->update(
['status' => 0],
'last_login_at < "2023-01-01"'
);
// 通过数组条件更新
$result = $userModel->update(
['score' => 100],
['department' => '技术部', 'status' => 1]
);
// 链式操作更新
$result = $userModel->data(['name' => '王五', 'email' => 'wangwu@example.com'])
->update([], 5);保存数据(自动判断插入或更新)
// 插入新记录(返回自增ID)
$result = $userModel->save([
'name' => '张三',
'email' => 'zhangsan@example.com'
]);
// 更新已有记录(数据中含主键即走更新,主键不参与SET)
$result = $userModel->save([
'id' => 5,
'name' => '张三(已更新)',
'email' => 'zhangsan@example.com'
]);
// 链式调用
$userModel->data(['name' => '李四'])
->data(['email' => 'lisi@example.com'])
->save();删除数据
// 通过ID删除
$result = $userModel->delete(5);
// 通过条件删除
$result = $userModel->delete('status = 0 AND created_at < "2022-01-01"');
// 通过数组条件删除
$result = $userModel->delete(['status' => 0, 'is_deleted' => 1]);安全提示:不带条件的全表 update()/delete() 会被框架安全守卫拦截并抛出异常。清空表请使用 truncate();如确需全表更新/删除,请先调用 allowFullTable() 显式确认。
模型特性(StartMVC 2.9.1+)
以下ORM特性全部按需声明、默认关闭:未声明的模型行为与传统模式完全一致。启用后自动生效于 find()/findAll()/paginate() 及链式代理查询。
自动时间戳
class UserModel extends Model
{
protected $table = 'users';
protected $timestamps = true; // 开关
protected $createTime = 'created_at'; // 创建时间字段(设为null可单独禁用)
protected $updateTime = 'updated_at'; // 更新时间字段(设为null可单独禁用)
}insert() 自动写入 created_at 与 updated_at;update()/save() 自动刷新 updated_at;字段已显式赋值时不覆盖。批量插入同样逐行补充。
软删除
class UserModel extends Model
{
protected $table = 'users';
protected $softDelete = true; // 开关(布尔值)
protected $deleteTime = 'deleted_at'; // 软删除标记字段
}// delete() 自动改写为更新 deleted_at 标记,而非真实删除
$userModel->delete(5);
// find/findAll/paginate 及链式查询(where()->get()/count() 等)自动排除已删记录
$users = $userModel->findAll();
$count = $userModel->where('status', 1)->count();
// 查询范围:含已删记录
$userModel->withTrashed()->findAll();
// 查询范围:仅已删记录
$userModel->onlyTrashed()->findAll();
// 恢复软删除记录
$userModel->restore(5);
// 真实删除(绕过软删除)
$userModel->forceDelete(5);已删除的记录同样会被 update() 排除,防止误更新;restore() 会同时刷新 updated_at(如启用时间戳)。
字段类型转换
class UserModel extends Model
{
protected $table = 'users';
protected $casts = [
'price' => 'float',
'views' => 'int',
'is_admin' => 'bool',
'ext' => 'json', // 读取时自动 json_decode 为数组
];
}casts 在 find()/findAll()/paginate() 返回数据时生效;数组/对象写入时由 Db 层自动 JSON 编码、读出自动还原,双向对称。
关联(hasOne / belongsTo)
class UserModel extends Model
{
protected $table = 'users';
// 一对一:[属性名 => [关联模型, 关联表外键, 本表主键]]
protected $hasOne = [
'profile' => [ProfileModel::class, 'user_id', 'id'],
];
// 反向关联:[属性名 => [关联模型, 本表外键, 关联表主键]]
protected $belongsTo = [
'dept' => [DeptModel::class, 'dept_id', 'id'],
];
}// find/findAll/paginate 自动装载关联数据
$user = $userModel->find(5);
echo $user['profile']['bio']; // hasOne:个人资料
echo $user['dept']['title']; // belongsTo:所属部门
// 无关联记录时对应键为 null,结构始终一致
关联采用批量 IN 查询装载,无 N+1 问题;关联行应用关联模型自身的 casts 转换。
直接使用查询构造器
除了使用预定义的方法外,还可以直接使用底层查询构造器进行更灵活的操作:
// 基本查询
$users = $userModel->where('status', 1)
->order('created_at', 'DESC')
->limit(10)
->get();
// 聚合查询
$count = $userModel->where('department', '技术部')->count('id');
$maxScore = $userModel->where('status', 1)->max('score');
$avgAge = $userModel->where('gender', '男')->avg('age');
// 字段查询
$names = $userModel->where('status', 1)->column('name');
$email = $userModel->where('id', 5)->value('email');
// 原生SQL(query + 参数绑定,返回语句对象后取结果)
$rows = $userModel->query('SELECT * FROM users WHERE score > ? AND status = ?', [60, 1])
->fetchAll();
$one = $userModel->query('SELECT * FROM users WHERE id = ?', [5])->fetch();
事务处理
// 手动控制事务(transaction() 开始事务,支持嵌套,内层自动使用SAVEPOINT)
$userModel->transaction();
try {
$userId = $userModel->insert(['name' => '张三']);
$logModel->insert(['user_id' => $userId, 'action' => '注册']);
$userModel->commit();
return true;
} catch (Exception $e) {
$userModel->rollback();
return false;
}
表维护操作
// 优化表
$userModel->optimize();
// 分析表
$userModel->analyze();
// 检查表
$userModel->check();
// 修复表
$userModel->repair();
// 校验表
$userModel->checksum();
// 清空表(不可恢复,慎用)
$userModel->truncate();
// 删除表(不可恢复,慎用)
$userModel->drop();危险提示:truncate() 清空全表数据、drop() 删除整张表,均无需 WHERE 条件且不可恢复,请谨慎使用。
高级用法
自定义模型示例
namespace app\home\model;
use startmvc\core\Model;
use startmvc\core\Loader;
class UserModel extends Model
{
protected $table = 'users';
protected $pk = 'id';
// 启用软删除(布尔开关,标记字段用 $deleteTime 指定)
protected $softDelete = true;
protected $deleteTime = 'deleted_at';
// 启用时间戳
protected $timestamps = true;
protected $createTime = 'created_at';
protected $updateTime = 'updated_at';
// 获取激活用户
public function getActiveUsers()
{
return $this->findAll('status = 1');
}
// 获取用户信息与订单(模型内调用其他模型请用 Loader 或 new 实例化)
public function getUserWithOrders($userId)
{
$user = $this->find($userId);
if ($user) {
$orderModel = Loader::getInstance(OrderModel::class);
$user['orders'] = $orderModel->findAll(['user_id' => $userId]);
}
return $user;
}
// 修改用户状态
public function changeStatus($userId, $status)
{
return $this->update(['status' => $status], $userId);
}
}
软删除的恢复无需自行实现,直接使用模型原生方法:$userModel->restore($userId)。
在控制器中使用
namespace app\home\controller;
use startmvc\core\Controller;
use startmvc\core\Request;
class UserController extends Controller
{
public function indexAction()
{
$userModel = $this->model('User');
$users = $userModel->findAll();
$this->display('index', ['users' => $users]);
}
public function viewAction($id)
{
$userModel = $this->model('User');
$user = $userModel->getUserWithOrders($id);
$this->display('view', ['user' => $user]);
}
public function createAction()
{
if (Request::isPost()) {
$data = Request::post();
$userModel = $this->model('User');
$userId = $userModel->insert($data);
if ($userId) {
$this->redirect('index');
}
}
$this->display('create');
}
public function updateAction($id)
{
$userModel = $this->model('User');
if (Request::isPost()) {
$data = Request::post();
$result = $userModel->update($data, $id);
if ($result) {
$this->redirect('index');
}
}
$user = $userModel->find($id);
$this->display('update', ['user' => $user]);
}
public function deleteAction($id)
{
$userModel = $this->model('User');
$userModel->delete($id);
$this->redirect('index');
}
}总结
StartMVC的模型系统提供了丰富而灵活的数据操作方法:从基本的CRUD操作、链式查询构建、事务处理,到自动时间戳、软删除、字段类型转换与关联装载等ORM特性(2.9.1+),都有简洁直观的API接口。通过合理使用模型,可以有效组织业务逻辑,提高代码复用率,使应用程序更加易于维护和扩展。