03강: 파사드(Facade)의 가면을 벗기다 — Payment 커스텀 파사드 제작
실습 프로젝트: 지니샵(JinyShop) 온라인 도서 쇼핑몰
주제: 정적 문법 뒤에 숨겨진 동적 컨테이너 인스턴스,Facade와__callStatic(), 서점 결제 전용Payment커스텀 파사드
등장인물: 🐱 지니(시니어 멘토), 👧 도로시(주니어 개발자), 🐶 토토(개발 보조견)
공식 문서 연계: 파사드 (Facades)
💬 1. 지니와 도로시의 티키타카 회의
👧 도로시: “지니! 라라벨 코드를 볼 때마다 항상 풀리지 않던 수수께끼가 있어요! Route::get(), Cache::put(), Log::info() 처럼 클래스를 new로 생성하지도 않고 정적 메서드(::)로 부르는데, 뒤에서는 어떻게 데이터베이스 연결이나 캐시 객체가 정상적으로 돌아가는 거예요? 순수 정적(Static) 클래스는 테스트하기도 힘들다고 들었는데 라라벨은 어떻게 파사드를 테스트하죠?”
🐱 지니: “후후, 도로시가 드디어 라라벨의 가장 신비로운 마법의 가면을 벗겨낼 준비가 되었구나! 파사드(Facade, 건축물의 정면/가면)는 진짜 정적 클래스가 아니란다! PHP의 __callStatic() 마술을 이용해, 정적 호출이 들어오는 순간 가면 뒤에 숨어있는 진짜 서비스 컨테이너 인스턴스를 꺼내어 대신 일하게 만드는 프록시(Proxy) 통로란다! 덕분에 개발자는 간결한 문법을 누리면서도 테스트할 때는 Payment::shouldReceive()로 완벽한 가짜 객체 모킹을 할 수 있지!”
🐶 토토: “멍멍! 우리 지니샵에도 Payment::charge() 한 줄로 결제를 실행하는 멋진 커스텀 파사드를 만들어보자멍!”
🎯 2. 실습 목표 및 서점 시나리오
- 라라벨 파사드의 내부 동작 메커니즘(
__callStatic$\rightarrow$getFacadeAccessor$\rightarrow$ 컨테이너 해결)을 완전히 파헤칩니다. - 서점 결제 모듈을 정적 문법으로 편리하게 호출할 수 있는
App\Facades\Payment커스텀 파사드를 만듭니다. - 파사드의 강력한 테스트 모킹(
Payment::shouldReceive())을 실습합니다.
🛠️ 3. 단계별 실습 절차 (Step-by-Step)
[Step 1] 파사드 동작 원리 4단계 다이어그램
[개발자 코드] Payment::charge(35000, $orderData)
│
▼
[1. PHP 매직 메서드] Facade::__callStatic('charge', [35000, ...])
│
▼
[2. 접근자 확인] Payment::getFacadeAccessor() ➔ 'App\Contracts\PaymentGatewayContract'
│
▼
[3. 컨테이너 해결] app()->make('App\Contracts\PaymentGatewayContract') ➔ MockPaymentGateway 인스턴스 획득!
│
▼
[4. 동적 메서드 실행] $mockPaymentGateway->charge(35000, $orderData) ➔ 결과 반환!
[Step 2] Payment 커스텀 파사드 작성
app/Facades/Payment.php 파일을 생성합니다:
namespace App\Facades;
use App\Contracts\PaymentGatewayContract;
use Illuminate\Support\Facades\Facade;
/**
* @method static \App\Contracts\PaymentResult charge(int $amount, array $orderData)
* @method static bool refund(string $transactionId, int $amount)
*
* @see \App\Contracts\PaymentGatewayContract
*/
class Payment extends Facade
{
/**
* 서비스 컨테이너에서 찾아올 바인딩 키 또는 인터페이스 반환
*/
protected static function getFacadeAccessor(): string
{
return PaymentGatewayContract::class;
}
}
지니의 감탄:
단 10줄의 코드만으로 완벽한 파사드가 완성되었단다! 상단의 PHPDoc 주석을 달아두면 VS Code나 PhpStorm 에디터에서 자동완성까지 완벽하게 지원하지!
[Step 3] 컨트롤러에서 우아한 한 줄 결제 호출
컨트롤러에서 생성자 주입이나 번거로운 코드 없이 파사드로 깔끔하게 결제를 집행합니다:
// app/Http/Controllers/CheckoutController.php
use App\Facades\Payment; // 우리가 만든 커스텀 파사드!
use App\Services\CartService;
use Illuminate\Http\Request;
class CheckoutController extends Controller
{
public function process(Request $request, CartService $cart)
{
// 🚀 파사드를 통한 극도로 간결한 결제 실행!
$result = Payment::charge($cart->totalPrice(), [
'order_name' => '지니샵 도서 주문',
]);
if (! $result->success) {
return back()->with('error', $result->errorMessage);
}
$cart->clear();
return redirect()->route('home')->with('success', '주문 완료!');
}
}
[Step 4] Tinker에서 파사드 테스트
php artisan tinker
use App\Facades\Payment;
$result = Payment::charge(45000, ['order_id' => 99]);
echo "결제 결과: " . ($result->success ? '성공' : '실패') . PHP_EOL;
echo "거래 번호: " . $result->transactionId . PHP_EOL;
🔍 4. 라라벨 공식 문서 원리 심층 분석 (Deep Dive)
파사드가 단위 테스트에서 사랑받는 이유
순수 정적 클래스는 테스트 시 메서드를 가로채거나 가짜 값으로 모킹(Mocking)하기가 극도로 어렵습니다.
하지만 라라벨 파사드는 뒤에 실제 객체가 숨어있기 때문에, 테스트 코드에서 다음과 같이 손쉽게 가짜 결제를 흉내 낼 수 있습니다:
// 테스트 코드에서 실제 결제 모듈 실행을 가짜로 가로챔!
Payment::shouldReceive('charge')
->once()
->with(35000, Mockery::any())
->andReturn(new PaymentResult(true, 'TEST-TX-1234', 35000));
🐶 5. 토토의 트러블슈팅 & 주의사항
🐶 토토의 멍멍 가이드!
- 실시간 파사드(Real-time Facades)란 무엇인가요?
파사드 파일을 따로 만들지 않아도, 네임스페이스 앞에Facades\만 붙이면 라라벨이 런타임에 파사드를 즉석에서 가상 생성해 준다멍! (예:Facades\App\Services\CartService::totalPrice())
💡 6. 3강 자가진단 과제
Payment파사드에refund()메서드를 호출하는 테스트 코드를 작성해 보세요.- Tinker에서
Payment::getFacadeRoot()를 호출하여 파사드 뒤에 숨어있는 실제 구현체 객체가 무엇인지 눈으로 확인하세요.