در پروژههای PHP و Laravel معمولاً با بخشهایی روبهرو میشویم که برای انجام یک عملیات ساده، باید چندین کلاس و سرویس مختلف را فراخوانی کنیم.
برای مثال، تکمیل یک سفارش ممکن است به عملیات زیر نیاز داشته باشد:
- بررسی موجودی محصول
- محاسبه مبلغ نهایی
- ثبت سفارش
- پرداخت
- کاهش موجودی
- ارسال اعلان
- ایجاد فاکتور
اگر Controller یا Service اصلی مستقیماً تمام این کلاسها را فراخوانی کند، کد پروژه به مرور پیچیده و سختنگهداری میشود.
در چنین شرایطی میتوانیم از Facade Pattern در PHP استفاده کنیم.
Facade یک Interface ساده در اختیار بخشهای دیگر برنامه قرار میدهد و جزئیات اجرای عملیات پیچیده را در پشت خود پنهان میکند.
Facade Pattern در PHP چیست؟
Facade Pattern یکی از الگوهای طراحی ساختاری یا Structural Design Patterns است.
هدف این الگو، ایجاد یک نقطه ورود ساده برای استفاده از یک سیستم پیچیده است.
در این الگو، به جای اینکه کد اصلی برنامه مستقیماً با چندین کلاس ارتباط داشته باشد، فقط با یک کلاس Facade کار میکند.
برای مثال:
کد اصلی برنامه
↓
OrderFacade
↓
InventoryService
PaymentService
InvoiceService
NotificationService
کد اصلی فقط OrderFacade را میشناسد و نیازی ندارد جزئیات سرویسهای داخلی را بداند.
مثال ساده برای درک Facade
فرض کنید برای روشن کردن یک سیستم سینمای خانگی باید کارهای زیر را انجام دهیم:
- روشن کردن تلویزیون
- روشن کردن دستگاه پخش
- تنظیم صدا
- انتخاب ورودی HDMI
- اجرای فیلم
اگر کاربر مجبور باشد همه این عملیات را بهصورت جداگانه انجام دهد، استفاده از سیستم دشوار میشود.
Facade میتواند همه این عملیات را پشت یک متد ساده قرار دهد:
$homeTheater->watchMovie();
در این مثال، کاربر فقط یک متد را صدا میزند، اما Facade در پشت صحنه چندین عملیات را اجرا میکند.
مشکل استفاده مستقیم از چند سرویس
فرض کنید یک سیستم ثبت سفارش داریم:
class InventoryService
{
public function check(int $productId, int $quantity): bool
{
return true;
}
public function decrease(int $productId, int $quantity): void
{
// کاهش موجودی
}
}
سرویس پرداخت:
class PaymentService
{
public function pay(int $amount): bool
{
return true;
}
}
سرویس فاکتور:
class InvoiceService
{
public function create(int $orderId): void
{
// ایجاد فاکتور
}
}
سرویس اعلان:
class NotificationService
{
public function send(string $message): void
{
// ارسال اعلان
}
}
حالا اگر Controller بخواهد سفارش را تکمیل کند، باید تمام این سرویسها را بشناسد:
$inventory = new InventoryService();
$payment = new PaymentService();
$invoice = new InvoiceService();
$notification = new NotificationService();
$inventory->check(10, 2);
$payment->pay(500000);
$inventory->decrease(10, 2);
$invoice->create(100);
$notification->send('سفارش با موفقیت ثبت شد.');
این کد چند مشکل دارد:
- Controller به جزئیات داخلی سیستم وابسته است.
- ترتیب اجرای عملیات در چند بخش پروژه تکرار میشود.
- تغییر سرویسها دشوارتر میشود.
- تست کردن عملیات تکمیل سفارش پیچیدهتر خواهد شد.
- منطق مربوط به سفارش در Controller قرار میگیرد.
برای حل این مشکل یک Facade میسازیم.
ساخت OrderFacade در PHP
class OrderFacade
{
public function __construct(
private InventoryService $inventory,
private PaymentService $payment,
private InvoiceService $invoice,
private NotificationService $notification
) {
}
public function completeOrder(
int $productId,
int $quantity,
int $amount,
int $orderId
): bool {
$hasStock = $this->inventory->check(
$productId,
$quantity
);
if (!$hasStock) {
return false;
}
$paid = $this->payment->pay($amount);
if (!$paid) {
return false;
}
$this->inventory->decrease(
$productId,
$quantity
);
$this->invoice->create($orderId);
$this->notification->send(
'سفارش با موفقیت تکمیل شد.'
);
return true;
}
}
اکنون تمام عملیات مرتبط با تکمیل سفارش داخل OrderFacade قرار گرفته است.
کد اصلی برنامه دیگر نیازی ندارد با چهار سرویس مختلف کار کند.
استفاده از OrderFacade
$facade = new OrderFacade(
new InventoryService(),
new PaymentService(),
new InvoiceService(),
new NotificationService()
);
$result = $facade->completeOrder(
productId: 10,
quantity: 2,
amount: 500000,
orderId: 100
);
در اینجا یک متد ساده، چند عملیات داخلی را اجرا میکند.
از دید Controller، فقط یک عملیات با نام completeOrder() وجود دارد.
ساخت Facade با Interface
برای افزایش انعطافپذیری بهتر است سرویسهای داخلی به Interface وابسته باشند.
ابتدا Interface موجودی را تعریف میکنیم:
interface InventoryInterface
{
public function check(
int $productId,
int $quantity
): bool;
public function decrease(
int $productId,
int $quantity
): void;
}
Interface پرداخت:
interface PaymentInterface
{
public function pay(int $amount): bool;
}
Interface اعلان:
interface NotificationInterface
{
public function send(string $message): void;
}
اکنون Facade به جای کلاسهای مشخص، به قراردادها وابسته میشود:
class OrderFacade
{
public function __construct(
private InventoryInterface $inventory,
private PaymentInterface $payment,
private NotificationInterface $notification
) {
}
public function completeOrder(
int $productId,
int $quantity,
int $amount
): bool {
if (!$this->inventory->check(
$productId,
$quantity
)) {
return false;
}
if (!$this->payment->pay($amount)) {
return false;
}
$this->inventory->decrease(
$productId,
$quantity
);
$this->notification->send(
'سفارش با موفقیت تکمیل شد.'
);
return true;
}
}
این روش تستپذیری و قابلیت تغییر سیستم را بهتر میکند.
Facade و اصل Single Responsibility
Facade نباید به یک کلاس بسیار بزرگ تبدیل شود.
وظیفه اصلی Facade این است که:
- عملیات مرتبط را هماهنگ کند.
- Interface ساده ارائه دهد.
- جزئیات غیرضروری را از کد اصلی پنهان کند.
- ترتیب اجرای سرویسها را مدیریت کند.
اما اگر Facade خودش تمام منطق محاسبه قیمت، قوانین تخفیف، مدیریت موجودی و ثبت تراکنش را پیادهسازی کند، احتمالاً مسئولیتهای زیادی پیدا کرده است.
بهتر است منطق تخصصی در سرویسهای مربوطه قرار داشته باشد و Facade فقط آنها را هماهنگ کند.
تفاوت Facade و Adapter
Facade و Adapter هر دو میتوانند یک لایه واسط بین کد اصلی و کلاسهای دیگر ایجاد کنند، اما هدف آنها متفاوت است.
Adapter
Adapter برای سازگار کردن دو Interface متفاوت استفاده میشود.
مثال:
Interface برنامه: send()
Interface سرویس: sendMessage()
Adapter این دو Interface را به هم متصل میکند.
Facade
Facade برای ساده کردن استفاده از چند کلاس یا یک سیستم پیچیده به کار میرود.
مثال:
completeOrder()
↓
checkStock()
pay()
createInvoice()
sendNotification()
خلاصه:
- Adapter، Interface را تبدیل میکند.
- Facade، استفاده از چند سرویس را ساده میکند.
تفاوت Facade و Decorator
Decorator همان Interface اصلی را حفظ میکند و رفتار جدیدی به یک Object اضافه میکند.
برای مثال:
PaymentService
↓
LoggingDecorator
↓
CachingDecorator
اما Facade ممکن است چند سرویس کاملاً متفاوت را پشت یک متد قرار دهد:
OrderFacade
↓
InventoryService
PaymentService
InvoiceService
بنابراین:
- Decorator رفتار یک Object را توسعه میدهد.
- Facade استفاده از چند Object را ساده میکند.
تفاوت Facade و Factory
Factory برای ساخت یا انتخاب یک Object استفاده میشود.
مثال:
$gateway = PaymentGatewayFactory::make('zarinpal');
Facade برای اجرای یک عملیات ساده روی چند سرویس استفاده میشود.
ممکن است Facade در داخل خود از Factory نیز استفاده کند، اما این دو الگو مسئولیت یکسانی ندارند.
تفاوت Facade و Service Layer
Facade و Service Layer در برخی پروژهها به هم شبیه هستند.
Service Layer معمولاً منطق یک Use Case یا عملیات کسبوکار را مدیریت میکند.
Facade بیشتر روی سادهسازی دسترسی به یک زیرسیستم تمرکز دارد.
در عمل، یک کلاس میتواند هم نقش Service Layer و هم نقش Facade را داشته باشد؛ اما بهتر است هدف و مسئولیت آن شفاف باشد.
استفاده از Facade در Laravel
Laravel یک مفهوم معروف به نام Facade دارد که اجازه میدهد به سرویسهای ثبتشده در Service Container از طریق Interfaceهای استاتیکمانند دسترسی پیدا کنیم.
برای مثال:
use Illuminate\Support\Facades\Cache;
Cache::put('name', 'Ali', 3600);
یا:
use Illuminate\Support\Facades\Log;
Log::info('Order created.');
این Syntax شبیه استفاده از متدهای Static است، اما Facadeهای Laravel معمولاً به یک Object واقعی در Service Container متصل میشوند.
بنابراین Facade در Laravel با یک کلاس Static ساده تفاوت دارد.
ساخت Facade اختصاصی در Laravel
فرض کنید یک سرویس برای مدیریت سفارش داریم:
namespace App\Services;
class OrderManager
{
public function create(int $userId, int $amount): array
{
return [
'user_id' => $userId,
'amount' => $amount,
'status' => 'pending',
];
}
}
برای دسترسی سادهتر میتوانیم یک Facade اختصاصی ایجاد کنیم:
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Order extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'order.manager';
}
}
سپس سرویس را در Service Provider ثبت میکنیم:
$this->app->singleton(
'order.manager',
function () {
return new \App\Services\OrderManager();
}
);
اکنون میتوانیم از Facade استفاده کنیم:
use App\Facades\Order;
$order = Order::create(
userId: 10,
amount: 500000
);
در پشت صحنه، Laravel متد create() را روی نمونه OrderManager اجرا میکند.
چه زمانی Facade اختصاصی در Laravel مناسب است؟
Facade اختصاصی میتواند در شرایط زیر مفید باشد:
- یک سرویس پرکاربرد در بخشهای مختلف پروژه وجود دارد.
- میخواهیم API سادهتری برای یک زیرسیستم ارائه کنیم.
- نیاز داریم چند سرویس مرتبط پشت یک نقطه ورود قرار بگیرند.
- میخواهیم نحوه دسترسی به سرویس را استاندارد کنیم.
با این حال، استفاده بیش از حد از Facadeهای اختصاصی میتواند وابستگیهای پنهان ایجاد کند.
در سرویسهای پیچیده و قابل تست، تزریق مستقیم Interface از طریق Constructor معمولاً شفافتر است.
Facade و Dependency Injection
Facade میتواند با Dependency Injection نیز استفاده شود.
برای مثال:
class OrderFacade
{
public function __construct(
private PaymentInterface $payment,
private InventoryInterface $inventory
) {
}
}
در این روش، وابستگیها صریح هستند و تست کردن Facade سادهتر میشود.
در مقابل، اگر Facade مستقیماً همه سرویسها را داخل متدهای خود بسازد، وابستگیها پنهان میشوند:
class OrderFacade
{
public function completeOrder(): bool
{
$payment = new PaymentService();
$inventory = new InventoryService();
// ...
}
}
این روش معمولاً پیشنهاد نمیشود؛ زیرا تغییر و تست کد را دشوارتر میکند.
Facade در سیستم پرداخت
یک مثال کاربردی دیگر، ساخت Facade برای پرداخت است.
پرداخت ممکن است شامل مراحل زیر باشد:
- بررسی وضعیت سفارش
- محاسبه مبلغ
- ایجاد تراکنش
- ارسال درخواست به درگاه
- ذخیره شناسه پرداخت
- ثبت لاگ
- ارسال اعلان
Facade میتواند این مراحل را هماهنگ کند:
class PaymentFacade
{
public function __construct(
private OrderService $orders,
private TransactionService $transactions,
private PaymentGateway $gateway,
private NotificationService $notifications
) {
}
public function payOrder(int $orderId): bool
{
$order = $this->orders->find($orderId);
if (!$order) {
return false;
}
$transaction = $this->transactions->create(
$orderId,
$order->amount
);
$result = $this->gateway->pay(
$order->amount
);
if (!$result->isSuccessful()) {
return false;
}
$this->transactions->markAsPaid(
$transaction->id
);
$this->notifications->send(
'پرداخت با موفقیت انجام شد.'
);
return true;
}
}
در این ساختار، Controller فقط یک متد را فراخوانی میکند:
$paymentFacade->payOrder($orderId);
مدیریت تراکنش در Facade
اگر Facade چند عملیات مرتبط با دیتابیس را اجرا میکند، بهتر است تراکنش دیتابیس در لایه مناسب مدیریت شود.
در Laravel میتوان از DB::transaction() استفاده کرد:
use Illuminate\Support\Facades\DB;
DB::transaction(function () use ($orderId) {
// ثبت سفارش
// ثبت تراکنش
// کاهش موجودی
});
اما باید توجه داشت که تراکنش دیتابیس، عملیات خارجی مانند درخواست به درگاه پرداخت یا ارسال پیامک را بهصورت خودکار Rollback نمیکند.
بنابراین در سیستمهای پرداخت بهتر است وضعیتهای مختلف عملیات بهدرستی طراحی شوند و برای عملیات خارجی از الگوهایی مانند:
- وضعیت Pending
- Retry
- Queue
- Outbox Pattern
- Idempotency
- ثبت لاگ دقیق
استفاده شود.
اشتباهات رایج در طراحی Facade
۱. تبدیل Facade به کلاس بزرگ
Facade نباید تمام منطق پروژه را در خود جای دهد.
۲. ساخت مستقیم وابستگیها
بهتر است سرویسهای موردنیاز از طریق Constructor تزریق شوند.
۳. ایجاد وابستگی پنهان
Facadeهای Static ممکن است باعث شوند وابستگیهای یک کلاس در Constructor مشخص نباشند.
۴. استفاده از Facade به جای همه سرویسها
هر سرویس کوچک و سادهای نیاز به Facade ندارد.
۵. ترکیب مسئولیتهای نامرتبط
یک Facade نباید همزمان مدیریت سفارش، احراز هویت، گزارشگیری و تنظیمات سیستم را انجام دهد؛ مگر اینکه واقعاً یک زیرسیستم مشترک داشته باشند.
۶. نادیده گرفتن خطاهای داخلی
Facade باید نتیجه عملیات را به شکل مشخص برگرداند و خطاها را پنهان نکند.
۷. قرار دادن عملیات خارجی داخل تراکنش بدون طراحی مناسب
ارسال درخواست به API خارجی داخل تراکنش دیتابیس میتواند باعث طولانی شدن تراکنش و ایجاد ناسازگاری شود.
مزایای Facade Pattern در PHP
مهمترین مزایای Facade Pattern عبارتاند از:
- سادهتر شدن استفاده از سیستمهای پیچیده
- کاهش وابستگی Controllerها
- پنهان کردن جزئیات غیرضروری
- ایجاد نقطه ورود مشخص برای یک زیرسیستم
- افزایش خوانایی کد
- جلوگیری از تکرار ترتیب اجرای عملیات
- سادهتر شدن تغییرات داخلی
- مناسب بودن برای Use Caseهای پیچیده
- امکان ترکیب چند سرویس مرتبط
معایب Facade Pattern
Facade معایبی نیز دارد:
- ممکن است به یک کلاس بزرگ تبدیل شود.
- جزئیات مهم سیستم بیش از حد پنهان شوند.
- استفاده بیش از حد از Facadeهای Static تست را دشوار کند.
- وابستگیهای پنهان ایجاد شود.
- ممکن است یک Facade به نقطهای تبدیل شود که تمام منطق پروژه از آن عبور میکند.
چه زمانی از Facade Pattern استفاده کنیم؟
Facade در شرایط زیر انتخاب مناسبی است:
- برای انجام یک عملیات ساده به چند کلاس نیاز داریم.
- Controllerها بیش از حد به سرویسهای داخلی وابسته شدهاند.
- یک زیرسیستم API پیچیدهای دارد.
- ترتیب اجرای چند عملیات در بخشهای مختلف تکرار میشود.
- میخواهیم یک Use Case مشخص را پشت یک متد قرار دهیم.
- قصد داریم دسترسی به یک سرویس پیچیده را ساده کنیم.
چه زمانی از Facade استفاده نکنیم؟
اگر فقط میخواهیم Interface یک کلاس را به Interface دیگری تبدیل کنیم، Adapter مناسبتر است.
اگر هدف افزودن لاگ، کش یا اعتبارسنجی به یک Object است، Decorator انتخاب بهتری خواهد بود.
اگر فقط یک کلاس ساده داریم و استفاده از آن پیچیدگی خاصی ندارد، ساخت Facade اضافی ضرورتی ندارد.
جمعبندی
Facade Pattern در PHP یک الگوی طراحی ساختاری است که یک Interface ساده برای استفاده از سیستمهای پیچیده فراهم میکند.
در این الگو، Facade چند سرویس مرتبط را هماهنگ میکند و جزئیات اجرای آنها را از کد اصلی برنامه پنهان میسازد.
در این مقاله با موارد زیر آشنا شدیم:
- مفهوم Facade Pattern در PHP
- ساخت Facade برای سیستم سفارش
- استفاده از Interface در Facade
- تفاوت Facade با Adapter
- تفاوت Facade با Decorator و Factory
- استفاده از Facade در Laravel
- ساخت Facade اختصاصی در Laravel
- ارتباط Facade با Dependency Injection
- مدیریت تراکنش در Facade
- مزایا، معایب و اشتباهات رایج
Facade زمانی ارزشمند است که یک سیستم پیچیده را پشت یک Interface ساده قرار دهد؛ اما نباید به بهانه سادهسازی، تمام منطق پروژه را در یک کلاس بزرگ جمع کنیم.
# سوالات متداول درباره Facade Pattern در PHP
Facade Pattern در PHP چیست؟
Facade Pattern الگویی ساختاری است که یک Interface ساده برای استفاده از چند کلاس یا یک سیستم پیچیده فراهم میکند.
تفاوت Facade و Adapter چیست؟
Adapter برای سازگار کردن Interfaceهای متفاوت استفاده میشود؛ اما Facade برای ساده کردن استفاده از چند سرویس یا یک زیرسیستم پیچیده به کار میرود.
آیا Facade در Laravel با Static Method یکسان است؟
خیر. Facadeهای Laravel معمولاً به Objectهای ثبتشده در Service Container متصل میشوند و فقط ظاهری شبیه متدهای Static دارند.
آیا استفاده از Facade در Laravel خوب است؟
بله، در موارد مناسب میتواند خوانایی کد را افزایش دهد؛ اما استفاده بیش از حد ممکن است وابستگیهای پنهان ایجاد کند.
آیا Facade باید از Interface استفاده کند؟
اجباری نیست، اما استفاده از Interface و Dependency Injection معمولاً باعث افزایش تستپذیری و انعطافپذیری میشود.
آیا Facade جایگزین Service Layer است؟
همیشه نه. Facade بیشتر برای سادهسازی دسترسی به یک زیرسیستم استفاده میشود؛ در حالی که Service Layer معمولاً یک عملیات یا Use Case تجاری را مدیریت میکند.
آیا میتوان چند Facade را با هم ترکیب کرد؟
بله، اما باید مراقب ایجاد زنجیرههای پیچیده و وابستگیهای پنهان بود.
آیا Facade برای پروژههای کوچک مناسب است؟
اگر سیستم پیچیدهای وجود ندارد، استفاده از Facade ممکن است فقط پیچیدگی اضافه ایجاد کند. این الگو بیشتر برای هماهنگ کردن چند سرویس مرتبط مفید است.





