Laravel的DRY原理

考虑一个负责添加新用户的简单模块。

通过他的例子,我们将看到DRY原理的应用打开了哪些可能性。

对我来说,DRY(不要重复自己)的原则始终体现在两个基本定义中:

  1. 知识的重复永远是违反原则的
  2. 代码重复并不总是违反原则

让我们从包含最少逻辑量的控制器开始。

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;
    }
    
    //Other methods
}

逐渐地,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) {
                // Deal with duplicate users
            }
        });
    }
}

通过将代码嵌入到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);
         }
    }

结论


这是一个简单的例子。随着复杂性开始增长,真正的好处开始显现出来。我们一直都知道代码在一个地方,并且界限清楚。

我们获得以下优势:防止重复,简化测试
并为其他原理和设计模式的应用开辟道路。

All Articles