service类职责必须单一,仅协调有状态变更或跨边界交互的核心流程,如userregistrationservice只负责校验→创建→触发事件;工具方法、http细节、生命周期钩子等均应剥离。

Service 类职责必须单一,别塞进所有业务逻辑
很多人把 Service 类当成“垃圾桶”,注册、发邮件、写日志、调第三方 API 全堆进去,结果一个 UserServiceImpl 超过 800 行,改个密码逻辑要翻 20 分钟。这不是封装,是藏匿。
真正可维护的 Service 类只做一件事:协调当前领域内**有状态变更或跨边界交互**的核心流程。比如 UserRegistrationService 只负责「校验 → 创建用户 → 触发注册事件」,不处理邮件发送本身(那是 EmailSender 的事),也不管密码加密细节(交给 PasswordHasher)。
- 判断标准:如果抽掉这个类,某条完整业务线(如“用户注册成功后发欢迎邮件”)会断掉,那它才该属于这个 Service
- 拒绝“工具方法”:
formatPhone()、generateToken()这类纯函数不该出现在 Service 类里,放Support\StringHelper或独立 value object 中更合适 - 避免继承链:别搞
BaseUserService→AdminUserService→ApiUserService,用组合(注入不同策略)替代继承
依赖用接口注入,别在构造函数里 new 具体类
常见错误是 Service 构造函数直接 new HttpClient() 或 new DatabaseConnection(),导致无法 mock、无法切换实现、单元测试写不下去。
正确做法是声明依赖接口,并通过构造函数注入:
class UserRegistrationService
{
public function __construct(
private UserRepository $userRepository,
private EmailSenderInterface $emailSender,
private PasswordHasherInterface $hasher,
private EventDispatcherInterface $dispatcher
) {}
}
- 每个依赖对应一个清晰契约(interface),哪怕一开始只有 1 个实现,也先定义接口
- 不要为“方便测试”而加 setter 注入——它破坏不可变性,且让类状态变得模糊
- 警惕“伪依赖”:像
LoggerInterface或CacheInterface是横切关注点,应通过框架容器自动注入,别手动传参
方法粒度要小,但别过度拆分到“函数即服务”
有人走向另一个极端:把每个原子操作都拆成单独方法,比如 validateEmailFormat()、checkEmailUniqueness()、saveUserEntity()……然后主流程变成 10 行调用,看着清爽,实则把控制流散得满地都是,阅读成本反而更高。
合理粒度是:一个 public 方法 = 一条明确的业务动词 + 完整上下文。例如:
- ✅
register(array $input): User—— 输入、校验、创建、返回,一气呵成 - ❌
doRegisterStep1Then2And3(...)—— 暴露内部步骤,破坏封装 - ❌
createUserFromInput(...)+sendWelcomeEmail(...)+logRegistration(...)全部 public —— 外部可随意调用中间态,破坏一致性
私有方法可以封装子逻辑,但仅限于当前 Service 内部协作,不对外暴露语义。
别在 Service 里处理 HTTP 请求/响应细节
看到 Request $request 出现在 Service 构造函数或方法参数里,基本就能判定设计已偏航。Service 层必须与传输层解耦。
控制器才是解析 Request 的地方,它应该把结构化数据(数组、DTO、value object)传给 Service:
// Controller
public function store(Request $request)
{
$data = [
'name' => $request->string('name'),
'email' => $request->email('email'),
'password' => $request->string('password'),
];
$user = $this->userRegistrationService->register($data);
return response()->json(['id' => $user->id]);
}
- Service 方法参数类型应是 domain-level 的,比如
UserRegistrationData、array、string,绝不是Request、Response、Session - 如果 Service 需要“当前用户”,别传
Auth::user(),而是传UserId或UserContext这种轻量、无副作用的值对象 - 异常也需抽象:抛
UserAlreadyExistsException,而不是HttpException或ValidationException
最易被忽略的一点:Service 类不该有生命周期感知(比如 onKernelTerminate、onCommandFinish)。那些钩子逻辑属于事件监听器或中间件,混进来只会让类越来越重、越来越难测。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











