考虑一个负责添加新用户的简单模块。通过他的例子,我们将看到DRY原理的应用打开了哪些可能性。对我来说,DRY(不要重复自己)的原则始终体现在两个基本定义中:- 知识的重复永远是违反原则的
- 代码重复并不总是违反原则
让我们从包含最少逻辑量的控制器开始。class UserController
{
public function create(CreateRequest $request)
{
$user = User::create($request->all());
return view('user.created', compact('user'));
}
}
class UserApiController
{
public function create(CreateRequest $request)
{
$user = User::create($request->all());
return response()->noContent(201);
}
}
在初始阶段,这样的重复代码似乎是无害的。但是我们已经有了知识的重复,知识的重复是被禁止的。为此,我们在UserService类中概括用户创建class UserService
{
public function create(array $data): User
{
$user = new User;
$user->email = $data['email'];
$user->password = $data['password'];
$user->save();
return $user;
}
public function delete($userId): bool
{
$user = User::findOrFail($userId);
return $user->delete();
}
}
将使用模型的所有逻辑转移到服务后,我们摆脱了其在控制器中的重复。但是我们还有另一个问题。假设我们必须稍微简化创建用户的过程。class UserService
{
protected $blogService;
public function __construct(BlogService $blogService)
{
$this->blogService = $blogService;
}
public function create(array $data): User
{
$user = new User;
$user->email = $data['email'];
$user->password = $data['password'];
$user->save();
$blog = $this->blogService->create();
$user->blogs()->attach($blog);
return $user;
}
}
逐渐地,UserService类将开始增长,我们冒着获得具有大量依赖关系的超类的风险。CreateUser单操作类
为了避免此类后果,您可以将服务分为单个操作的类。本课程的基本要求:- 代表要执行的动作的名称
- 有一个公共方法(我将使用__invoke magic方法)
- 具有所有必要的依赖关系
- 确保遵守其内部的所有业务规则,并在违反时生成异常
class CreateUser
{
protected $blogService;
public function __construct(BlogService $blogService)
{
$this->blogService = $blogService;
}
public function __invoke(array $data): User
{
$email = $data['email'];
if (User::whereEmail($email)->first()) {
throw new EmailNotUniqueException("$email should be unique!");
}
$user = new User;
$user->email = $data['email'];
$user->password = $data['password'];
$user->save();
$blog = $this->blogService->create();
$user->blogs()->attach($blog);
return $user;
}
}
我们在CreateRequet类中已经有一个电子邮件字段检查,但是也可以在此处添加一个检查。这可以更准确地反映创建用户的业务逻辑,还可以简化调试。控制器采用以下形式class UserController
{
public function create(CreateRequest $request, CreateUser $createUser)
{
$user = $createUser($request->all());
return view('user.created', compact('user'));
}
}
class UserApiController
{
public function create(CreateRequest $request, CreateUser $createUser)
{
$user = $createUser($request->all());
return response()->noContent(201);
}
}
因此,我们拥有用于创建用户的完全隔离的逻辑。方便修改和扩展。现在,让我们看看这种方法给我们带来了什么好处。例如,有一个导入用户的任务。class ImportUser
{
protected $createUser;
public function __construct(CreateUser $createUser)
{
$this->createUser = $createUser;
}
public function handle(array $rows): Collection
{
return collect($rows)->map(function (array $row) {
try {
return $this->createUser($row);
} catch (EmailNotUniqueException $e) {
}
});
}
}
通过将代码嵌入到Collection :: map()方法中,我们有机会重用代码。并为电子邮件地址不唯一的我们的需求用户处理。敷料
假设我们需要在文件中注册每个新用户。为此,我们不会将此操作嵌入到CreateUser类本身中,而是使用Decorator模式。class LogCreateUser extends CreateUser
{
public function __invoke(array $data)
{
Log::info("A new user has registered: " . $data['email']);
parent::__invoke($data);
}
}
然后,使用Laravel IoC容器,我们可以将LogCreateUser类与CreateUser类相关联,并且每当需要第二个实例时就可以实现第一个。class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(CreateUser::class, LogCreateUser::class);
}
我们也有机会使用配置文件中的变量进行用户创建设置。class AppServiceProvider extends ServiceProvider
{
public function register()
{
if (config("users.log_registration")) {
$this->app->bind(CreateUser::class, LogCreateUser::class);
}
}
结论
这是一个简单的例子。随着复杂性开始增长,真正的好处开始显现出来。我们一直都知道代码在一个地方,并且界限清楚。我们获得以下优势:防止重复,简化测试并为其他原理和设计模式的应用开辟道路。