本文目录导读:

在PHP中抽象服务层,核心目标是解耦业务逻辑与表现层、数据访问层,提升代码的可维护性、可测试性和可扩展性,以下是系统化的抽象方法和最佳实践:
服务层的核心定位
Controller(控制器) → Service(服务层) → Repository(仓储层) → Model(模型)
- 控制器:处理HTTP请求、参数验证、返回响应
- 服务层:业务逻辑、事务管理、领域规则
- 仓储层:数据持久化、查询封装
- 模型:数据结构定义
服务层抽象的基础架构
1 基础服务接口
<?php
namespace App\Services\Contracts;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\ModelNotFoundException;
/**
* 通用的 CRUD 服务接口
*/
interface BaseServiceInterface
{
/**
* 列表查询
* @param array $filters
* @param array $options
* @return Collection
*/
public function list(array $filters = [], array $options = []): Collection;
/**
* 详情查询
* @param int $id
* @return object
* @throws ModelNotFoundException
*/
public function find(int $id): object;
/**
* 创建数据
* @param array $data
* @return object
*/
public function create(array $data): object;
/**
* 更新数据
* @param int $id
* @param array $data
* @return object
*/
public function update(int $id, array $data): object;
/**
* 删除数据
* @param int $id
* @return bool
*/
public function delete(int $id): bool;
}
2 抽象基类
<?php
namespace App\Services;
use App\Repositories\Contracts\BaseRepositoryInterface;
use App\Services\Contracts\BaseServiceInterface;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\DB;
/**
* 抽象服务基类
*/
abstract class BaseService implements BaseServiceInterface
{
protected $repository;
/**
* 构造注入仓储层
*/
public function __construct(BaseRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function list(array $filters = [], array $options = []): Collection
{
// 统一处理过滤条件
$query = $this->repository
->queryBuilder()
->when(isset($filters['search']), function ($q) use ($filters) {
$this->applySearch($q, $filters['search']);
});
// 排序
$sortField = $options['sort_field'] ?? 'id';
$sortOrder = $options['sort_order'] ?? 'desc';
$query->orderBy($sortField, $sortOrder);
// 分页/限制
$limit = $options['limit'] ?? null;
if ($limit) {
return $query->limit($limit)->get();
}
return $query->get();
}
public function find(int $id): object
{
$record = $this->repository->find($id);
if (!$record) {
throw new ModelNotFoundException('Record not found');
}
return $record;
}
public function create(array $data): object
{
try {
DB::beginTransaction();
$record = $this->repository->create($data);
// 可在此添加创建后的逻辑
$this->afterCreate($record, $data);
DB::commit();
return $record;
} catch (\Throwable $e) {
DB::rollBack();
throw $e;
}
}
public function update(int $id, array $data): object
{
try {
DB::beginTransaction();
$record = $this->repository->update($id, $data);
$this->afterUpdate($record, $data);
DB::commit();
return $record;
} catch (\Throwable $e) {
DB::rollBack();
throw $e;
}
}
public function delete(int $id): bool
{
try {
DB::beginTransaction();
$result = $this->repository->delete($id);
$this->afterDelete($id);
DB::commit();
return $result;
} catch (\Throwable $e) {
DB::rollBack();
throw $e;
}
}
// 模板方法:可被子类覆写
protected function applySearch($query, $search): void
{
// 子类实现具体的搜索逻辑
}
protected function afterCreate($record, $data): void {}
protected function afterUpdate($record, $data): void {}
protected function afterDelete($id): void {}
}
业务服务示例
1 用户服务
<?php
namespace App\Services;
use App\Repositories\UserRepository;
use App\Services\Contracts\BaseServiceInterface;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Cache;
class UserService extends BaseService
{
protected $userRepository;
public function __construct(UserRepository $repository)
{
parent::__construct($repository);
$this->userRepository = $repository;
}
/**
* 注册新用户 - 包含额外业务逻辑
*/
public function register(array $data): object
{
// 密码加密
$data['password'] = Hash::make($data['password']);
// 创建用户
$user = $this->create($data);
// 发送欢迎邮件
try {
Mail::to($user->email)->send(new WelcomeMail($user));
} catch (\Exception $e) {
// 邮件失败不影响注册,记录日志
Log::warning('Welcome email failed', ['user_id' => $user->id]);
}
return $user;
}
/**
* 用户登录
*/
public function login(string $email, string $password): ?array
{
$user = $this->repository->findByEmail($email);
if (!$user || !Hash::check($password, $user->password)) {
return null;
}
// 生成 token(假设使用 Passport/Sanctum)
$token = $user->createToken('auth_token')->plainTextToken;
// 缓存用户信息
Cache::put("user:{$user->id}", $user, now()->addHours(24));
return [
'user' => $user,
'token' => $token
];
}
/**
* 更新用户资料(带数据验证)
*/
public function updateProfile(int $userId, array $data): object
{
// 业务规则:邮箱唯一性验证
$emailExists = $this->repository->findByEmail($data['email'] ?? '');
if ($emailExists && $emailExists->id !== $userId) {
throw new \InvalidArgumentException("Email already exists");
}
$user = $this->update($userId, $data);
// 更新缓存
Cache::put("user:{$userId}", $user, now()->addHours(24));
return $user;
}
protected function applySearch($query, $search): void
{
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
});
}
protected function afterDelete($id): void
{
// 删除用户的关联缓存
Cache::forget("user:{$id}");
}
}
依赖注入与服务容器(Laravel 示例)
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Repositories\UserRepository;
use App\Services\UserService;
use App\Services\Contracts\BaseServiceInterface;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
// 绑定接口到实现
$this->app->bind(BaseServiceInterface::class, UserService::class);
// 绑定具体服务
$this->app->singleton(UserService::class, function ($app) {
return new UserService(
$app->make(UserRepository::class)
);
});
// 自动绑定(如果使用自动解析)
// $this->app->resolving(UserService::class, function ($service, $app) {
// $service->setDependencies($app);
// });
}
public function boot()
{
//
}
}
控制器中使用服务层
<?php
namespace App\Http\Controllers;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class UserController extends BaseController
{
protected $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function index(Request $request): JsonResponse
{
$filters = $request->only(['search', 'role']);
$users = $this->userService->list($filters, [
'sort_field' => $request->get('sort_field', 'id'),
'limit' => $request->get('limit', 10)
]);
return $this->successResponse($users);
}
public function show(int $id): JsonResponse
{
try {
$user = $this->userService->find($id);
return $this->successResponse($user);
} catch (\Exception $e) {
return $this->errorResponse($this->statusNotFound);
}
}
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8',
]);
try {
$user = $this->userService->register($validated);
return $this->createdResponse($user);
} catch (\Throwable $e) {
return $this->errorResponse($this->statusServerError, $e->getMessage());
}
}
}
架构模式对比
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基础 CRUD | 简单直接 | 业务逻辑易膨胀 | 简单管理系统 |
| 领域服务 | 聚合领域逻辑 | 需要领域设计经验 | 复杂业务系统 |
| CQRS | 读写分离清晰 | 复杂度高 | 高并发的读写系统 |
| Action 模式 | 单一职责极度清晰 | 文件数量多 | 需要流程图展示的场景 |
最佳实践建议
1 接口设计原则
// ❌ 反例:接口过于宽泛 public function getUserData(int $id, array $params = []); public function saveUser(array $data); // ✅ 正例:单一职责 public function fetchUserProfile(int $userId): UserProfile; public function updateUserCredentials(int $userId, string $password): void;
2 错误处理策略
// 统一异常处理
class ServiceException extends \Exception {
public function __construct(
string $message,
protected int $statusCode = 400,
array $context = []
) {
parent::__construct($message);
}
}
// 在服务中使用
public function updateBalance(int $userId, float $amount): void
{
if ($amount <= 0) {
throw new ServiceException("Invalid amount");
}
if (!$this->userHasEnoughBalance($userId, $amount)) {
throw new ServiceException("Insufficient balance", 422);
}
}
3 缓存策略
// 可缓存的服务方法
public function list(array $filters): Collection
{
$cacheKey = $this->generateCacheKey($filters);
return Cache::remember($cacheKey, 3600, function () use ($filters) {
return parent::list($filters);
});
}
// 使缓存失效
protected function afterUpdate($record, $data): void
{
Cache::forget("user:{$record->id}");
Cache::forget("user:all");
}
进阶抽象:领域服务
<?php
namespace App\Services\Domain;
use App\Repositories\OrderRepository;
use App\Repositories\InventoryRepository;
use App\Services\Domain\Contracts\OrderDomainServiceInterface;
/**
* 订单领域服务 - 处理复杂业务规则
*/
class OrderDomainService implements OrderDomainServiceInterface
{
public function __construct(
private OrderRepository $orderRepository,
private InventoryRepository $inventoryRepository
) {}
public function placeOrder(array $items, int $customerId): Order
{
// 检查库存
foreach ($items as $item) {
if (!$this->inventoryRepository->hasStock($item['product_id'], $item['quantity'])) {
throw new \RuntimeException("Insufficient stock");
}
}
// 创建订单
$order = $this->orderRepository->create([
'customer_id' => $customerId,
'status' => OrderStatus::PENDING,
]);
// 扣减库存(事务操作)
DB::transaction(function () use ($items, $order) {
foreach ($items as $item) {
$order->items()->create($item);
$this->inventoryRepository->decrementStock(
$item['product_id'],
$item['quantity']
);
}
});
return $order->load('items');
}
public function cancelOrder(int $orderId): Order
{
return DB::transaction(function () use ($orderId) {
$order = $this->orderRepository->find($orderId);
if ($order->status === OrderStatus::SHIPPED) {
throw new \InvalidArgumentException("Cannot cancel shipped order");
}
// 恢复库存
foreach ($order->items as $item) {
$this->inventoryRepository->incrementStock(
$item->product_id,
$item->quantity
);
}
$order->status = OrderStatus::CANCELLED;
$order->save();
return $order;
});
}
}
测试服务层
<?php
namespace Tests\Unit;
use App\Services\UserService;
use App\Repositories\UserRepository;
use Mockery;
use Tests\TestCase;
class UserServiceTest extends TestCase
{
public function testRegisterCreatesUserAndHashesPassword()
{
// Mock 仓储
$repositoryMock = Mockery::mock(UserRepository::class);
$repositoryMock->shouldReceive('create')
->once()
->withArgs(function ($data) {
// 验证密码已加密
return Hash::needsRehash($data['password']) === false;
})
->andReturn(new User(['id' => 1, 'name' => 'Test User']));
$service = new UserService($repositoryMock);
$user = $service->register([
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password123'
]);
$this->assertInstanceOf(User::class, $user);
$this->assertEquals(1, $user->id);
}
public function testLoginReturnsTokenForValidCredentials()
{
// 测试登录逻辑
$user = new User(['email' => 'test@example.com']);
$user->password = Hash::make('secret123');
$repositoryMock = Mockery::mock(UserRepository::class);
$repositoryMock->shouldReceive('findByEmail')
->once()
->with('test@example.com')
->andReturn($user);
$service = new UserService($repositoryMock);
$result = $service->login('test@example.com', 'secret123');
$this->assertArrayHasKey('user', $result);
$this->assertArrayHasKey('token', $result);
}
}
设计原则
- 单一职责:一个服务只做一类事
- 开闭原则:通过扩展服务类而非修改核心逻辑
- 依赖倒置:依赖于抽象接口,不依赖具体实现
- 接口隔离:服务接口面向调用者需求设计
- 迪米特法则:服务不直接操作其他服务的内部细节
服务层的抽象是一个持续演进的过程,建议从简单的 BaseService 开始,随着业务复杂度增加逐步引入领域服务、CQRS 等高级模式,始终保持清晰的职责边界和可测试性。