在现代软件开发中,维护干净且结构化的代码库对于可伸缩性、可维护性和可测试性至关重要。 Laravel 是目前最流行的 PHP 框架,为构建 Web 应用程序提供了坚实的基础。 Laravel 中使用的一个关键架构模式是Service Layer。在本文中,我们将深入研究 Laravel 中Service的概念,并探索它如何帮助有效地组织您的应用程序逻辑。
什么是Service Layer(服务层)
Service Layer是一种架构模式,可促进关注点分离并使应用程序的业务逻辑与表示层和数据访问层分离。它充当控制器(或路由)和底层数据模型之间的中介,封装复杂的业务规则和操作。
使用Service Layer有什么好处
- 封装和模块化:通过将业务逻辑封装在服务类中,您可以实现模块化结构,以便于维护和测试。服务成为独立的单元,使得推理和修改它们的功能变得更简单。
- 可重用性:通过精心设计的服务层,您可以在应用程序的不同部分甚至多个项目中重用服务。这种可重用性减少了代码重复并促进了 DRY(不要重复自己)方法
- 简化的控制器逻辑:将复杂的业务逻辑置于服务层中有助于保持您的控制器精简并专注于处理 HTTP 请求和响应。这种分离确保控制器不会被复杂的业务规则所困扰,并保持管理用户界面的明确职责。
- 可测试性:服务可以很容易地单独测试,允许你编写全面的单元测试。将业务逻辑与特定于框架的组件(例如 HTTP 请求或数据库操作)分开,可以使测试更加直接并提高应用程序的整体质量。
如何在Laravel实现Service Layer
创建接口
首先,我们应该先创建一个接口用于描述Service功能,如我们创建一个UserServiceInterface
app/Contracts/Services/UserService/UserServiceInterface.php
<?php
declare(strict_types=1);
namespace App\Contracts\Services\UserService;
use App\Models\User;
use App\ValueObjects\Phone;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use LogicException;
interface UserServiceInterface
{
/**
* @param Phone $phone
* @return User
* @throws ModelNotFoundException
*/
public function findByPhone(Phone $phone): User;
/**
* @throws LogicException
*/
public function create(string $name, Phone $phone): User;
/**
* @param int $id
* @return void
* @throws ModelNotFoundException
*/
public function deleteById(int $id): void;
}
创建接口的实现
现在我们可以描述 UserService 的实现:
app/Services/UserService/UserService.php
<?php
declare(strict_types=1);
namespace App\Services\UserService;
use App\Contracts\Services\UserService\UserServiceInterface;
use App\Models\User;
use App\ValueObjects\Phone;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use LogicException;
class UserService implements UserServiceInterface
{
public function __construct(private readonly User $user)
{
}
/**
* @param int $userId
* @return User
* @throws ModelNotFoundException
*/
public function findById(int $userId): User
{
//TODO: I prefer to use the Repository pattern for this, but that's a topic for a separate article
/** @noinspection PhpIncompatibleReturnTypeInspection */
return $this->user->newQuery()
->findOrFail($userId);
}
/**
* @param Phone $phone
* @return User
* @throws ModelNotFoundException
*/
public function findByPhone(Phone $phone): User
{
//TODO: I prefer to use the Repository pattern for this, but that's a topic for a separate article
/** @noinspection PhpIncompatibleReturnTypeInspection */
return $this->user->newQuery()
->where('phone', '=', $phone->toString())
->firstOrFail();
}
/**
* @throws LogicException
*/
public function create(string $name, Phone $phone): User
{
try {
$this->findByPhone($phone);
throw new LogicException('User with this phone already exists!');//I recommend to use separate Exception for specific case
} catch (ModelNotFoundException $e) {
}
$user = new User();
$user->name = $name;
$user->phone = $phone;
$user->save();
//Some mandatory logic when creating a user
return $user;
}
/**
* @param int $id
* @return void
* @throws ModelNotFoundException
*/
public function deleteById(int $id): void
{
//TODO: I prefer to use the Repository pattern for this, but that's a topic for a separate article
$user = $this->findById($id);
$user->delete();
}
}
创建服务提供者
现在我们应该注册新服务,为此我们需要为我们的服务创建 ServiceProvider 文件并描述 UserService 的绑定:
app/Providers/ServiceServiceProvider.php
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Contracts\Services\UserService\UserServiceInterface;
use App\Services\UserService\UserService;
use Illuminate\Support\ServiceProvider;
class ServiceServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
//
}
/**
* Bootstrap services.
*/
public function boot(): void
{
$this->app->bind(
UserServiceInterface::class,
UserService::class
);
}
}
并添加新的 ServiceServiceProvider
config/app.php :
'providers' => [
/*
* Laravel Framework Service Providers...
*/
App\Providers\ServiceServiceProvider::class,
...
],
使用
至此,现在我们可以使用我们的 UserService 并在任何地方重用用户创建逻辑:
In Command:
app/Console/Commands/UserCreateCommand.php
<?php
namespace App\Console\Commands;
use App\Contracts\Services\UserService\UserServiceInterface;
use App\ValueObjects\Phone;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\App;
use InvalidArgumentException;
use LogicException;
class UserCreateCommand extends Command
{
protected $signature = 'users:create {name : User name} {phone : User phone}';
protected $description = 'Create user with name, email and phone';
public function handle(): int
{
$name = $this->argument('name');
try {
$phone = Phone::fromString($this->argument('phone'));
} catch (InvalidArgumentException $e) {
$this->error($e->getMessage());
return self::FAILURE;
}
/** @var UserServiceInterface $userService */
$userService = App::make(UserServiceInterface::class);
try {
$userService->create($name, $phone);
} catch (LogicException $e) {
$this->error($e->getMessage());
return self::FAILURE;
}
$this->info('User successfully created!');
return self::SUCCESS;
}
}
执行Command
php artisan users:create Alex +12124567891
User successfully created!
在控制器中:
我们将为我们的查询使用验证,也就是说,我们必须创建一个 Request 类:
app/Http/Requests/Rest/User/CreateRequest.php
<?php
declare(strict_types=1);
namespace App\Http\Requests\Rest\User;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;
class CreateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, Rule|array|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'phone' => ['required', 'string'],//We can create a separate rule for phone validation, but that will be in the next article
];
}
}
app/Http/Controllers/Rest/User/CreateController.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Rest\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\Rest\User\CreateRequest;
use App\Contracts\Services\UserService\UserServiceInterface;
use App\ValueObjects\Phone;
use DateTimeInterface;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
final class CreateController extends Controller
{
public function __construct(private readonly UserServiceInterface $userService)
{
}
public function __invoke(CreateRequest $request): JsonResponse
{
$name = $request->get('name');
$phone = Phone::fromString($request->get('phone'));
$user = $this->userService->create($name, $phone);
return new JsonResponse([
'id' => $user->id,
'name' => $user->name,
'phone' => $user->phone->toString(),
'created_at' => $user->created_at->format(DateTimeInterface::ATOM),
], Response::HTTP_CREATED);
}
}
注册路由
routes/api.php
<?php
use App\Http\Controllers\Rest\User\CreateController;
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'users'], function () {
Route::post('/', [CreateController::class, '__invoke']);
});
现在,可以跑一下接口测试了
curl --location 'http://127.0.0.1:8000/api/users' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"name": "Alex",
"phone": "+12124567892"
}'
总结
Laravel 中的服务层模式提供了明确的关注点分离,促进了模块化、可重用和可测试的代码。通过将复杂的业务逻辑封装在服务类中,您可以维护一个干净的架构,从而简化维护、增强可重用性、简化控制器逻辑并促进全面测试。
有效实施后,服务层可以显着改善 Laravel 应用程序的组织和结构,从而使代码库更易于维护和扩展。通过利用 Laravel 的依赖注入和服务容器的强大功能,您可以构建健壮且灵活的应用程序