مبدأ جاف مع Laravel

خذ بعين الاعتبار وحدة بسيطة مسؤولة عن إضافة مستخدمين جدد.

وبمثاله ، سنرى الاحتمالات التي يفتحها تطبيق مبدأ 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 السحرية )
  • لديه كل التبعيات اللازمة
  • يضمن الامتثال لجميع قواعد العمل داخل نفسه ، ويولد استثناء عند انتهاكها

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 نفسها ، ولكن استخدم نمط الديكور.

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