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(原子自增),方法名直接说明"查什么、给谁用",禁止用getData、query等模糊命名。 - 第 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区分"返回对象"还是"返回数组"。