Câu Hỏi Phỏng Vấn PHP Laravel Framework 2026: Eloquent, Queues và Kiến Trúc Patterns

Câu hỏi phỏng vấn PHP Laravel framework cho năm 2026. Nắm vững Eloquent ORM, kiến trúc queue, các pattern service container và thử thách coding cho vị trí senior.

Câu Hỏi Phỏng Vấn PHP Laravel Framework 2026: Eloquent, Queues và Kiến Trúc Patterns

Câu hỏi phỏng vấn PHP Laravel framework đánh giá nhiều hơn kiến thức về cú pháp. Các công ty tuyển dụng năm 2026 kỳ vọng ứng viên giải thích về tối ưu relationship Eloquent, xử lý lỗi queue và service container bindings với mức độ chi tiết production. Laravel 13 giới thiệu AI tooling, JSON:API resources và khả năng vector search, tạo thêm chiều sâu mới cho các cuộc phỏng vấn kỹ thuật.

Nhà tuyển dụng đánh giá điều gì

Vị trí senior Laravel tập trung vào ba lĩnh vực: hiệu suất Eloquent (ngăn chặn N+1, chunking, cursor pagination), độ tin cậy queue (retries, dead letter handling, job batching) và các quyết định kiến trúc (service providers, facades vs injection, repository patterns).

Eloquent ORM: Relationship Loading và Query Optimization

Câu hỏi phỏng vấn Eloquent thường xuyên nhắm vào vấn đề N+1. Ứng viên cần chứng minh khi nào sử dụng with(), load()loadMissing(), cùng với giải thích về đánh đổi bộ nhớ khi eager loading dataset lớn.

UserController.phpphp
// Eager load với constraints để tránh load dữ liệu không cần thiết
$users = User::query()
    ->with(['posts' => function (Builder $query) {
        $query->where('published', true)
              ->select('id', 'user_id', 'title', 'published_at');
    }])
    ->withCount('posts')
    ->paginate(25);

// Xấu: kích hoạt N+1 khi truy cập posts trong vòng lặp
foreach ($users as $user) {
    $user->posts; // Mỗi lần lặp thực hiện query đến database
}

Phương thức withCount() thêm subquery đếm các related records mà không load chúng. Điều này tránh overhead bộ nhớ khi hiển thị counts trong trang listing. Với những relationships có thể không được truy cập, loadMissing() ngăn các query thừa khi relationship đã được eager load trước đó.

Câu hỏi follow-up phổ biến hỏi về cursor pagination vs offset pagination. Cursor pagination sử dụng cursorPaginate() và tránh performance cliff mà offset pagination gặp phải trên các bảng lớn. Đánh đổi: cursor pagination không hỗ trợ điều hướng trực tiếp đến trang cụ thể.

php
// Cursor pagination cho dataset lớn
$posts = Post::query()
    ->where('status', 'published')
    ->orderBy('published_at', 'desc')
    ->cursorPaginate(50);

// Chunk processing cho batch operations
User::query()
    ->where('last_login_at', '<', now()->subYear())
    ->chunkById(1000, function (Collection $users) {
        foreach ($users as $user) {
            $user->markAsInactive();
        }
    });

Phương thức chunkById() xử lý records theo batch mà không gây memory exhaustion. Không giống chunk(), phương thức này duy trì ordering nhất quán khi records bị sửa đổi trong quá trình lặp.

Query Scopes và Reusable Query Logic

Nhà tuyển dụng kỳ vọng ứng viên tổ chức query logic sử dụng local và global scopes. Local scopes đóng gói các điều kiện filter thường dùng, trong khi global scopes áp dụng constraints tự động cho tất cả queries.

app/Models/Post.phpphp
class Post extends Model
{
    // Local scope cho nội dung đã publish
    public function scopePublished(Builder $query): Builder
    {
        return $query->where('status', 'published')
                     ->whereNotNull('published_at')
                     ->where('published_at', '<=', now());
    }

    // Local scope với tham số
    public function scopeByAuthor(Builder $query, int $authorId): Builder
    {
        return $query->where('author_id', $authorId);
    }

    // Scope có thể chain
    public function scopeRecent(Builder $query, int $days = 7): Builder
    {
        return $query->where('published_at', '>=', now()->subDays($days));
    }
}

// Sử dụng: chaining multiple scopes
$recentPosts = Post::published()
    ->byAuthor($userId)
    ->recent(14)
    ->get();

Global scopes áp dụng cho tất cả queries trên model. Các trường hợp sử dụng phổ biến bao gồm soft deletes, tenant isolation trong ứng dụng multi-tenant và status filtering.

app/Models/Scopes/ActiveScope.phpphp
class ActiveScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('is_active', true);
    }
}

// app/Models/Subscription.php
class Subscription extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope());
    }
}

// Bypass global scope khi cần
$allSubscriptions = Subscription::withoutGlobalScope(ActiveScope::class)->get();

Queue System: Job Handling và Failure Recovery

Kiến trúc queue của Laravel cung cấp độ tin cậy thông qua retries, timeouts và dead letter queues. Câu hỏi phỏng vấn tập trung vào xử lý lỗi và đảm bảo job idempotency.

app/Jobs/ProcessPayment.phpphp
class ProcessPayment implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $maxExceptions = 2;
    public int $timeout = 120;
    public int $backoff = 60;

    public function __construct(
        public readonly Order $order,
        public readonly string $idempotencyKey
    ) {}

    public function handle(PaymentGateway $gateway): void
    {
        // Kiểm tra idempotency để ngăn xử lý trùng lặp
        if (Cache::has("payment_processed:{$this->idempotencyKey}")) {
            return;
        }

        $result = $gateway->charge(
            $this->order->total,
            $this->order->payment_method_id
        );

        $this->order->update([
            'payment_status' => $result->status,
            'payment_id' => $result->id,
        ]);

        Cache::put(
            "payment_processed:{$this->idempotencyKey}",
            true,
            now()->addDays(7)
        );
    }

    public function failed(Throwable $exception): void
    {
        Log::error('Payment processing failed', [
            'order_id' => $this->order->id,
            'error' => $exception->getMessage(),
        ]);

        $this->order->update(['payment_status' => 'failed']);
        
        Notification::route('slack', config('services.slack.payments_channel'))
            ->notify(new PaymentFailedNotification($this->order, $exception));
    }

    public function retryUntil(): DateTime
    {
        return now()->addHours(6);
    }
}

Property $maxExceptions giới hạn số lần job có thể throw exception trước khi bị coi là thất bại, tách biệt với $tries đếm tổng số attempts. Property $backoff xác định độ trễ giữa các lần retry.

Job Batching và Chaining

Laravel cung cấp job batching để xử lý nhiều jobs như một đơn vị với tracking tiến độ và xử lý lỗi.

php
// Dispatch batch of jobs
$batch = Bus::batch([
    new ProcessPodcastEpisode($episode1),
    new ProcessPodcastEpisode($episode2),
    new ProcessPodcastEpisode($episode3),
])
->then(function (Batch $batch) {
    Log::info('All episodes processed successfully');
})
->catch(function (Batch $batch, Throwable $e) {
    Log::error('Batch processing failed', ['error' => $e->getMessage()]);
})
->finally(function (Batch $batch) {
    // Cleanup resources
})
->allowFailures()
->dispatch();

// Monitor batch progress
$progress = Bus::findBatch($batch->id);
echo $progress->progress(); // Percentage complete
echo $progress->failedJobs; // Count of failed jobs

Job chaining chạy các jobs theo tuần tự, job tiếp theo chỉ thực thi nếu job trước đó thành công.

php
// Job chain cho multi-step workflow
Bus::chain([
    new ValidateOrder($order),
    new ProcessPayment($order),
    new SendConfirmationEmail($order),
    new UpdateInventory($order),
])->dispatch();

Service Container và Dependency Injection

Hiểu service container rất quan trọng cho vị trí senior. Nhà tuyển dụng kiểm tra kiến thức về binding types, contextual binding và khi nào sử dụng singleton vs transient bindings.

app/Providers/PaymentServiceProvider.phpphp
class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Singleton: cùng instance trong toàn bộ request
        $this->app->singleton(PaymentGateway::class, function ($app) {
            return new StripeGateway(
                config('services.stripe.secret'),
                $app->make(HttpClient::class)
            );
        });

        // Binding interface tới implementation
        $this->app->bind(
            PaymentProcessorInterface::class,
            StripePaymentProcessor::class
        );

        // Contextual binding: implementation khác nhau dựa trên consumer
        $this->app->when(SubscriptionController::class)
            ->needs(PaymentProcessorInterface::class)
            ->give(RecurringPaymentProcessor::class);

        $this->app->when(CheckoutController::class)
            ->needs(PaymentProcessorInterface::class)
            ->give(OneTimePaymentProcessor::class);
    }
}

Contextual bindings cho phép giải quyết interface khác nhau dựa trên class nào yêu cầu dependency. Điều này tránh service locator pattern và duy trì dependency injection sạch.

Repository Pattern và Service Layer

Mặc dù Eloquent cung cấp query building mạnh mẽ, các team thường triển khai repository pattern để abstraction và testability.

app/Repositories/Contracts/UserRepositoryInterface.phpphp
interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function createWithProfile(array $userData, array $profileData): User;
    public function getActiveWithSubscriptions(): Collection;
}

// app/Repositories/EloquentUserRepository.php
class EloquentUserRepository implements UserRepositoryInterface
{
    public function __construct(
        private readonly User $model
    ) {}

    public function findById(int $id): ?User
    {
        return $this->model->find($id);
    }

    public function findByEmail(string $email): ?User
    {
        return $this->model->where('email', $email)->first();
    }

    public function createWithProfile(array $userData, array $profileData): User
    {
        return DB::transaction(function () use ($userData, $profileData) {
            $user = $this->model->create($userData);
            $user->profile()->create($profileData);
            return $user->load('profile');
        });
    }

    public function getActiveWithSubscriptions(): Collection
    {
        return $this->model
            ->where('status', 'active')
            ->with(['subscriptions' => fn($q) => $q->active()])
            ->get();
    }
}

Service layer chứa business logic và điều phối multiple repositories.

app/Services/UserRegistrationService.phpphp
class UserRegistrationService
{
    public function __construct(
        private readonly UserRepositoryInterface $userRepository,
        private readonly NotificationService $notificationService,
        private readonly EventDispatcher $events
    ) {}

    public function register(RegisterUserDTO $dto): User
    {
        $user = $this->userRepository->createWithProfile(
            $dto->toUserArray(),
            $dto->toProfileArray()
        );

        $this->notificationService->sendWelcomeEmail($user);
        $this->events->dispatch(new UserRegistered($user));

        return $user;
    }
}

Sẵn sàng chinh phục phỏng vấn Laravel?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Event-Driven Architecture và Observers

Laravel events tách rời các components và cho phép multiple listeners phản hồi các state changes. Nhà tuyển dụng kỳ vọng ứng viên giải thích khi nào sử dụng events vs observers vs model hooks.

app/Events/OrderPlaced.phpphp
class OrderPlaced implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public readonly Order $order
    ) {}

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('orders.' . $this->order->user_id),
        ];
    }
}

// app/Listeners/SendOrderConfirmation.php
class SendOrderConfirmation implements ShouldQueue
{
    public function handle(OrderPlaced $event): void
    {
        Mail::to($event->order->user)
            ->send(new OrderConfirmationMail($event->order));
    }

    public function shouldQueue(OrderPlaced $event): bool
    {
        return $event->order->total > 0;
    }
}

// app/Providers/EventServiceProvider.php
protected $listen = [
    OrderPlaced::class => [
        SendOrderConfirmation::class,
        UpdateInventory::class,
        NotifyWarehouse::class,
        RecordAnalytics::class,
    ],
];

Model observers nhóm các event handlers liên quan đến lifecycle của model.

app/Observers/OrderObserver.phpphp
class OrderObserver
{
    public function created(Order $order): void
    {
        $order->generateOrderNumber();
        Cache::tags(['orders', "user:{$order->user_id}"])->flush();
    }

    public function updated(Order $order): void
    {
        if ($order->wasChanged('status')) {
            event(new OrderStatusChanged($order));
        }
    }

    public function deleted(Order $order): void
    {
        $order->lineItems()->delete();
        Storage::delete($order->attachments);
    }
}

Testing Ứng Dụng Laravel

Phỏng vấn senior bao gồm câu hỏi về chiến lược testing. Laravel cung cấp helpers cho feature testing, unit testing và mocking external services.

tests/Feature/OrderControllerTest.phpphp
class OrderControllerTest extends TestCase
{
    use RefreshDatabase;

    public function test_user_can_place_order(): void
    {
        $user = User::factory()->create();
        $product = Product::factory()->create(['price' => 5000]);

        $this->actingAs($user)
            ->postJson('/api/orders', [
                'items' => [
                    ['product_id' => $product->id, 'quantity' => 2]
                ],
                'shipping_address_id' => $user->addresses()->first()->id,
            ])
            ->assertCreated()
            ->assertJsonStructure([
                'data' => ['id', 'total', 'status', 'items']
            ]);

        $this->assertDatabaseHas('orders', [
            'user_id' => $user->id,
            'total' => 10000,
        ]);
    }

    public function test_payment_failure_does_not_create_order(): void
    {
        $this->mock(PaymentGateway::class, function ($mock) {
            $mock->shouldReceive('charge')
                ->once()
                ->andThrow(new PaymentFailedException('Card declined'));
        });

        $user = User::factory()->create();

        $this->actingAs($user)
            ->postJson('/api/orders', $this->validOrderData())
            ->assertStatus(422)
            ->assertJsonPath('message', 'Payment failed: Card declined');

        $this->assertDatabaseMissing('orders', ['user_id' => $user->id]);
    }
}

Middleware và Request Lifecycle

Hiểu request lifecycle và middleware pipeline rất quan trọng cho debugging và optimization.

app/Http/Middleware/EnsureTeamAccess.phpphp
class EnsureTeamAccess
{
    public function handle(Request $request, Closure $next, string $permission): Response
    {
        $team = $request->route('team');
        $user = $request->user();

        if (!$user->belongsToTeam($team)) {
            abort(403, 'You are not a member of this team');
        }

        if (!$user->hasTeamPermission($team, $permission)) {
            abort(403, "Missing permission: {$permission}");
        }

        return $next($request);
    }
}

// routes/web.php
Route::middleware(['auth', 'team.access:manage-projects'])
    ->prefix('teams/{team}')
    ->group(function () {
        Route::resource('projects', ProjectController::class);
    });

Kết Luận

Chuẩn bị phỏng vấn Laravel đòi hỏi sự sâu sắc về Eloquent optimization, queue reliability và architectural patterns. Nhà tuyển dụng năm 2026 tập trung vào khả năng ứng viên xử lý workloads quy mô production: ngăn chặn N+1 thông qua eager loading chiến lược, job idempotency để ngăn xử lý trùng lặp và service container mastery cho maintainable code. Hiểu được các đánh đổi giữa repository pattern vs Eloquent trực tiếp, event-driven architecture vs procedural code và cursor vs offset pagination phân biệt ứng viên senior với junior. Thực hành với các code examples này và diễn đạt lý luận đằng sau mỗi quyết định kiến trúc.

Thử thách hôm nay

Bạn có tìm ra lỗi trong Laravel không?

Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Anthony Fillion-Maillet

Viết bởi

Anthony Fillion-Maillet

Người sáng lập SharpSkill

Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.

Cập nhật ngày 16 tháng 9, 2026

Chia sẻ

Bài viết liên quan