店滴开发者手册店滴开发者手册
首页
指南
插件
接口
智能设备
element后台
SDK
首页
指南
插件
接口
智能设备
element后台
SDK
  • 插件

    • 插件开发指南
    • 后台接口
    • 插件API开发指南
    • 后端
    • 目录结构
    • 配置
    • 接口文档
    • 插件模型开发指南
    • 插件搜索功能开发指南
    • 命令行

插件模型开发指南

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 模型设计建议

  1. 表结构设计:合理设计表结构,避免冗余字段
  2. 索引优化:为常用查询字段添加索引
  3. 关联关系:合理使用模型关联,简化查询逻辑
  4. 验证规则:完善的验证规则,确保数据安全
  5. 事件处理:合理使用模型事件,处理业务逻辑
  6. 代码规范:遵循 PSR-2 代码风格规范

9.2 性能优化

  1. 延迟加载:使用延迟加载减少初始查询开销
  2. 预加载:使用预加载减少 N+1 查询问题
  3. 缓存使用:合理使用缓存,减少数据库查询
  4. 批量操作:使用批量操作减少数据库交互次数
  5. 查询优化:优化查询语句,减少不必要的字段查询

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;
    }
}
Prev
接口文档
Next
插件搜索功能开发指南