Service 标准示例 — Article
源文件:
server/app/service/article/ArticleService.php(生成代码必须模仿本示例的结构与风格)本示例为手写风格范本,用于学习结构与分层;经 make:crud 生成的骨架方法名以骨架为准,做业务增量时不要为对齐本示例而改名。
完整代码
php
<?php
/* ============================================================
* 项目:元点Admin
* 官网:https://www.dev007.cn
* Slogan:提供高质量行业系统源码,帮助中小企业快速搭建专属应用
* Author:mashanglai Team
* ============================================================ */
declare(strict_types=1);
namespace app\service\article;
use app\model\article\Article;
use app\repository\article\ArticleRepository;
use core\base\Service;
class ArticleService extends Service
{
protected ArticleRepository $articleRepository;
/**
* 获取文章列表(管理端)
*/
public function getArticleList(array $params): array
{
[$page, $limit] = $this->extractPagination($params);
return $this->articleRepository->getSearchList($params, $page, $limit);
}
/**
* 获取文章详情(带分类名称)
*/
public function getArticleDetail(int $id): ?array
{
$article = $this->articleRepository->findWithCategory($id);
if (!$article) {
$this->throwBusinessException(lang('business.record_not_found'));
}
// 递增浏览量
$this->articleRepository->incrementViewCount($id);
return $article;
}
/**
* 创建文章
*/
public function createArticle(array $data): array
{
// 如果状态为已发布,设置发布时间
if (isset($data['status']) && (int) $data['status'] === Article::STATUS_PUBLISHED) {
$data['publish_at'] = $data['publish_at'] ?? date('Y-m-d H:i:s');
}
$article = $this->articleRepository->create($data);
$this->trigger('article.created', [
'article_id' => $article['id'],
'title' => $data['title'],
]);
return $article;
}
/**
* 更新文章
*/
public function updateArticle(int $id, array $data): bool
{
$article = $this->findOrFail($this->articleRepository, $id);
// 如果从草稿变为已发布且没有发布时间,设置发布时间
if (isset($data['status']) && (int) $data['status'] === Article::STATUS_PUBLISHED
&& (int) $article['status'] === Article::STATUS_DRAFT) {
$data['publish_at'] = $data['publish_at'] ?? date('Y-m-d H:i:s');
}
return $this->articleRepository->update($id, $data);
}
/**
* 删除文章(软删除)
*/
public function deleteArticle(int $id): bool
{
$this->findOrFail($this->articleRepository, $id);
return $this->articleRepository->delete($id);
}
/**
* 更新文章状态
*/
public function updateStatus(int $id, int $status): bool
{
$article = $this->findOrFail($this->articleRepository, $id);
$updateData = ['status' => $status];
// 发布时设置发布时间
if ($status === Article::STATUS_PUBLISHED && (int) $article['status'] === Article::STATUS_DRAFT) {
$updateData['publish_at'] = date('Y-m-d H:i:s');
}
return $this->articleRepository->update($id, $updateData);
}
/**
* 获取已发布的文章列表(C端)
*/
public function getPublishedList(array $params): array
{
[$page, $limit] = $this->extractPagination($params);
$categoryId = (int) ($params['category_id'] ?? 0);
return $this->articleRepository->getPublishedList($page, $limit, $categoryId);
}
}要点注解
- 第 18 行
protected ArticleRepository $articleRepository;是唯一的依赖属性声明,由core\base\Service基类自动 DI 注入;Service 全程只调用$this->articleRepository->xxx(),禁止出现Db::table()、Article::where()/Article::find()/Article::create()等 Model 静态查询。 - 第 34、40 行
getArticleDetail()中所有数据访问(findWithCategory、incrementViewCount)都委托给 Repository 方法,Service 本身不拼装任何查询条件,只做"判空 → 抛异常 → 编排调用顺序"的业务流程控制。 - 第 36 行
$this->throwBusinessException(lang('business.record_not_found')):找不到记录时统一用基类提供的异常抛出方法 + 多语言键,不直接throw new Exception('xxx')。 - 第 70、86、95 行
$this->findOrFail($this->articleRepository, $id):更新/删除/改状态前先用基类通用方法校验记录存在,避免每个方法各自重复if (!$xxx) { throw ... }判断逻辑。 - 第 57-60 行
$this->trigger('article.created', [...]):创建成功后的副作用(如通知、日志、缓存清理)通过事件触发交给 Listener 处理,Service 内部不写内联的副作用代码(如直接记录操作日志)。 - 第 51、73-75、100 行状态相关判断使用
Article::STATUS_PUBLISHED/Article::STATUS_DRAFT常量而非魔法数字1/0,业务分支(发布时间自动填充)体现"从草稿到已发布"的状态机编排逻辑,属于 Service 该管的业务规则。 - 第 25、112 行
[$page, $limit] = $this->extractPagination($params);:分页参数提取统一走基类方法,不在 Service 内手写(int)($params['page_no'] ?? 1)等重复代码。 - 本文件全程没有
Db::startTrans()/commit()/rollback(),因为单表单步操作不需要事务;若涉及多表联动写入才需要在 Service 方法内包裹事务,事务边界只能出现在 Service 层,不能下沉到 Repository。