# PHP Laravel 프레임워크 면접 질문 2026: Eloquent, 큐, 아키텍처 패턴 완벽 가이드 > 2026년 Laravel 기술 면접에서 자주 출제되는 Eloquent ORM, 큐 설계, 서비스 컨테이너 관련 질문과 답변 예시를 상세히 설명합니다. N+1 문제 해결부터 잡 배칭, DI 패턴까지 포괄적으로 다룹니다. - Published: 2026-09-16 - Updated: 2026-09-16 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- PHP Laravel 프레임워크 면접 질문은 단순한 문법 지식 이상을 요구합니다. 2026년에 채용을 진행하는 기업들은 Eloquent 관계 최적화, 큐 장애 처리, 서비스 컨테이너 바인딩에 대해 프로덕션 수준의 상세한 설명을 기대합니다. Laravel 13에서는 AI 도구, JSON:API 리소스, 벡터 검색 기능이 도입되어 기술 면접에 새로운 차원이 추가되었습니다. > **면접관이 평가하는 핵심 영역** > > 시니어 Laravel 포지션에서는 세 가지 영역에 초점을 맞춥니다: Eloquent 성능(N+1 방지, 청킹, 커서 페이지네이션), 큐 신뢰성(재시도, 데드 레터 처리, 잡 배칭), 그리고 아키텍처 결정(서비스 프로바이더, 파사드 vs 인젝션, 리포지토리 패턴). ## Eloquent ORM: 관계 로딩과 쿼리 최적화 Eloquent 면접 질문에서는 N+1 문제가 일관되게 다뤄집니다. 지원자는 `with()`, `load()`, `loadMissing()`을 언제 사용하는지, 그리고 대규모 데이터셋의 이거 로딩에서 메모리 트레이드오프를 설명할 수 있어야 합니다. ```php // UserController.php // 불필요한 데이터 로딩을 피하기 위해 제약 조건과 함께 이거 로드 $users = User::query() ->with(['posts' => function (Builder $query) { $query->where('published', true) ->select('id', 'user_id', 'title', 'published_at'); }]) ->withCount('posts') ->paginate(25); // 잘못된 예: 루프 내에서 posts에 접근하면 N+1 발생 foreach ($users as $user) { $user->posts; // 각 반복마다 데이터베이스에 쿼리 실행 } ``` `withCount()` 메서드는 관련 레코드를 로드하지 않고 카운트하는 서브쿼리를 추가합니다. 이를 통해 목록 페이지에서 카운트를 표시할 때 메모리 오버헤드를 방지할 수 있습니다. 접근되지 않을 수 있는 관계에는 `loadMissing()`을 사용하여 상위에서 이미 이거 로드된 경우 중복 쿼리를 방지할 수 있습니다. 자주 나오는 후속 질문으로 커서 페이지네이션과 오프셋 페이지네이션의 차이가 있습니다. 커서 페이지네이션은 `cursorPaginate()`를 사용하며, 대규모 테이블에서 오프셋 페이지네이션이 겪는 급격한 성능 저하를 방지합니다. 트레이드오프로, 커서 페이지네이션에서는 임의 페이지로의 점프가 불가능합니다. ## 고급 Eloquent: 다형성 관계와 쿼리 스코프 다형성 관계에서는 morph 맵과 인덱스 전략에 대한 정확한 이해가 필요합니다. 면접관은 게시물, 동영상, 제품에 속하는 댓글 시나리오를 제시하고 지원자에게 쿼리 최적화를 요청하는 경우가 많습니다. ```php // Comment.php class Comment extends Model { // morphTo는 commentable_type 열에서 부모 타입을 자동으로 해석 public function commentable(): MorphTo { return $this->morphTo(); } } // AppServiceProvider.php // morph 맵은 데이터베이스에 클래스명 노출을 방지하고 쿼리 성능을 향상 Relation::enforceMorphMap([ 'post' => Post::class, 'video' => Video::class, 'product' => Product::class, ]); ``` morph 맵은 두 가지 목적을 제공합니다: 데이터베이스 값을 클래스명에서 분리하고(마이그레이션 없이 리팩토링 가능), 더 짧고 인덱싱 가능한 문자열을 생성합니다. morph 맵이 없으면 Laravel은 `App\Models\Post`와 같은 전체 클래스명을 `commentable_type` 열에 저장합니다. 쿼리 스코프는 재사용 가능한 쿼리 로직의 이해를 보여줍니다. 글로벌 스코프는 자동으로 적용되고, 로컬 스코프는 명시적 호출이 필요합니다. 전형적인 면접 질문에서는 각각을 언제 사용하는지 묻습니다. ```php // Post.php // 글로벌 스코프: 명시적으로 제거하지 않는 한 모든 쿼리에 적용 protected static function booted(): void { static::addGlobalScope('published', function (Builder $builder) { $builder->where('published', true); }); } // 로컬 스코프: ->popular()로 명시적으로 호출 public function scopePopular(Builder $query, int $minViews = 1000): Builder { return $query->where('views', '>=', $minViews); } // 사용 예: Post::popular(5000)->get() // 글로벌 스코프 제거: Post::withoutGlobalScope('published')->get() ``` ## 큐 아키텍처: 잡 설계와 장애 처리 큐 관련 질문에서는 프로덕션 대응 능력이 평가됩니다. Laravel 13.31에서는 연결의 모든 잡을 카운트하는 `Queue::totalSize()`와 그레이스풀 셧다운 처리를 위한 `JobInterrupted` 이벤트가 추가되었습니다. 지원자는 sync, database, Redis, SQS 드라이버의 차이점과 각각이 적합한 상황을 이해해야 합니다. Laravel 큐 패턴에 대한 더 자세한 내용은 [큐 & 잡 면접 질문](/technologies/laravel/interview-questions/queues-jobs) 모듈을 참조하시기 바랍니다. ```php // ProcessOrder.php class ProcessOrder implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public int $tries = 3; // 실패 전 최대 시도 횟수 public int $backoff = 60; // 재시도 간 초 public int $timeout = 120; // 최대 실행 시간 public int $maxExceptions = 2; // 2회 미처리 예외 후 실패 public function __construct( public readonly Order $order ) {} public function handle(PaymentGateway $gateway): void { // SerializesModels는 잡 실행 시 Order를 데이터베이스에서 새로 가져옴 $gateway->charge($this->order); } public function failed(Throwable $exception): void { // 모든 재시도 실패 후 호출 - 팀 알림, 환불 등 Log::critical('Order processing failed', [ 'order_id' => $this->order->id, 'exception' => $exception->getMessage(), ]); } } ``` `SerializesModels` 트레이트는 전체 모델이 아닌 모델 ID만 큐 페이로드에 저장합니다. 잡 실행 시 Eloquent가 새 인스턴스를 가져옵니다. 이를 통해 오래된 데이터를 방지할 수 있지만, 삭제된 모델은 `ModelNotFoundException`을 발생시킵니다. 이는 `deleteWhenMissingModels`로 처리합니다. 잡 배칭 관련 질문도 자주 출제됩니다. 배치는 잡을 그룹화하고 완료, 실패, 취소 시 콜백을 정의할 수 있습니다. ```php // OrderBatchController.php public function processBatch(array $orderIds): PendingBatch { $jobs = collect($orderIds) ->map(fn (int $id) => new ProcessOrder(Order::find($id))); return Bus::batch($jobs) ->name('Process Orders ' . now()->toDateString()) ->allowFailures() // 일부 잡이 실패해도 배치 계속 ->then(function (Batch $batch) { // 모든 잡이 성공적으로 완료 Notification::send(Admin::all(), new BatchCompleted($batch)); }) ->catch(function (Batch $batch, Throwable $e) { // 배치 내 첫 번째 실패 Log::warning('Batch job failed', ['batch_id' => $batch->id]); }) ->finally(function (Batch $batch) { // 배치 종료(성공 또는 실패) }) ->dispatch(); } ``` ## 서비스 컨테이너: 바인딩, 컨텍스트 인젝션, 프로바이더 [서비스 컨테이너](/technologies/laravel/interview-questions/service-container-di)는 Laravel의 핵심입니다. 면접 질문에서는 바인딩 타입, 해석 순서, 각 접근 방식을 언제 사용하는지에 대한 이해가 테스트됩니다. ```php // AppServiceProvider.php public function register(): void { // 싱글톤: 요청 라이프사이클 전체에서 동일한 인스턴스 $this->app->singleton(PaymentGateway::class, function (Application $app) { return new StripeGateway( apiKey: config('services.stripe.secret'), logger: $app->make(LoggerInterface::class) ); }); // 바인드: 해석할 때마다 새 인스턴스 $this->app->bind(ReportGenerator::class, function (Application $app) { return new PdfReportGenerator($app->make(ViewFactory::class)); }); // 컨텍스트 바인딩: 컨슈머별로 다른 구현 $this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(fn () => Storage::disk('photos')); $this->app->when(DocumentController::class) ->needs(Filesystem::class) ->give(fn () => Storage::disk('documents')); } ``` 컨텍스트 바인딩은 별도의 인터페이스를 생성하지 않고 다른 클래스에 다른 구현을 인젝트하는 문제를 해결합니다. 컨테이너는 어떤 클래스가 요청하는지에 따라 `Filesystem`을 다르게 해석합니다. 시니어 레벨 질문에서는 지연 프로바이더에 대해 묻습니다. 표준 서비스 프로바이더는 서비스가 사용되지 않더라도 모든 요청에서 등록됩니다. 지연 프로바이더는 선언된 서비스 중 하나가 해석될 때만 등록됩니다. ```php // ReportingServiceProvider.php class ReportingServiceProvider extends ServiceProvider implements DeferrableProvider { public function register(): void { $this->app->singleton(ReportingService::class, function () { return new ReportingService(/* 무거운 초기화 */); }); } // ReportingService가 요청될 때만 컨테이너가 이 프로바이더를 로드 public function provides(): array { return [ReportingService::class]; } } ``` ## 미들웨어: 요청/응답 흐름과 터미네이블 미들웨어 미들웨어 질문에서는 요청 라이프사이클에 대한 이해도를 확인합니다. 미들웨어는 컨트롤러 전후 모두에서 코드를 실행할 수 있으며, 터미네이블 미들웨어는 응답이 클라이언트에 전송된 후 실행됩니다. ```php // LogRequestMetrics.php class LogRequestMetrics { public function handle(Request $request, Closure $next): Response { $request->attributes->set('start_time', microtime(true)); // 다음 미들웨어/컨트롤러로 진행 return $next($request); } // 터미네이블: 응답 전송 후 실행 public function terminate(Request $request, Response $response): void { $duration = microtime(true) - $request->attributes->get('start_time'); Log::info('Request completed', [ 'path' => $request->path(), 'status' => $response->getStatusCode(), 'duration_ms' => round($duration * 1000, 2), ]); } } ``` 터미네이블 미들웨어는 분석, 로깅, 정리 등 응답 시간에 영향을 주지 않아야 하는 작업에 유용합니다. `terminate` 메서드는 응답이 클라이언트에 전송된 후 컨테이너에 의해 호출됩니다. ## 리포지토리 패턴: 사용해야 할 때와 피해야 할 때 리포지토리 패턴은 논쟁의 대상입니다. Laravel의 Eloquent는 이미 Active Record를 구현하고 있습니다. 리포지토리를 추가하면 일부 팀이 불필요하다고 느끼는 추상화가 생깁니다. 면접관은 지원자에게 자신의 입장을 정당화하도록 요청합니다. 리포지토리의 장점: - 비즈니스 로직을 Eloquent에서 분리하여 데이터베이스 전환 가능(실제로는 드묾) - 리포지토리 인터페이스를 목킹하여 테스트 단순화 - 여러 컨트롤러가 유사한 쿼리를 사용할 때 쿼리 로직 중앙화 반대 의견: - CRUD 작업에 대해 명확한 이점 없이 보일러플레이트 추가 - Eloquent의 쿼리 빌더는 이미 표현력 있고 테스트 가능 - Laravel의 DI는 모델과 직접 작동 가능 ```php // OrderRepository.php(리포지토리가 가치를 추가하는 경우) class OrderRepository { public function __construct( private readonly Order $model ) {} public function findPendingForUser(User $user): Collection { return $this->model->query() ->where('user_id', $user->id) ->where('status', OrderStatus::Pending) ->with('items.product') ->orderByDesc('created_at') ->get(); } public function calculateRevenueForPeriod(CarbonPeriod $period): Money { $total = $this->model->query() ->whereBetween('completed_at', [$period->start, $period->end]) ->where('status', OrderStatus::Completed) ->sum('total_cents'); return Money::ofMinor($total, 'USD'); } } ``` 균형 잡힌 답변: 쿼리 로직이 복잡하거나 컨텍스트 간에 공유되거나 팀이 인터페이스 목킹을 통한 테스트 용이성을 우선시할 때 리포지토리는 가치를 추가합니다. 단순한 CRUD의 경우 모델을 직접 인젝트하여 코드를 간결하게 유지할 수 있습니다. ## 테스트: 피처 테스트, 목킹, 데이터베이스 전략 Laravel 테스트 관련 질문에서는 지원자가 신뢰할 수 있고 빠른 테스트를 작성할 수 있는지 검증합니다. 프레임워크는 서로 다른 트레이드오프를 가진 `RefreshDatabase`, `DatabaseTransactions`, `LazilyRefreshDatabase` 트레이트를 제공합니다. ```php // OrderTest.php class OrderTest extends TestCase { use RefreshDatabase; // 클래스당 1회 마이그레이션, 테스트당 트랜잭션 public function test_user_can_place_order(): void { // Arrange $user = User::factory()->create(); $product = Product::factory()->create(['price_cents' => 2999]); // Act $response = $this->actingAs($user) ->postJson('/api/orders', [ 'items' => [ ['product_id' => $product->id, 'quantity' => 2], ], ]); // Assert $response->assertCreated() ->assertJsonPath('data.total_cents', 5998); $this->assertDatabaseHas('orders', [ 'user_id' => $user->id, 'status' => 'pending', ]); } public function test_order_dispatches_processing_job(): void { Queue::fake(); $user = User::factory()->create(); $order = Order::factory()->for($user)->create(); $this->actingAs($user) ->postJson("/api/orders/{$order->id}/process"); Queue::assertPushed(ProcessOrder::class, function ($job) use ($order) { return $job->order->id === $order->id; }); } } ``` `RefreshDatabase` 트레이트는 테스트 클래스당 1회 마이그레이션을 실행하고 각 테스트를 롤백하는 트랜잭션으로 래핑합니다. 이를 통해 속도와 격리의 균형을 맞춥니다. 대규모 테스트 스위트에서는 `LazilyRefreshDatabase`가 스키마가 일치할 때 마이그레이션을 건너뜁니다. 외부 서비스 목킹은 테스트가 실제 API에 접근하는 것을 방지합니다. ```php // PaymentTest.php public function test_payment_failure_returns_error(): void { $gateway = $this->mock(PaymentGateway::class); $gateway->shouldReceive('charge') ->once() ->andThrow(new PaymentFailedException('Card declined')); $user = User::factory()->create(); $order = Order::factory()->for($user)->create(); $response = $this->actingAs($user) ->postJson("/api/orders/{$order->id}/pay"); $response->assertStatus(402) ->assertJsonPath('error', 'Card declined'); } ``` ## 면접에서 출제될 가능성이 높은 Laravel 13 기능 Laravel 13은 2026년 3월에 릴리스되었으며, 면접관이 지원자에게 이해를 기대하는 기능들을 포함하고 있습니다. [공식 릴리스 노트](https://laravel.com/docs/13.x/releases)에서 이에 대한 자세한 내용을 확인할 수 있습니다. **AI SDK**: Laravel 13에는 텍스트 생성, 임베딩, 도구 호출 에이전트를 위한 통합 API를 갖춘 퍼스트파티 AI 도구가 포함되어 있습니다. 기존 애플리케이션에 AI 기능 통합에 대한 질문이 나올 수 있습니다. **JSON:API 리소스**: 새로운 `JsonApiResource` 클래스는 관계, 스파스 필드셋, 컴파운드 문서를 포함한 JSON:API 준수 응답 구축을 단순화합니다. **벡터 검색**: `AsVector` Eloquent 캐스트는 시맨틱 검색을 위한 벡터 열을 처리합니다. PostgreSQL pgvector 및 MariaDB의 바이너리 벡터 형식과 함께 작동합니다. **Cloud 파사드**: `Cloud::hosted()`는 Laravel Cloud 배포를 감지하고, `Cloud::usesManagedQueues()`는 관리형 큐 연결을 확인합니다. 이를 통해 설정 확인 없이 환경별 동작이 가능합니다. ## Laravel 면접 준비 핵심 사항 - `with()`, `loadMissing()`, `withCount()`를 사용한 N+1 방지를 설명합니다. 대규모 데이터셋의 이거 로딩에서 메모리 영향을 이해합니다. - 큐 잡 설계를 설명합니다: 재시도 전략, `SerializesModels` 동작, 배칭 콜백, `deleteWhenMissingModels` 사용 시점. - 서비스 컨테이너 바인딩 타입을 명확히 합니다: 싱글톤 vs 바인드, 컨텍스트 바인딩, 성능 향상을 위한 지연 프로바이더. - 미들웨어 요청/응답 흐름과 응답 후 작업을 위한 터미네이블 미들웨어를 시연합니다. - 교조적인 규칙보다 구체적인 트레이드오프로 리포지토리 패턴에 대한 입장을 취합니다. - `RefreshDatabase`를 적절히 사용하고, 외부 서비스를 목킹하며, 큐 디스패치를 어설션하는 테스트를 작성합니다. - Laravel 13 기능, 특히 AI SDK, JSON:API 리소스, 벡터 검색 기능에 대한 최신 지식을 유지합니다. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/laravel/php-laravel-framework-interview-questions-eloquent-queues-architecture