You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:仅处理创建逻辑,构造函数只接收$data
  • UpdateCmsUserService:仅处理更新逻辑,构造函数接收$data和$user_id
  • DeleteCmsUserService:仅处理删除逻辑,构造函数接收$users_to_be_deleted

优势:

  • 每个类职责明确,没有多余属性和逻辑
  • 完全避免null参数问题
  • 测试时只需关注单一操作,更高效

总结

  • 不想拆分类时,静态工厂方法是最优解,兼顾优雅性和类型安全
  • 追求参数极致清晰,选择DTO+单方法服务模式
  • 长远维护角度,拆分单职责小类反而降低复杂度,看似类多实则更易管理

内容的提问来源于stack exchange,提问作者Bernardo Benini Fantin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 18:55:23