Skip to content

Repository 标准示例 — Article

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

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

完整代码

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

namespace app\repository\article;

use app\model\article\Article;
use core\base\Repository;
use think\Model;

class ArticleRepository extends Repository
{
    protected function getModel(): Model
    {
        return new Article();
    }

    /**
     * 获取模型实例(用于更新操作)
     */
    public function findModel(int $id): ?Article
    {
        return Article::find($id);
    }

    /**
     * 获取文章详情(带分类名称)
     */
    public function findWithCategory(int $id): ?array
    {
        $article = $this->model->with(['category'])->find($id);
        if (!$article) {
            return null;
        }
        $data = $article->toArray();
        $data['category_name'] = $data['category']['name'] ?? '';
        $data['views'] = $data['view_count'] ?? 0;
        unset($data['category']);
        return $data;
    }

    /**
     * 搜索文章列表(管理端,带分类名称)
     */
    public function getSearchList(array $params, int $page = 1, int $limit = 20): array
    {
        $query = $this->model->with(['category']);

        if (isset($params['status']) && $params['status'] !== '') {
            $query->where('status', '=', (int) $params['status']);
        }
        if (!empty($params['category_id'])) {
            $query->where('category_id', '=', (int) $params['category_id']);
        }
        if (!empty($params['keyword'])) {
            $query->where('title', 'like', "%{$params['keyword']}%");
        }

        $total = $query->count();
        $list = $query->page($page, $limit)->order('id desc')->select()->toArray();

        // 追加 category_name 字段
        foreach ($list as &$item) {
            $item['category_name'] = $item['category']['name'] ?? '';
            unset($item['category']);
        }

        return [
            'list' => $list,
            'pagination' => [
                'current_page' => $page,
                'per_page' => $limit,
                'total' => $total,
                'last_page' => (int) ceil($total / $limit),
            ],
        ];
    }

    /**
     * 获取已发布的文章列表(C端,带分类名称)
     */
    public function getPublishedList(int $page = 1, int $limit = 10, int $categoryId = 0): array
    {
        $query = $this->model->with(['category'])
            ->where('status', '=', Article::STATUS_PUBLISHED)
            ->where('publish_at', '<=', date('Y-m-d H:i:s'));

        if ($categoryId > 0) {
            $query->where('category_id', '=', $categoryId);
        }

        $total = $query->count();
        $list = $query->page($page, $limit)->order('id desc')->select()->toArray();

        foreach ($list as &$item) {
            $item['category_name'] = $item['category']['name'] ?? '';
            $item['views'] = $item['view_count'] ?? 0;
            unset($item['category']);
        }

        return [
            'list' => $list,
            'pagination' => [
                'current_page' => $page,
                'per_page' => $limit,
                'total' => $total,
                'last_page' => (int) ceil($total / $limit),
            ],
        ];
    }

    /**
     * 递增浏览量
     */
    public function incrementViewCount(int $id): void
    {
        $this->inc(['id' => $id], 'view_count');
    }
}

要点注解

  • Repository 是唯一接触 Model 的层:全文件中出现的 Article::find($id)(第 28 行)、$this->model->with(['category'])->find($id)(第 36 行)、$this->model->with(['category'])(第 52、89 行)等所有 ORM 查询都封装在这里,Controller/Service 都不允许直接写这类代码。
  • 第 18-21 行 protected function getModel(): Model { return new Article(); }core\base\Repository 要求子类实现该抽象方法,返回对应的 Model 实例,构造函数会用它初始化 $this->model,之后所有查询统一走 $this->model->xxx()
  • 查询方法命名体现意图:findWithCategory(单条 + 关联)、getSearchList(管理端条件搜索 + 分页)、getPublishedList(C 端已发布过滤 + 分页)、incrementViewCount(原子自增),方法名直接说明"查什么、给谁用",禁止用 getDataquery 等模糊命名。
  • 第 122 行 $this->inc(['id' => $id], 'view_count'); 复用基类 Repository::inc() 方法做字段自增,不在子类里手写 $this->model->where(...)->inc(...)->update()
  • 分页统一返回 ['list' => ..., 'pagination' => ['current_page', 'per_page', 'total', 'last_page']] 结构(第 73-81、106-114 行),与基类 buildPagination() 输出结构保持一致,Controller 侧的 paginate() 才能正确解析。
  • 软删除处理:本文件未显式过滤 deleted_at,因为 Article 继承的 core\base\Model 统一 use SoftDelete$deleteTime = 'deleted_at',ThinkPHP 的 find()/select()/where() 查询会自动排除已软删除记录,Repository 不需要手动拼 where('deleted_at', null)
  • 关联查询后手动 unset 关联字段(第 43 行 unset($data['category'])、第 70、103 行 unset($item['category']))而不是直接把嵌套的 category 对象返回给前端,把关联对象"拍平"成 category_name 字段,减少前端处理嵌套结构的成本。
  • findModel(int $id): ?Article(第 26-29 行)返回 Model 实例本身而非数组,用于需要继续调用 Model 方法(如保存前修改属性)的场景,与其他返回 array/?array 的查询方法形成对比,命名上以 findModel 区分"返回对象"还是"返回数组"。

基于 MIT 许可发布