در پروژههای PHP معمولاً درخواستها باید از چند مرحله مختلف عبور کنند. برای مثال، قبل از اینکه یک کاربر بتواند سفارشی ثبت کند، ممکن است لازم باشد مراحل زیر انجام شوند:
- بررسی ورود کاربر
- بررسی سطح دسترسی
- اعتبارسنجی اطلاعات
- بررسی موجودی حساب
- ثبت لاگ
- اجرای عملیات اصلی
یک پیادهسازی ساده ممکن است تمام این مراحل را در یک 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 در نهایت مسئول پردازش آن خواهد بود. citeturn0search0
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 بیشتر برای فرآیندهای چندمرحلهای و قابل توسعه مناسب است.





