PHP Laravel面接質問 2026年版:Eloquent、キュー、アーキテクチャパターン完全解説

2026年のLaravel技術面接で頻出するEloquent ORM、キュー設計、サービスコンテナに関する質問と回答例を詳しく解説。N+1問題対策からジョブバッチング、DIパターンまで網羅。

PHP Laravel面接質問 2026年版:Eloquent、キュー、アーキテクチャパターン完全解説

PHP Laravelフレームワークの面接質問は、単なる構文知識以上のものを求める。2026年に採用を行う企業は、Eloquentのリレーション最適化、キュー障害処理、サービスコンテナのバインディングについて、本番環境レベルの詳細な説明を期待している。Laravel 13ではAIツール、JSON:APIリソース、ベクトル検索機能が導入され、技術面接に新たな次元が加わった。

面接官が評価するポイント

シニアLaravelポジションでは3つの領域に焦点が当てられる:Eloquentパフォーマンス(N+1防止、チャンキング、カーソルページネーション)、キューの信頼性(リトライ、デッドレター処理、ジョブバッチング)、そしてアーキテクチャ決定(サービスプロバイダ、ファサード vs インジェクション、リポジトリパターン)。

Eloquent ORM:リレーション読み込みとクエリ最適化

Eloquentの面接質問では、N+1問題が一貫して取り上げられる。候補者はwith()、load()、loadMissing()をいつ使用するか、また大規模データセットのイーガーロードにおけるメモリのトレードオフを説明できなければならない。

UserController.phpphp
// 不要なデータの読み込みを避けるため、制約付きでイーガーロード
$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マップとインデックス戦略について正確な理解が求められる。面接官は、投稿、動画、製品に属するコメントのシナリオを提示し、候補者にクエリの最適化を求めることが多い。

Comment.phpphp
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マップには2つの目的がある:データベース値をクラス名から分離し(マイグレーションなしでリファクタリングを可能にする)、より短くインデックス可能な文字列を作成する。morphマップがなければ、LaravelはApp\Models\Postのような完全なクラス名をcommentable_type列に格納する。

クエリスコープは、再利用可能なクエリロジックの理解を示す。グローバルスコープは自動的に適用され、ローカルスコープは明示的な呼び出しが必要である。典型的な面接質問では、それぞれをいつ使用するかが問われる。

Post.phpphp
// グローバルスコープ:明示的に除去しない限りすべてのクエリに適用
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キューパターンのより詳細な内容については、キュー&ジョブ面接質問モジュールを参照されたい。

ProcessOrder.phpphp
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で処理する。

ジョブバッチングに関する質問も頻繁に出現する。バッチはジョブをグループ化し、完了、失敗、キャンセル時のコールバックを定義できる。

OrderBatchController.phpphp
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();
}

Laravelの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

サービスコンテナ:バインディング、コンテキストインジェクション、プロバイダ

サービスコンテナはLaravelの中核である。面接質問では、バインディングタイプ、解決順序、各アプローチをいつ使用するかについての理解がテストされる。

AppServiceProvider.phpphp
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を異なる方法で解決する。

シニアレベルの質問では、遅延プロバイダについて問われる。標準のサービスプロバイダは、そのサービスが使用されなくてもすべてのリクエストで登録される。遅延プロバイダは、宣言されたサービスの1つが解決されたときにのみ登録される。

ReportingServiceProvider.phpphp
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];
    }
}

ミドルウェア:リクエスト/レスポンスフローとターミナブルミドルウェア

ミドルウェアの質問では、リクエストライフサイクルの理解度を確認する。ミドルウェアはコントローラの前後の両方でコードを実行でき、ターミナブルミドルウェアはレスポンスがクライアントに送信された後に実行される。

LogRequestMetrics.phpphp
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はモデルと直接連携可能
OrderRepository.php(リポジトリが価値を追加する場合)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トレイトを提供している。

OrderTest.phpphp
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にアクセスするのを防ぐ。

PaymentTest.phpphp
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月にリリースされ、面接官が候補者に理解を期待する機能が含まれている。公式リリースノートでこれらの詳細が説明されている。

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リソース、ベクトル検索機能について最新の知識を維持する。
今日のチャレンジ

Laravel のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月16日 更新

共有

関連記事