Skip to content

Model 标准示例 — Article

源文件:server/app/model/article/Article.php(生成代码必须模仿本示例的结构与风格)

本示例为手写风格范本,用于学习结构与分层;经 make:crud 生成的骨架方法名以骨架为准,做业务增量时不要为对齐本示例而改名。

完整代码

php
<?php
/* ============================================================
 * 项目:元点Admin
 * 官网:https://www.dev007.cn
 * Slogan:提供高质量行业系统源码,帮助中小企业快速搭建专属应用
 * Author:mashanglai Team
 * ============================================================ */
declare(strict_types=1);

namespace app\model\article;

use core\base\Model;

class Article extends Model
{
    protected $name = 'articles';

    protected $fillable = [
        'category_id', 'title', 'cover', 'summary', 'content',
        'tags', 'author', 'view_count', 'status', 'publish_at', 'admin_id',
    ];

    // 状态常量
    const STATUS_DRAFT     = 0;
    const STATUS_PUBLISHED = 1;

    protected $type = [
        'category_id' => 'integer',
        'status'      => 'integer',
        'admin_id'    => 'integer',
        'view_count'  => 'integer',
        'tags'        => 'json',
    ];

    protected $append = ['status_text'];

    /**
     * 所属分类
     */
    public function category(): \think\model\relation\BelongsTo
    {
        return $this->belongsTo(ArticleCategory::class, 'category_id', 'id');
    }

    /**
     * 状态文本获取器
     */
    public function getStatusTextAttr($value, $data): string
    {
        $statusMap = [
            self::STATUS_DRAFT     => '草稿',
            self::STATUS_PUBLISHED => '已发布',
        ];
        return $this->getStatusText((int) ($data['status'] ?? 0), $statusMap);
    }
}

要点注解

  • 第 14 行 class Article extends Modelcore\base\Model):核心基类已统一 use SoftDelete$deleteTime = 'deleted_at'$createTime = 'created_at'$updateTime = 'updated_at'$autoWriteTimestamp = true,子类 Model 不需要也不应该重复声明这些时间戳字段配置,直接继承即可。
  • 第 35 行 protected $append = ['status_text']; 与第 48-55 行 getStatusTextAttr($value, $data) 成对出现:定义了 getXxxAttr() 访问器就必须同时在 $append 数组里登记对应的下划线字段名(status_text),否则该访问器不会出现在 toArray()/toJson() 输出中,前端拿不到这个字段。
  • 第 48-55 行访问器内部调用 $this->getStatusText((int) ($data['status'] ?? 0), $statusMap) 复用了 core\base\Model 提供的 getStatusText() 辅助方法,而不是自己手写 $statusMap[$status] ?? '未知' 的映射逻辑;基类还提供了默认的 getStatusTextAttr() 实现(1=启用/0=禁用),本类因为语义是"草稿/已发布"而非通用启禁用,所以在子类中 override 了这个方法,符合基类注释里"若业务语义不同则子类 override"的约定。
  • 第 40-43 行 category(): \think\model\relation\BelongsTo 定义关联关系,方法名用小写单数业务名词(category)而非 getCategory,返回类型显式声明为 \think\model\relation\BelongsTobelongsTo(ArticleCategory::class, 'category_id', 'id') 显式传入外键与主键字段名,不依赖框架的默认推断。
  • 第 16 行 protected $name = 'articles'; 显式声明表名(复数形式,无表前缀),与类名 Article(单数)区分,避免依赖 ThinkPHP 默认的类名转表名推断规则。
  • 第 18-21 行 protected $fillable = [...] 白名单式声明允许批量赋值的字段,未列出的字段(如 idcreated_at)不会被 create()/save() 批量写入,防止越权字段注入。
  • 第 27-33 行 protected $type = [...] 声明字段类型转换,其中 'tags' => 'json' 触发基类 getJsonAttr()/setJsonAttr()(在 core\base\Model 中定义)自动做 JSON 编解码,Model 层不需要在访问器里手写 json_decode/json_encode
  • 第 24-25 行 const STATUS_DRAFT = 0; const STATUS_PUBLISHED = 1; 在 Model 中定义状态常量,供 Service/Repository 层通过 Article::STATUS_PUBLISHED 引用,避免在业务代码中出现魔法数字。

基于 MIT 许可发布