插件模型开发指南
1. 模型目录结构
插件的模型文件通常放在插件目录下的 models 目录中,按照功能模块进行组织。
1.1 目录结构示例
addons/
└─diandi_mall/
└─models/
├─goods/ # 商品相关模型
│ ├─Goods.php # 商品模型
│ └─GoodsSearch.php # 商品搜索模型
├─order/ # 订单相关模型
│ └─Order.php # 订单模型
└─user/ # 用户相关模型
└─User.php # 用户模型
2. 模型创建
2.1 基础模型
插件的模型应该继承系统提供的基础模型类,以获得统一的功能和特性。
2.2 模型示例
<?php
namespace addons\diandi_mall\models\goods;
use common\models\BaseModel;
/**
* 商品模型
*
* @property int $id 商品ID
* @property string $name 商品名称
* @property string $description 商品描述
* @property decimal $price 商品价格
* @property int $stock 商品库存
* @property int $status 商品状态
* @property int $created_at 创建时间
* @property int $updated_at 更新时间
*/
class Goods extends BaseModel
{
/**
* {@inheritdoc}
*/
public static function tableName()
{
return '{{%diandi_mall_goods}}';
}
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[['name', 'price', 'stock'], 'required'],
[['price'], 'number', 'min' => 0],
[['stock', 'status'], 'integer', 'min' => 0],
[['name'], 'string', 'max' => 255],
[['description'], 'string'],
];
}
/**
* {@inheritdoc}
*/
public function attributeLabels()
{
return [
'id' => '商品ID',
'name' => '商品名称',
'description' => '商品描述',
'price' => '商品价格',
'stock' => '商品库存',
'status' => '商品状态',
'created_at' => '创建时间',
'updated_at' => '更新时间',
];
}
}
3. 模型使用
3.1 查询数据
<?php
// 查询所有商品
$goodsList = Goods::find()->all();
// 条件查询
$goodsList = Goods::find()
->where(['status' => 1])
->andWhere(['>', 'price', 0])
->orderBy(['created_at' => SORT_DESC])
->all();
// 分页查询
$pagination = new Pagination([
'totalCount' => Goods::find()->count(),
'pageSize' => 10,
]);
$goodsList = Goods::find()
->offset($pagination->offset)
->limit($pagination->limit)
->all();
// 单条查询
$goods = Goods::findOne($id);
$goods = Goods::find()->where(['id' => $id])->one();
3.2 新增数据
<?php
$goods = new Goods();
$goods->name = '商品名称';
$goods->price = 99.99;
$goods->stock = 100;
$goods->status = 1;
if ($goods->save()) {
// 保存成功
} else {
// 保存失败
$errors = $goods->getErrors();
}
3.3 更新数据
<?php
// 方法一
$goods = Goods::findOne($id);
if ($goods) {
$goods->name = '新商品名称';
$goods->price = 199.99;
if ($goods->save()) {
// 更新成功
}
}
// 方法二
Goods::updateAll(
['name' => '新商品名称', 'price' => 199.99],
['id' => $id]
);
3.4 删除数据
<?php
// 方法一
$goods = Goods::findOne($id);
if ($goods) {
if ($goods->delete()) {
// 删除成功
}
}
// 方法二
Goods::deleteAll(['id' => $id]);
4. 模型关联
4.1 一对一关联
<?php
namespace addons\diandi_mall\models\goods;
use common\models\BaseModel;
use addons\diandi_mall\models\goods\GoodsDetail;
class Goods extends BaseModel
{
// ...
/**
* 获取商品详情
*/
public function getDetail()
{
return $this->hasOne(GoodsDetail::className(), ['goods_id' => 'id']);
}
}
4.2 一对多关联
<?php
namespace addons\diandi_mall\models\goods;
use common\models\BaseModel;
use addons\diandi_mall\models\goods\GoodsSku;
class Goods extends BaseModel
{
// ...
/**
* 获取商品SKU
*/
public function getSkus()
{
return $this->hasMany(GoodsSku::className(), ['goods_id' => 'id']);
}
}
4.3 多对多关联
<?php
namespace addons\diandi_mall\models\goods;
use common\models\BaseModel;
use addons\diandi_mall\models\category\Category;
class Goods extends BaseModel
{
// ...
/**
* 获取商品分类
*/
public function getCategories()
{
return $this->hasMany(Category::className(), ['id' => 'category_id'])
->viaTable('{{%diandi_mall_goods_category}}', ['goods_id' => 'id']);
}
}
5. 模型搜索
5.1 搜索模型
<?php
namespace addons\diandi_mall\models\goods;
use yii\base\Model;
use yii\data\ActiveDataProvider;
/**
* 商品搜索模型
*/
class GoodsSearch extends Goods
{
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[['id', 'stock', 'status'], 'integer'],
[['name', 'description'], 'safe'],
[['price'], 'number'],
];
}
/**
* {@inheritdoc}
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* @param array $params
*
* @return ActiveDataProvider
*/
public function search($params)
{
$query = Goods::find();
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
],
'sort' => [
'defaultOrder' => [
'created_at' => SORT_DESC,
],
],
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'id' => $this->id,
'price' => $this->price,
'stock' => $this->stock,
'status' => $this->status,
]);
$query->andFilterWhere(['like', 'name', $this->name])
->andFilterWhere(['like', 'description', $this->description]);
return $dataProvider;
}
}
5.2 搜索使用
<?php
use addons\diandi_mall\models\goods\GoodsSearch;
// 创建搜索模型
$searchModel = new GoodsSearch();
// 加载搜索参数
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
// 获取搜索结果
$goodsList = $dataProvider->getModels();
$totalCount = $dataProvider->getTotalCount();
6. 模型事件
6.1 事件定义
<?php
namespace addons\diandi_mall\models\goods;
use common\models\BaseModel;
use yii\base\Event;
class Goods extends BaseModel
{
// ...
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
// 绑定事件
$this->on(self::EVENT_BEFORE_INSERT, [$this, 'beforeInsert']);
$this->on(self::EVENT_BEFORE_UPDATE, [$this, 'beforeUpdate']);
$this->on(self::EVENT_AFTER_INSERT, [$this, 'afterInsert']);
$this->on(self::EVENT_AFTER_UPDATE, [$this, 'afterUpdate']);
}
/**
* 插入前事件
* @param Event $event
*/
public function beforeInsert($event)
{
// 插入前处理逻辑
}
/**
* 更新前事件
* @param Event $event
*/
public function beforeUpdate($event)
{
// 更新前处理逻辑
}
/**
* 插入后事件
* @param Event $event
*/
public function afterInsert($event)
{
// 插入后处理逻辑
}
/**
* 更新后事件
* @param Event $event
*/
public function afterUpdate($event)
{
// 更新后处理逻辑
}
}
7. 模型验证
7.1 验证规则
<?php
namespace addons\diandi_mall\models\goods;
use common\models\BaseModel;
class Goods extends BaseModel
{
// ...
/**
* {@inheritdoc}
*/
public function rules()
{
return [
// 必填字段
[['name', 'price', 'stock'], 'required'],
// 数值验证
[['price'], 'number', 'min' => 0],
[['stock', 'status'], 'integer', 'min' => 0],
// 字符串验证
[['name'], 'string', 'max' => 255],
[['description'], 'string'],
// 自定义验证
[['name'], 'validateName'],
// 条件验证
[['stock'], 'required', 'when' => function ($model) {
return $model->status == 1;
}, 'whenClient' => "function (attribute, value) {
return $('#goods-status').val() == 1;
}"],
];
}
/**
* 自定义验证方法
* @param string $attribute 属性名
* @param array $params 验证参数
*/
public function validateName($attribute, $params)
{
if (empty($this->$attribute)) {
$this->addError($attribute, '商品名称不能为空');
} elseif (strlen($this->$attribute) < 2) {
$this->addError($attribute, '商品名称至少2个字符');
}
}
}
7.2 验证使用
<?php
$goods = new Goods();
$goods->load(Yii::$app->request->post());
if ($goods->validate()) {
// 验证通过
if ($goods->save()) {
// 保存成功
}
} else {
// 验证失败
$errors = $goods->getErrors();
}
8. 常见问题
8.1 模型找不到表
可能原因:
- 表名配置错误
- 数据库中不存在对应表
- 插件未安装或SQL未执行
解决方案:
- 检查
tableName()方法返回的表名是否正确 - 确认数据库中存在对应表
- 重新安装插件,确保SQL脚本执行成功
8.2 模型关联失败
可能原因:
- 关联配置错误
- 外键约束不匹配
- 关联表不存在
解决方案:
- 检查关联方法的配置是否正确
- 确认外键约束是否匹配
- 确认关联表是否存在
8.3 模型验证失败
可能原因:
- 验证规则配置错误
- 输入数据不符合验证规则
- 自定义验证方法有问题
解决方案:
- 检查验证规则配置是否正确
- 确保输入数据符合验证规则
- 检查自定义验证方法的逻辑
9. 最佳实践
9.1 模型设计建议
- 表结构设计:合理设计表结构,避免冗余字段
- 索引优化:为常用查询字段添加索引
- 关联关系:合理使用模型关联,简化查询逻辑
- 验证规则:完善的验证规则,确保数据安全
- 事件处理:合理使用模型事件,处理业务逻辑
- 代码规范:遵循 PSR-2 代码风格规范
9.2 性能优化
- 延迟加载:使用延迟加载减少初始查询开销
- 预加载:使用预加载减少 N+1 查询问题
- 缓存使用:合理使用缓存,减少数据库查询
- 批量操作:使用批量操作减少数据库交互次数
- 查询优化:优化查询语句,减少不必要的字段查询
10. 示例代码
10.1 完整模型示例
<?php
namespace addons\diandi_mall\models\goods;
use common\models\BaseModel;
use yii\base\Event;
/**
* 商品模型
*
* @property int $id 商品ID
* @property string $name 商品名称
* @property string $description 商品描述
* @property decimal $price 商品价格
* @property int $stock 商品库存
* @property int $status 商品状态
* @property int $created_at 创建时间
* @property int $updated_at 更新时间
*/
class Goods extends BaseModel
{
const STATUS_ACTIVE = 1; // 激活状态
const STATUS_INACTIVE = 0; // inactive状态
/**
* {@inheritdoc}
*/
public static function tableName()
{
return '{{%diandi_mall_goods}}';
}
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[['name', 'price', 'stock'], 'required'],
[['price'], 'number', 'min' => 0],
[['stock', 'status'], 'integer', 'min' => 0],
[['name'], 'string', 'max' => 255],
[['description'], 'string'],
];
}
/**
* {@inheritdoc}
*/
public function attributeLabels()
{
return [
'id' => '商品ID',
'name' => '商品名称',
'description' => '商品描述',
'price' => '商品价格',
'stock' => '商品库存',
'status' => '商品状态',
'created_at' => '创建时间',
'updated_at' => '更新时间',
];
}
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
// 设置默认值
if ($this->isNewRecord) {
$this->status = self::STATUS_ACTIVE;
}
}
/**
* 获取商品状态文本
* @return string
*/
public function getStatusText()
{
$statuses = [
self::STATUS_ACTIVE => '激活',
self::STATUS_INACTIVE => 'inactive',
];
return $statuses[$this->status] ?? '未知';
}
/**
* 获取商品SKU
*/
public function getSkus()
{
return $this->hasMany(GoodsSku::className(), ['goods_id' => 'id']);
}
/**
* 获取商品分类
*/
public function getCategories()
{
return $this->hasMany(Category::className(), ['id' => 'category_id'])
->viaTable('{{%diandi_mall_goods_category}}', ['goods_id' => 'id']);
}
/**
* 插入前事件
* @param Event $event
*/
public function beforeInsert($event)
{
parent::beforeInsert($event);
// 插入前处理逻辑
}
/**
* 更新前事件
* @param Event $event
*/
public function beforeUpdate($event)
{
parent::beforeUpdate($event);
// 更新前处理逻辑
}
}
10.2 搜索模型示例
<?php
namespace addons\diandi_mall\models\goods;
use yii\base\Model;
use yii\data\ActiveDataProvider;
/**
* 商品搜索模型
*/
class GoodsSearch extends Goods
{
public $category_id;
public $start_price;
public $end_price;
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[['id', 'stock', 'status', 'category_id'], 'integer'],
[['name', 'description'], 'safe'],
[['price', 'start_price', 'end_price'], 'number'],
];
}
/**
* {@inheritdoc}
*/
public function scenarios()
{
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* @param array $params
*
* @return ActiveDataProvider
*/
public function search($params)
{
$query = Goods::find();
// 关联查询
$query->joinWith(['categories']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
],
'sort' => [
'defaultOrder' => [
'created_at' => SORT_DESC,
],
],
]);
$this->load($params);
if (!$this->validate()) {
return $dataProvider;
}
// 过滤条件
$query->andFilterWhere([
'id' => $this->id,
'stock' => $this->stock,
'status' => $this->status,
]);
// 分类过滤
if ($this->category_id) {
$query->andFilterWhere(['category.id' => $this->category_id]);
}
// 价格范围过滤
if ($this->start_price) {
$query->andFilterWhere(['>=', 'price', $this->start_price]);
}
if ($this->end_price) {
$query->andFilterWhere(['<=', 'price', $this->end_price]);
}
// 关键词过滤
$query->andFilterWhere(['like', 'name', $this->name])
->andFilterWhere(['like', 'description', $this->description]);
return $dataProvider;
}
}
