Pertanyaan Wawancara PHP Laravel Framework 2026: Eloquent, Queues, dan Pola Arsitektur

Pertanyaan wawancara PHP Laravel framework untuk 2026. Kuasai Eloquent ORM, arsitektur queue, pola service container, dan tantangan coding untuk posisi senior.

Pertanyaan Wawancara PHP Laravel Framework 2026: Eloquent, Queues, dan Pola Arsitektur

Pertanyaan wawancara PHP Laravel framework menguji lebih dari sekadar pengetahuan sintaks. Perusahaan yang merekrut pada tahun 2026 mengharapkan kandidat untuk menjelaskan optimasi relationship Eloquent, penanganan kegagalan queue, dan service container bindings dengan detail tingkat produksi. Laravel 13 memperkenalkan AI tooling, JSON:API resources, dan kemampuan vector search yang menambah dimensi baru pada wawancara teknis.

Yang dinilai pewawancara

Posisi senior Laravel berfokus pada tiga area: performa Eloquent (pencegahan N+1, chunking, cursor pagination), reliabilitas queue (retries, dead letter handling, job batching), dan keputusan arsitektural (service providers, facades vs injection, repository patterns).

Eloquent ORM: Relationship Loading dan Query Optimization

Pertanyaan wawancara Eloquent secara konsisten menargetkan masalah N+1. Kandidat harus mendemonstrasikan kapan menggunakan with(), load(), dan loadMissing(), serta menjelaskan tradeoff memori dari eager loading dataset besar.

UserController.phpphp
// Eager load dengan constraints untuk menghindari loading data yang tidak perlu
$users = User::query()
    ->with(['posts' => function (Builder $query) {
        $query->where('published', true)
              ->select('id', 'user_id', 'title', 'published_at');
    }])
    ->withCount('posts')
    ->paginate(25);

// Buruk: memicu N+1 saat mengakses posts dalam loop
foreach ($users as $user) {
    $user->posts; // Setiap iterasi melakukan query ke database
}

Method withCount() menambahkan subquery yang menghitung related records tanpa memuatnya. Ini menghindari overhead memori saat menampilkan counts di halaman listing. Untuk relationships yang mungkin tidak diakses, loadMissing() mencegah query redundan ketika relationship sudah di-eager load sebelumnya.

Pertanyaan follow-up yang umum menanyakan tentang cursor pagination vs offset pagination. Cursor pagination menggunakan cursorPaginate() dan menghindari performance cliff yang dialami offset pagination pada tabel besar. Tradeoff-nya: cursor pagination tidak mendukung navigasi langsung ke halaman tertentu.

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

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

Method chunkById() memproses records dalam batch tanpa memory exhaustion. Tidak seperti chunk(), method ini menjaga ordering konsisten ketika records dimodifikasi selama iterasi.

Query Scopes dan Reusable Query Logic

Pewawancara mengharapkan kandidat mengorganisir query logic menggunakan local dan global scopes. Local scopes mengenkapsulasi kondisi filter yang sering digunakan, sementara global scopes menerapkan constraints secara otomatis ke semua query.

app/Models/Post.phpphp
class Post extends Model
{
    // Local scope untuk konten yang dipublikasikan
    public function scopePublished(Builder $query): Builder
    {
        return $query->where('status', 'published')
                     ->whereNotNull('published_at')
                     ->where('published_at', '<=', now());
    }

    // Local scope dengan parameter
    public function scopeByAuthor(Builder $query, int $authorId): Builder
    {
        return $query->where('author_id', $authorId);
    }

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

// Penggunaan: chaining multiple scopes
$recentPosts = Post::published()
    ->byAuthor($userId)
    ->recent(14)
    ->get();

Global scopes berlaku untuk semua query pada model. Kasus penggunaan umum termasuk soft deletes, tenant isolation dalam aplikasi multi-tenant, dan 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 ketika diperlukan
$allSubscriptions = Subscription::withoutGlobalScope(ActiveScope::class)->get();

Queue System: Job Handling dan Failure Recovery

Arsitektur queue Laravel menyediakan reliability melalui retries, timeouts, dan dead letter queues. Pertanyaan wawancara berfokus pada penanganan kegagalan dan memastikan 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
    {
        // Cek idempotency untuk mencegah pemrosesan duplikat
        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 membatasi berapa kali job dapat melempar exception sebelum dianggap gagal, terpisah dari $tries yang menghitung total attempts. Property $backoff menentukan penundaan antar retries.

Job Batching dan Chaining

Laravel menyediakan job batching untuk memproses banyak jobs sebagai unit tunggal dengan tracking progress dan handling kegagalan.

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 menjalankan jobs secara berurutan, dengan job selanjutnya hanya dieksekusi jika job sebelumnya berhasil.

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

Service Container dan Dependency Injection

Memahami service container sangat penting untuk posisi senior. Pewawancara menguji pengetahuan tentang binding types, contextual binding, dan kapan menggunakan singleton vs transient bindings.

app/Providers/PaymentServiceProvider.phpphp
class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Singleton: instance yang sama di seluruh request
        $this->app->singleton(PaymentGateway::class, function ($app) {
            return new StripeGateway(
                config('services.stripe.secret'),
                $app->make(HttpClient::class)
            );
        });

        // Binding interface ke implementasi
        $this->app->bind(
            PaymentProcessorInterface::class,
            StripePaymentProcessor::class
        );

        // Contextual binding: implementasi berbeda berdasarkan 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 memungkinkan resolusi interface yang berbeda berdasarkan class mana yang meminta dependency. Ini menghindari service locator pattern dan menjaga dependency injection yang bersih.

Repository Pattern dan Service Layer

Meskipun Eloquent menyediakan query building yang powerful, tim sering mengimplementasikan repository pattern untuk abstraction dan 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 berisi business logic dan mengkoordinasikan 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;
    }
}

Siap menguasai wawancara Laravel Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Event-Driven Architecture dan Observers

Laravel events mendecouple komponen dan memungkinkan multiple listeners merespons state changes. Pewawancara mengharapkan kandidat menjelaskan kapan menggunakan 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 mengelompokkan event handlers yang terkait dengan lifecycle 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 Aplikasi Laravel

Wawancara senior termasuk pertanyaan tentang strategi testing. Laravel menyediakan helpers untuk feature testing, unit testing, dan 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 dan Request Lifecycle

Memahami request lifecycle dan middleware pipeline sangat penting untuk debugging dan 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);
    });

Kesimpulan

Persiapan wawancara Laravel memerlukan kedalaman di Eloquent optimization, queue reliability, dan architectural patterns. Pewawancara di 2026 berfokus pada kemampuan kandidat menangani workloads skala produksi: pencegahan N+1 melalui eager loading strategis, job idempotency untuk mencegah pemrosesan duplikat, dan service container mastery untuk maintainable code. Memahami tradeoffs antara repository pattern vs Eloquent langsung, event-driven architecture vs procedural code, dan cursor vs offset pagination membedakan kandidat senior dari junior. Praktik dengan contoh kode ini dan artikulasikan reasoning di balik setiap keputusan arsitektural.

Tantangan harian

Bisakah kamu menemukan bug di Laravel?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 16 September 2026

Bagikan

Artikel terkait