PHP服务层怎么抽象

wen PHP项目 1

本文目录导读:

PHP服务层怎么抽象

  1. 服务层的核心定位
  2. 服务层抽象的基础架构
  3. 业务服务示例
  4. 依赖注入与服务容器(Laravel 示例)
  5. 控制器中使用服务层
  6. 架构模式对比
  7. 最佳实践建议
  8. 进阶抽象:领域服务
  9. 测试服务层
  10. 设计原则

在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);
    }
}

设计原则

  1. 单一职责:一个服务只做一类事
  2. 开闭原则:通过扩展服务类而非修改核心逻辑
  3. 依赖倒置:依赖于抽象接口,不依赖具体实现
  4. 接口隔离:服务接口面向调用者需求设计
  5. 迪米特法则:服务不直接操作其他服务的内部细节

服务层的抽象是一个持续演进的过程,建议从简单的 BaseService 开始,随着业务复杂度增加逐步引入领域服务、CQRS 等高级模式,始终保持清晰的职责边界和可测试性。

抱歉,评论功能暂时关闭!