Laravel中避免向Service构造函数传递null的优雅方案
Laravel服务类构造函数传null的优雅解决方案
问题背景
在Laravel的Controller中调用Service时,通常采用组合模式将服务注入到Controller的构造函数,调用时直接传入必要参数执行对应方法即可。但我希望创建一个不可变、无参数的对象,于是将所有方法所需参数都放到Service的构造函数中,虽然实现了目标,但存在明显问题:不同方法需要的参数不同,每次调用都要传很多null,参数被迫定义为[类型]|null,既不优雅也不符合类型安全。我不介意小型类,但创建CreateCmsUserService、UpdateCmsUserService这类单操作类又觉得冗余,想知道有没有更好的解决办法。
当前实现代码
CmsUsersService.php
<?php namespace App\Services; use App\Models\User; use Exception; use Illuminate\Support\Facades\Hash; use App\Interfaces\CRUD; class CmsUsersService implements CRUD { private ?int $user_id; private ?array $data; private ?string $users_to_be_deleted; public function __construct(?array $data, ?int $user_id, ?string $users_to_be_deleted) { $this->user_id = $user_id; $this->data = $data; $this->users_to_be_deleted = $users_to_be_deleted; } public function create() { $this->data['token'] = Hash::make($this->data['email']); $this->data['password'] = Hash::make($this->data['password']); User::create($this->data); return cms_response(trans('cms.users.success_create')); } public function update() { try { if (array_key_exists('password', $this->data)) { $this->data['password'] = Hash::make($this->data['password']); } $user = $this->__findOrFail(); $user->update($this->data); return cms_response(trans('cms.users.success_update')); } catch (\Throwable $th) { return cms_response($th->getMessage(), false, 400); } } public function delete() { User::whereIn('id', json_decode($this->users_to_be_deleted))->delete(); return cms_response(trans('cms.users.success_delete')); } private function __findOrFail() { $user = User::find($this->user_id); if ($user instanceof User) { return $user; } throw new Exception(trans('cms.users.error_user_not_found')); } }
UsersController.php
<?php namespace App\Http\Controllers\Cms; use App\Http\Controllers\Controller; use App\Http\Requests\UserRequest; use App\Services\CmsUsersService; use Illuminate\Http\Request; class UsersController extends Controller { /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(UserRequest $request) { $users_service = new CmsUsersService($request->all(), null, null); $result = $users_service->create(); return redirect()->back()->with('message', $result); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(UserRequest $request, $id) { $users_service = new CmsUsersService($request->all(), $id, null); $result = $users_service->update(); return redirect()->back()->with('message', $result); } /** * Remove the specified resource from storage. * * @param string $users_id * @return \Illuminate\Http\Response */ public function destroy($users_id) { $users_service = new CmsUsersService(null, null, $users_id); $result = $users_service->delete(); return redirect()->back()->with('message', $result); } }
优雅解决方案
方案1:静态工厂方法(推荐)
在CmsUsersService中添加对应操作的静态工厂方法,强制通过工厂方法创建实例,避免直接传null,同时保证类的不可变性。
修改后的CmsUsersService.php
<?php namespace App\Services; use App\Models\User; use Exception; use Illuminate\Support\Facades\Hash; use App\Interfaces\CRUD; class CmsUsersService implements CRUD { private ?int $user_id; private ?array $data; private ?string $users_to_be_deleted; // 将构造函数设为私有,禁止直接实例化 private function __construct(?array $data, ?int $user_id, ?string $users_to_be_deleted) { $this->user_id = $user_id; $this->data = $data; $this->users_to_be_deleted = $users_to_be_deleted; } // 用于创建操作的工厂方法,只接收必要参数 public static function forCreate(array $data): self { return new self($data, null, null); } // 用于更新操作的工厂方法 public static function forUpdate(array $data, int $user_id): self { return new self($data, $user_id, null); } // 用于删除操作的工厂方法 public static function forDelete(string $users_to_be_deleted): self { return new self(null, null, $users_to_be_deleted); } // 原有业务逻辑保持不变 public function create() { $this->data['token'] = Hash::make($this->data['email']); $this->data['password'] = Hash::make($this->data['password']); User::create($this->data); return cms_response(trans('cms.users.success_create')); } public function update() { try { if (array_key_exists('password', $this->data)) { $this->data['password'] = Hash::make($this->data['password']); } $user = $this->__findOrFail(); $user->update($this->data); return cms_response(trans('cms.users.success_update')); } catch (\Throwable $th) { return cms_response($th->getMessage(), false, 400); } } public function delete() { User::whereIn('id', json_decode($this->users_to_be_deleted))->delete(); return cms_response(trans('cms.users.success_delete')); } private function __findOrFail() { $user = User::find($this->user_id); if ($user instanceof User) { return $user; } throw new Exception(trans('cms.users.error_user_not_found')); } }
Controller调用方式修改
// store方法 $users_service = CmsUsersService::forCreate($request->all()); $result = $users_service->create(); // update方法 $users_service = CmsUsersService::forUpdate($request->all(), $id); $result = $users_service->update(); // destroy方法 $users_service = CmsUsersService::forDelete($users_id); $result = $users_service->delete();
优势:
- 无需传递null,每个工厂方法明确当前操作的必要参数,类型安全
- 保持类的不可变性,构造后属性无法修改
- 避免拆分类的冗余,同时代码可读性大幅提升,一眼就能看出实例用途
方案2:DTO(数据传输对象)+ 单方法服务
为每个操作定义DTO,明确参数结构,服务类方法接收DTO作为参数,既保证不可变性,又避免构造函数塞冗余参数。
创建示例DTO
<?php namespace App\DTOs; class CreateCmsUserDTO { public function __construct( public string $name, public string $email, public string $password ) {} // 从Request快速生成DTO public static function fromRequest(\Illuminate\Http\Request $request): self { return new self( $request->input('name'), $request->input('email'), $request->input('password') ); } }
修改后的CmsUsersService.php
<?php namespace App\Services; use App\Models\User; use Illuminate\Support\Facades\Hash; use App\Interfaces\CRUD; use App\DTOs\CreateCmsUserDTO; use App\DTOs\UpdateCmsUserDTO; use App\DTOs\DeleteCmsUserDTO; class CmsUsersService implements CRUD { public function create(CreateCmsUserDTO $dto) { User::create([ 'name' => $dto->name, 'email' => $dto->email, 'password' => Hash::make($dto->password), 'token' => Hash::make($dto->email) ]); return cms_response(trans('cms.users.success_create')); } public function update(UpdateCmsUserDTO $dto) { try { $data = [ 'name' => $dto->name, 'email' => $dto->email ]; if ($dto->password) { $data['password'] = Hash::make($dto->password); } $user = User::findOrFail($dto->user_id); $user->update($data); return cms_response(trans('cms.users.success_update')); } catch (\Throwable $th) { return cms_response($th->getMessage(), false, 400); } } public function delete(DeleteCmsUserDTO $dto) { User::whereIn('id', $dto->userIds)->delete(); return cms_response(trans('cms.users.success_delete')); } }
优势:
- DTO明确参数结构,类型安全,避免无效参数
- 服务类方法专注业务逻辑,参数清晰
- DTO可复用,其他场景创建用户时直接使用
方案3:拆分单职责小类
如果追求极致的单一职责,拆分小类并非冗余——每个类只处理一个操作,逻辑极简,维护和测试更简单。比如:
CreateCmsUserService:仅处理创建逻辑,构造函数只接收$dataUpdateCmsUserService:仅处理更新逻辑,构造函数接收$data和$user_idDeleteCmsUserService:仅处理删除逻辑,构造函数接收$users_to_be_deleted
优势:
- 每个类职责明确,没有多余属性和逻辑
- 完全避免null参数问题
- 测试时只需关注单一操作,更高效
总结
- 不想拆分类时,静态工厂方法是最优解,兼顾优雅性和类型安全
- 追求参数极致清晰,选择DTO+单方法服务模式
- 长远维护角度,拆分单职责小类反而降低复杂度,看似类多实则更易管理
内容的提问来源于stack exchange,提问作者Bernardo Benini Fantin
相关产品推荐
相关产品推荐

