سی ام اس نویسسی ام اس نویسیاد بگیر، سیستم خودتو بنویس
مجلهتماس با ما
سی ام اس نویسسی ام اس نویس

CMS نویس مرجعی برای آموزش برنامه نویسی وب و PHP، توسعه افزونه‌های اختصاصی وردپرس، طراحی ورک‌فلو و اتوماسیون با n8n و توسعه CMS های اختصاصی است. هدف ما ارائه آموزش‌های کاربردی و راهکارهای حرفه‌ای برای توسعه‌دهندگان، کسب‌وکارها و علاقه‌مندان به دنیای برنامه‌نویسی است.

پیوندهای سریع
محصولات فیزیکیمحصولات دیجیتالدوره‌های آموزشی
سی ام اس نویس
مجله سی ام اس نویستماس با ماپنل کاربری
سی ام اس نویس — تمامی حقوق برای سی ام اس نویس محفوظ است. © 1406 تماس با ما09307711941
Chain of Responsibility Pattern در PHP چیست؟ آموزش الگوی زنجیره مسئولیت

Chain of Responsibility Pattern در PHP چیست؟ آموزش الگوی زنجیره مسئولیت

ممدیر کل سیستم ۲۲ شهریور ۱۴۰۵ ۲۰ دقیقه مطالعه ۲ بازدید
Chain of Responsibility Pattern در PHP چیست؟ آموزش الگوی زنجیره مسئولیت

در پروژه‌های PHP معمولاً درخواست‌ها باید از چند مرحله مختلف عبور کنند. برای مثال، قبل از اینکه یک کاربر بتواند سفارشی ثبت کند، ممکن است لازم باشد مراحل زیر انجام شوند:

  1. بررسی ورود کاربر
  2. بررسی سطح دسترسی
  3. اعتبارسنجی اطلاعات
  4. بررسی موجودی حساب
  5. ثبت لاگ
  6. اجرای عملیات اصلی

یک پیاده‌سازی ساده ممکن است تمام این مراحل را در یک Controller یا Service قرار دهد:


public function store(Request $request)

{

    if (!auth()->check()) {

        // ...

    }



    if (!auth()->user()->can('create-order')) {

        // ...

    }



    if (!$this->validateRequest($request)) {

        // ...

    }



    if (!$this->hasEnoughBalance()) {

        // ...

    }



    // ثبت سفارش

}

این کد در پروژه‌های کوچک شاید قابل قبول باشد، اما با افزایش مراحل، Controller بزرگ و پیچیده می‌شود.

در چنین شرایطی می‌توان از Chain of Responsibility Pattern استفاده کرد.

Chain of Responsibility یک الگوی طراحی رفتاری است که درخواست را در یک زنجیره از Objectها قرار می‌دهد. هر Object می‌تواند درخواست را پردازش کند یا آن را به Handler بعدی منتقل کند. به این ترتیب، فرستنده درخواست لازم نیست بداند کدام Handler در نهایت مسئول پردازش آن خواهد بود. citeturn0search0


Chain of Responsibility Pattern چیست؟

Chain of Responsibility یا الگوی زنجیره مسئولیت الگویی است که در آن چند Handler به‌صورت زنجیره‌ای به یکدیگر متصل می‌شوند.

هر Handler معمولاً دو وظیفه دارد:

  • بررسی کند آیا می‌تواند درخواست را پردازش کند یا خیر.
  • اگر نتوانست، درخواست را به Handler بعدی منتقل کند.

ساختار کلی به شکل زیر است:


Request

   ↓

Handler 1

   ↓

Handler 2

   ↓

Handler 3

   ↓

Handler 4

برای مثال در یک سیستم احراز هویت:


Request

   ↓

AuthenticationHandler

   ↓

AuthorizationHandler

   ↓

ValidationHandler

   ↓

BusinessHandler

هر Handler فقط مسئول بخشی از منطق است و لازم نیست از جزئیات تمام Handlerهای دیگر اطلاع داشته باشد.


مثال واقعی برای درک Chain of Responsibility

فرض کنید در یک فروشگاه اینترنتی، کاربر می‌خواهد سفارشی ثبت کند.

درخواست باید مراحل زیر را طی کند:


بررسی ورود کاربر

        ↓

بررسی دسترسی

        ↓

بررسی اطلاعات سفارش

        ↓

بررسی موجودی

        ↓

ثبت سفارش

اگر کاربر وارد نشده باشد، درخواست همان‌جا متوقف می‌شود.

اگر کاربر وارد شده باشد اما دسترسی لازم را نداشته باشد، درخواست به مرحله بعد نمی‌رسد.

اگر تمام مراحل موفق باشند، درخواست به Handler نهایی می‌رسد و سفارش ثبت می‌شود.

نکته مهم این است که Controller فقط زنجیره را اجرا می‌کند و لازم نیست تمام قوانین را خودش بداند.


اجزای اصلی Chain of Responsibility

این Pattern معمولاً از اجزای زیر تشکیل می‌شود:

1. Request

درخواست یا داده‌ای است که باید پردازش شود.

برای مثال:


$request = new OrderRequest();

یا:


$request = [

    'user_id' => 10,

    'product_id' => 25,

    'quantity' => 2,

];

2. Handler

Handler قرارداد مشترک تمام اعضای زنجیره را مشخص می‌کند.

این قرارداد معمولاً شامل متدی مانند handle() است.

3. Concrete Handler

هر Concrete Handler منطق یک مرحله مشخص را پیاده‌سازی می‌کند.

برای مثال:


AuthenticationHandler

ValidationHandler

BalanceHandler

LoggingHandler

4. Successor

Successor همان Handler بعدی در زنجیره است.

اگر Handler فعلی نتواند درخواست را پردازش کند، آن را به Successor منتقل می‌کند.

5. Client

Client زنجیره را ایجاد و درخواست را به اولین Handler ارسال می‌کند.


# پیاده‌سازی ساده Chain of Responsibility در PHP

در این مثال می‌خواهیم درخواست ثبت سفارش را از میان چند Handler عبور دهیم.

ابتدا یک Request ساده ایجاد می‌کنیم:


final class OrderRequest

{

    public function __construct(

        public readonly int $userId,

        public readonly int $productId,

        public readonly int $quantity,

        public readonly int $balance,

    ) {}

}

این کلاس اطلاعات مورد نیاز برای ثبت سفارش را نگهداری می‌کند.


ایجاد Handler Interface

حالا Interface مربوط به Handler را ایجاد می‌کنیم:


interface HandlerInterface

{

    public function setNext(

        HandlerInterface $handler

    ): HandlerInterface;



    public function handle(

        OrderRequest $request

    ): bool;

}

در این Interface دو متد داریم:

متد setNext

برای تعیین Handler بعدی استفاده می‌شود.

متد handle

درخواست را دریافت و پردازش می‌کند.

مقدار bool مشخص می‌کند که آیا درخواست با موفقیت پردازش شده است یا خیر.


# ایجاد کلاس پایه Handler

برای جلوگیری از تکرار کد، یک کلاس Abstract ایجاد می‌کنیم:


abstract class AbstractHandler implements HandlerInterface

{

    private ?HandlerInterface $nextHandler = null;



    public function setNext(

        HandlerInterface $handler

    ): HandlerInterface {

        $this->nextHandler = $handler;



        return $handler;

    }



    public function handle(

        OrderRequest $request

    ): bool {

        if ($this->nextHandler !== null) {

            return $this->nextHandler->handle($request);

        }



        return true;

    }

}

در این کلاس:


private ?HandlerInterface $nextHandler = null;

Handler بعدی نگهداری می‌شود.

متد setNext() علاوه بر تنظیم Handler بعدی، خود Handler را نیز برمی‌گرداند تا بتوانیم زنجیره را به‌صورت زنجیره‌ای بسازیم.

مثلاً:


$authentication

    ->setNext($authorization)

    ->setNext($validation);


# ایجاد AuthenticationHandler

حالا اولین Handler را ایجاد می‌کنیم:


final class AuthenticationHandler extends AbstractHandler

{

    public function handle(

        OrderRequest $request

    ): bool {

        if ($request->userId <= 0) {

            throw new LogicException(

                'User must be authenticated.'

            );

        }



        echo "Authentication passed.\n";



        return parent::handle($request);

    }

}

این Handler بررسی می‌کند که شناسه کاربر معتبر باشد.

اگر شناسه کاربر نامعتبر باشد، Exception ایجاد می‌شود و زنجیره متوقف خواهد شد.

اگر اطلاعات معتبر باشد، درخواست به Handler بعدی منتقل می‌شود.


# ایجاد AuthorizationHandler

حالا Handler مربوط به سطح دسترسی را ایجاد می‌کنیم:


final class AuthorizationHandler extends AbstractHandler

{

    public function handle(

        OrderRequest $request

    ): bool {

        $hasPermission = $request->userId === 10;



        if (!$hasPermission) {

            throw new LogicException(

                'User does not have permission.'

            );

        }



        echo "Authorization passed.\n";



        return parent::handle($request);

    }

}

در این مثال ساده، فقط کاربر شماره ۱۰ اجازه ثبت سفارش دارد.

در پروژه واقعی می‌توان این بررسی را به سیستم Permission یا Policy منتقل کرد.


# ایجاد ValidationHandler

حالا اطلاعات سفارش را اعتبارسنجی می‌کنیم:


final class ValidationHandler extends AbstractHandler

{

    public function handle(

        OrderRequest $request

    ): bool {

        if ($request->productId <= 0) {

            throw new InvalidArgumentException(

                'Product ID is invalid.'

            );

        }



        if ($request->quantity <= 0) {

            throw new InvalidArgumentException(

                'Quantity must be greater than zero.'

            );

        }



        echo "Validation passed.\n";



        return parent::handle($request);

    }

}

این Handler فقط مسئول اعتبارسنجی است و کاری به احراز هویت یا موجودی حساب ندارد.


# ایجاد BalanceHandler

حالا موجودی حساب کاربر را بررسی می‌کنیم:


final class BalanceHandler extends AbstractHandler

{

    public function handle(

        OrderRequest $request

    ): bool {

        $requiredBalance = $request->quantity * 100;



        if ($request->balance < $requiredBalance) {

            throw new LogicException(

                'User does not have enough balance.'

            );

        }



        echo "Balance check passed.\n";



        return parent::handle($request);

    }

}

در این مثال فرض کرده‌ایم قیمت هر واحد محصول ۱۰۰ واحد است.

در پروژه واقعی باید قیمت از دیتابیس یا سرویس قیمت‌گذاری دریافت شود.


# ایجاد OrderHandler

آخرین Handler مسئول اجرای عملیات اصلی است:


final class OrderHandler extends AbstractHandler

{

    public function handle(

        OrderRequest $request

    ): bool {

        echo "Order created successfully.\n";



        return true;

    }

}

چون این Handler آخرین مرحله زنجیره است، دیگر نیازی به فراخوانی Handler بعدی ندارد.


# ساخت زنجیره Handlerها

حالا تمام Handlerها را به یکدیگر متصل می‌کنیم:


$authentication = new AuthenticationHandler();

$authorization = new AuthorizationHandler();

$validation = new ValidationHandler();

$balance = new BalanceHandler();

$order = new OrderHandler();



$authentication

    ->setNext($authorization)

    ->setNext($validation)

    ->setNext($balance)

    ->setNext($order);

زنجیره نهایی به شکل زیر خواهد بود:


AuthenticationHandler

          ↓

AuthorizationHandler

          ↓

ValidationHandler

          ↓

BalanceHandler

          ↓

OrderHandler


# اجرای زنجیره

حالا یک درخواست ایجاد می‌کنیم:


$request = new OrderRequest(

    userId: 10,

    productId: 25,

    quantity: 2,

    balance: 500,

);

و آن را به اولین Handler می‌دهیم:


$authentication->handle($request);

خروجی:


Authentication passed.

Authorization passed.

Validation passed.

Balance check passed.

Order created successfully.

در این مثال هر Handler وظیفه خودش را انجام می‌دهد و در صورت موفقیت، درخواست را به مرحله بعد منتقل می‌کند.


# توقف زنجیره در صورت خطا

اگر موجودی کاربر کافی نباشد:


$request = new OrderRequest(

    userId: 10,

    productId: 25,

    quantity: 10,

    balance: 500,

);

در این حالت BalanceHandler درخواست را متوقف می‌کند:


Authentication passed.

Authorization passed.

Validation passed.

و سپس Exception ایجاد می‌شود:


User does not have enough balance.

در نتیجه:


OrderHandler

هرگز اجرا نمی‌شود.


# روش دوم؛ بازگرداندن پاسخ به‌جای Exception

در بعضی پروژه‌ها بهتر است Handlerها به‌جای پرتاب Exception، پاسخ مشخصی برگردانند.

برای مثال:


final class HandlerResult

{

    public function __construct(

        public readonly bool $success,

        public readonly ?string $message = null,

    ) {}

}

حالا Interface را تغییر می‌دهیم:


interface HandlerInterface

{

    public function setNext(

        HandlerInterface $handler

    ): HandlerInterface;



    public function handle(

        OrderRequest $request

    ): HandlerResult;

}

یک Handler می‌تواند پاسخ ناموفق تولید کند:


final class BalanceHandler extends AbstractHandler

{

    public function handle(

        OrderRequest $request

    ): HandlerResult {

        $requiredBalance = $request->quantity * 100;



        if ($request->balance < $requiredBalance) {

            return new HandlerResult(

                success: false,

                message: 'Insufficient balance.'

            );

        }



        return parent::handle($request);

    }

}

این روش برای APIها مناسب است، زیرا می‌توان پاسخ استاندارد JSON تولید کرد.


# تفاوت Chain of Responsibility با if/else

در روش سنتی ممکن است تمام منطق در یک متد قرار بگیرد:


public function process(OrderRequest $request): void

{

    if (!$this->isAuthenticated($request)) {

        // ...

    }



    if (!$this->hasPermission($request)) {

        // ...

    }



    if (!$this->isValid($request)) {

        // ...

    }



    if (!$this->hasBalance($request)) {

        // ...

    }



    $this->createOrder($request);

}

مشکل این روش این است که:

  • تمام قوانین در یک کلاس قرار دارند.
  • تغییر یک مرحله ممکن است روی کل متد اثر بگذارد.
  • تست کردن مراحل به‌صورت مستقل سخت‌تر می‌شود.
  • اضافه کردن Handler جدید باعث تغییر کد اصلی می‌شود.

در Chain of Responsibility:


AuthenticationHandler

AuthorizationHandler

ValidationHandler

BalanceHandler

OrderHandler

هر مرحله در کلاس جداگانه قرار می‌گیرد.


# Chain of Responsibility در Laravel

Laravel نمونه‌های زیادی از معماری زنجیره‌ای دارد.

یکی از شناخته‌شده‌ترین نمونه‌ها، Middleware است.

در Middleware، درخواست HTTP از میان چند Middleware عبور می‌کند:


Request

   ↓

TrustProxies

   ↓

Authenticate

   ↓

VerifyCsrfToken

   ↓

SubstituteBindings

   ↓

Controller

هر Middleware می‌تواند:

  • درخواست را متوقف کند.
  • آن را به Middleware بعدی بفرستد.
  • قبل و بعد از اجرای مرحله بعد کاری انجام دهد.

ساختار عمومی Middleware در Laravel به شکل زیر است:


public function handle(

    Request $request,

    Closure $next

) {

    // قبل از مرحله بعد



    $response = $next($request);



    // بعد از مرحله بعد



    return $response;

}

اگر Middleware تصمیم بگیرد درخواست را ادامه ندهد، می‌تواند مستقیماً پاسخ برگرداند:


public function handle(

    Request $request,

    Closure $next

) {

    if (!$request->user()) {

        return response()->json([

            'message' => 'Unauthenticated.',

        ], 401);

    }



    return $next($request);

}

از نظر مفهومی، Middleware و Chain of Responsibility بسیار به یکدیگر نزدیک هستند، زیرا هر دو درخواست را از میان زنجیره‌ای از پردازشگرها عبور می‌دهند.


# ساخت Middleware سفارشی در Laravel

برای ساخت Middleware می‌توان از دستور زیر استفاده کرد:


php artisan make:middleware CheckAccountBalance

سپس فایل Middleware را به شکل زیر تکمیل می‌کنیم:


namespace App\Http\Middleware;



use Closure;

use Illuminate\Http\Request;

use Symfony\Component\HttpFoundation\Response;



final class CheckAccountBalance

{

    public function handle(

        Request $request,

        Closure $next

    ): Response {

        $user = $request->user();



        if (!$user || $user->balance <= 0) {

            return response()->json([

                'message' => 'Insufficient balance.',

            ], 403);

        }



        return $next($request);

    }

}

حالا می‌توان Middleware را روی Route قرار داد:


Route::post('/orders', function () {

    return response()->json([

        'message' => 'Order created.',

    ]);

})->middleware('check.balance');

در این حالت، درخواست قبل از رسیدن به Controller یا Closure باید از Middleware عبور کند.


# Chain of Responsibility و Laravel Pipeline

Laravel علاوه بر Middleware، مفهوم Pipeline نیز دارد.

Pipeline برای عبور دادن یک مقدار یا درخواست از میان چند مرحله استفاده می‌شود.

نمونه ساده:


use Illuminate\Pipeline\Pipeline;



$result = app(Pipeline::class)

    ->send($request)

    ->through([

        AuthenticateUser::class,

        ValidateOrder::class,

        CheckBalance::class,

    ])

    ->thenReturn();

هر مرحله می‌تواند داده را تغییر دهد یا اجرای زنجیره را متوقف کند.

Pipeline برای موارد زیر کاربرد دارد:

  • پردازش سفارش
  • فیلتر داده‌ها
  • اعتبارسنجی
  • اجرای عملیات مرحله‌ای
  • پردازش گزارش‌ها
  • اعمال قوانین تجاری
  • پردازش ورودی API

# تفاوت Chain of Responsibility و Middleware

Middleware را می‌توان یکی از کاربردهای عملی ایده Chain of Responsibility دانست، اما این دو دقیقاً یک مفهوم محدود و یکسان نیستند.

Chain of Responsibility

یک Design Pattern عمومی است و می‌تواند برای هر نوع درخواست استفاده شود:

  • درخواست HTTP
  • پیام
  • عملیات مالی
  • لاگ
  • اعتبارسنجی
  • پردازش فایل

Middleware

معمولاً یک مرحله پردازشی در چرخه اجرای درخواست است و در Frameworkهایی مانند Laravel کاربرد دارد.

به‌صورت خلاصه:


Chain of Responsibility

= الگوی عمومی زنجیره پردازش



Middleware

= کاربرد رایج زنجیره پردازش در درخواست‌ها


# تفاوت Chain of Responsibility و Decorator

Chain of Responsibility و Decorator از نظر ساختار ظاهری شبیه یکدیگر هستند، زیرا هر دو ممکن است Objectها را به هم متصل کنند.

اما هدف آن‌ها متفاوت است.

Decorator

Decorator برای اضافه کردن رفتار به یک Object استفاده می‌شود.

مثلاً:


BasicLogger

   ↓

TimestampLogger

   ↓

JsonLogger

در Decorator معمولاً تمام لایه‌ها می‌توانند اجرا شوند.

Chain of Responsibility

در Chain of Responsibility هر Handler تصمیم می‌گیرد:

  • درخواست را پردازش کند.
  • یا آن را به Handler بعدی منتقل کند.

پس در Chain ممکن است فقط یکی از Handlerها درخواست را پردازش کند، یا چند Handler به‌صورت مرحله‌ای اجرا شوند.


# تفاوت Chain of Responsibility و Command

در Command، یک درخواست یا عملیات به Object تبدیل می‌شود:


SendEmailCommand

CreateOrderCommand

DeleteProductCommand

اما در Chain of Responsibility، درخواست از میان چند Handler عبور می‌کند:


AuthenticationHandler

ValidationHandler

OrderHandler

به شکل ساده:


Command

= تبدیل یک عملیات به Object



Chain of Responsibility

= عبور دادن درخواست از زنجیره Handlerها

این دو Pattern حتی می‌توانند با هم استفاده شوند.

برای مثال، هر Handler می‌تواند در نهایت یک Command را اجرا کند.


# Chain of Responsibility و اعتبارسنجی

اعتبارسنجی یکی از بهترین کاربردهای این Pattern است.

فرض کنید برای ثبت کاربر باید مراحل زیر بررسی شوند:


بررسی خالی نبودن نام

        ↓

بررسی معتبر بودن ایمیل

        ↓

بررسی قدرت رمز عبور

        ↓

بررسی تکراری نبودن ایمیل

هر مرحله می‌تواند یک Validator جداگانه باشد:


interface ValidatorInterface

{

    public function validate(array $data): bool;

}

سپس Validatorها را به شکل زنجیره‌ای اجرا می‌کنیم.

مزیت این روش این است که افزودن قانون جدید باعث تغییر تمام Validatorهای قبلی نمی‌شود.


# Chain of Responsibility در سیستم لاگ‌گیری

در یک سیستم Logging می‌توان پیام را از میان چند Handler عبور داد:


Log Message

   ↓

FileLogHandler

   ↓

DatabaseLogHandler

   ↓

EmailLogHandler

هر Handler می‌تواند بر اساس سطح لاگ تصمیم بگیرد که پیام را پردازش کند یا خیر.

مثلاً:

  • لاگ‌های عادی در فایل ذخیره شوند.
  • خطاهای مهم در دیتابیس ثبت شوند.
  • خطاهای بحرانی از طریق ایمیل ارسال شوند.

# Chain of Responsibility در سیستم کش

یک کاربرد دیگر، جست‌وجوی اطلاعات در چند منبع است:


Memory Cache

   ↓

Redis Cache

   ↓

Database

اگر اطلاعات در Memory Cache پیدا نشد، درخواست به Redis منتقل می‌شود.

اگر Redis نیز نتیجه‌ای نداشت، دیتابیس بررسی می‌شود.

نمونه ساده:


final class MemoryCacheHandler

{

    public function __construct(

        private array $data,

        private ?object $next = null

    ) {}



    public function handle(string $key): mixed

    {

        if (array_key_exists($key, $this->data)) {

            return $this->data[$key];

        }



        return $this->next?->handle($key);

    }

}

این ساختار در سیستم‌هایی که چند منبع داده با سرعت‌های متفاوت دارند، کاربردی است.


# حالت‌های مختلف Chain of Responsibility

Chain of Responsibility همیشه فقط به معنای «اولین Handler موفق» نیست.

چند مدل رایج وجود دارد.

مدل اول: اولین Handler موفق

در این مدل، هر Handler اگر بتواند درخواست را پردازش کند، نتیجه را برمی‌گرداند.


Handler 1 → موفق

درخواست دیگر به Handlerهای بعدی نمی‌رسد.

مدل دوم: عبور از تمام Handlerها

در این مدل، هر Handler بخشی از پردازش را انجام می‌دهد و سپس درخواست را ادامه می‌دهد.


Handler 1

   ↓

Handler 2

   ↓

Handler 3

Middlewareها معمولاً به این مدل نزدیک هستند.

مدل سوم: توقف در صورت خطا

در این مدل، هر Handler می‌تواند در صورت مشاهده خطا، زنجیره را متوقف کند.

این مدل برای موارد زیر مناسب است:

  • اعتبارسنجی
  • مجوز دسترسی
  • بررسی موجودی
  • بررسی امنیتی

# مزایای Chain of Responsibility در PHP

1. کاهش وابستگی بین فرستنده و پردازشگر

Client لازم نیست بداند کدام Handler درخواست را پردازش می‌کند.

2. جداسازی مسئولیت‌ها

هر Handler فقط یک بخش از منطق را مدیریت می‌کند.

3. افزایش قابلیت توسعه

می‌توان Handler جدیدی به زنجیره اضافه کرد، بدون اینکه تمام منطق قبلی تغییر کند.

4. تست‌پذیری بهتر

هر Handler را می‌توان جداگانه تست کرد.

5. انعطاف در ترتیب مراحل

می‌توان ترتیب Handlerها را تغییر داد.

6. مناسب برای قوانین مرحله‌ای

این Pattern برای فرآیندهایی که چند مرحله متوالی دارند، بسیار مناسب است.


# معایب Chain of Responsibility

1. دشوار شدن دنبال کردن مسیر درخواست

اگر زنجیره بسیار طولانی باشد، پیدا کردن Handler مسئول ممکن است سخت شود.

2. احتمال پردازش نشدن درخواست

اگر هیچ Handlerای درخواست را پردازش نکند و پاسخ پیش‌فرض نداشته باشیم، ممکن است نتیجه نامشخص باشد.

3. وابستگی به ترتیب Handlerها

گاهی تغییر ترتیب Handlerها باعث تغییر نتیجه می‌شود.

4. پیچیدگی بیش از حد در پروژه‌های کوچک

اگر فقط دو مرحله ساده داریم، استفاده از چندین کلاس ممکن است ضروری نباشد.


# اشتباهات رایج در استفاده از Chain of Responsibility

اشتباه اول: Handlerهای بسیار بزرگ

هر Handler باید یک مسئولیت مشخص داشته باشد.

اگر یک Handler چند صد خط کد داشته باشد، احتمالاً مسئولیت‌های زیادی به آن داده شده است.

اشتباه دوم: زنجیره بدون Handler نهایی

باید مشخص باشد اگر هیچ Handlerای درخواست را پردازش نکرد، چه اتفاقی رخ می‌دهد.

مثلاً:


throw new LogicException(

    'Request was not handled.'

);

اشتباه سوم: وابستگی شدید Handlerها به یکدیگر

Handler نباید جزئیات داخلی Handler بعدی را بشناسد.

اشتباه چهارم: نادیده گرفتن ترتیب اجرا

در بعضی سیستم‌ها ابتدا باید احراز هویت انجام شود و بعد اعتبارسنجی یا بررسی دسترسی.

اشتباه پنجم: استفاده برای منطق کاملاً مستقل

اگر مراحل هیچ ارتباطی با یکدیگر ندارند، شاید استفاده از یک Service معمولی مناسب‌تر باشد.


# تست Chain of Responsibility با PHPUnit

برای تست، ابتدا بررسی می‌کنیم که درخواست معتبر از تمام Handlerها عبور کند:


public function test_valid_order_request_passes_chain(): void

{

    $authentication = new AuthenticationHandler();

    $authorization = new AuthorizationHandler();

    $validation = new ValidationHandler();

    $balance = new BalanceHandler();

    $order = new OrderHandler();



    $authentication

        ->setNext($authorization)

        ->setNext($validation)

        ->setNext($balance)

        ->setNext($order);



    $request = new OrderRequest(

        userId: 10,

        productId: 25,

        quantity: 2,

        balance: 500,

    );



    $this->assertTrue(

        $authentication->handle($request)

    );

}

حالا یک درخواست نامعتبر را تست می‌کنیم:


public function test_request_with_invalid_quantity_fails(): void

{

    $this->expectException(

        InvalidArgumentException::class

    );



    $authentication = new AuthenticationHandler();

    $authorization = new AuthorizationHandler();

    $validation = new ValidationHandler();



    $authentication

        ->setNext($authorization)

        ->setNext($validation);



    $request = new OrderRequest(

        userId: 10,

        productId: 25,

        quantity: 0,

        balance: 500,

    );



    $authentication->handle($request);

}

این تست مشخص می‌کند که ValidationHandler درخواست نامعتبر را متوقف می‌کند.


# چه زمانی از Chain of Responsibility استفاده کنیم؟

این Pattern زمانی مناسب است که:

  • چند Object می‌توانند یک درخواست را پردازش کنند.
  • نمی‌خواهیم Client پردازشگر نهایی را بشناسد.
  • مراحل پردازش به ترتیب مشخص اجرا می‌شوند.
  • هر مرحله می‌تواند درخواست را متوقف کند.
  • قوانین اعتبارسنجی یا پردازش زیاد هستند.
  • می‌خواهیم Handlerها مستقل و قابل تست باشند.
  • اضافه شدن مراحل جدید باید ساده باشد.

نمونه‌های واقعی:


Authentication

Authorization

Validation

Logging

Caching

Spam Filtering

Payment Checks

Order Processing

Workflow

Middleware

Request Pipeline


# چه زمانی از Chain of Responsibility استفاده نکنیم؟

اگر فقط یک یا دو شرط ساده دارید، احتمالاً این Pattern لازم نیست.

مثلاً:


if (!$user->is_active) {

    return;

}

برای چنین شرایطی ایجاد چند Handler می‌تواند باعث پیچیدگی غیرضروری شود.

همچنین اگر تمام مراحل باید حتماً اجرا شوند و هیچ مرحله‌ای امکان توقف ندارد، شاید یک Service معمولی یا Pipeline ساده انتخاب بهتری باشد.


# جمع‌بندی

Chain of Responsibility در PHP یک الگوی طراحی رفتاری است که درخواست را از میان زنجیره‌ای از Handlerها عبور می‌دهد.

هر Handler می‌تواند:

  • درخواست را پردازش کند.
  • درخواست را به Handler بعدی منتقل کند.
  • در صورت خطا زنجیره را متوقف کند.

ساختار کلی آن به شکل زیر است:


Request

   ↓

Handler 1

   ↓

Handler 2

   ↓

Handler 3

   ↓

Final Handler

این Pattern برای سیستم‌های اعتبارسنجی، Middleware، لاگ‌گیری، کش، احراز هویت، بررسی دسترسی و پردازش مرحله‌ای بسیار کاربردی است.

با این حال نباید آن را برای هر شرط ساده‌ای استفاده کرد. زمانی که تعداد مراحل افزایش پیدا می‌کند و قرار دادن تمام منطق در یک کلاس باعث پیچیدگی می‌شود، Chain of Responsibility می‌تواند ساختار کد را خواناتر، قابل توسعه‌تر و تست‌پذیرتر کند.


# سوالات متداول درباره Chain of Responsibility در PHP

Chain of Responsibility در PHP چیست؟

Chain of Responsibility الگویی است که یک درخواست را از میان چند Handler عبور می‌دهد. هر Handler می‌تواند درخواست را پردازش کند یا آن را به Handler بعدی منتقل کند.

Handler در Chain of Responsibility چه وظیفه‌ای دارد؟

Handler مسئول اجرای یک بخش مشخص از منطق است. این Handler می‌تواند درخواست را بپذیرد، رد کند یا به Handler بعدی ارسال کند.

آیا Middleware در Laravel همان Chain of Responsibility است؟

Middleware در Laravel نمونه‌ای عملی از ایده زنجیره پردازش است. هر Middleware می‌تواند درخواست را بررسی کند و در صورت موفقیت آن را با $next($request) به مرحله بعد بفرستد.

تفاوت Chain of Responsibility و Decorator چیست؟

Decorator معمولاً برای افزودن رفتار به یک Object استفاده می‌شود، اما Chain of Responsibility برای عبور دادن درخواست از میان چند Handler طراحی شده است.

تفاوت Chain of Responsibility و Command چیست؟

Command یک عملیات را به Object تبدیل می‌کند، اما Chain of Responsibility یک درخواست را از میان چند پردازشگر عبور می‌دهد.

آیا Chain of Responsibility در Laravel کاربرد دارد؟

بله. Middleware، Pipeline، اعتبارسنجی، پردازش سفارش و اجرای قوانین تجاری از کاربردهای مناسب آن در Laravel هستند.

آیا استفاده از Chain of Responsibility باعث حذف کامل if/else می‌شود؟

خیر. هدف Pattern حذف تمام شرط‌ها نیست، بلکه انتقال هر شرط به Handler مسئول خودش است تا منطق در یک کلاس بزرگ متمرکز نشود.

آیا Chain of Responsibility برای پروژه‌های کوچک مناسب است؟

اگر پروژه فقط چند شرط ساده دارد، استفاده از آن ممکن است بیش از حد پیچیده باشد. این Pattern بیشتر برای فرآیندهای چندمرحله‌ای و قابل توسعه مناسب است.

نظرات (0)

نظرات پس از تأیید مدیر نمایش داده می‌شوند

هنوز نظری ثبت نشده است؛ اولین نفر باشید!

مقالات مرتبط

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

۲۴ دقیقه
ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

۲۲ دقیقه
ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

۲۲ دقیقه
مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

۲۲ دقیقه