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 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.
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.
// 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.
// 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.
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.
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.
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.
// 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 jobsJob chaining menjalankan jobs secara berurutan, dengan job selanjutnya hanya dieksekusi jika job sebelumnya berhasil.
// 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.
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.
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.
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.
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.
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.
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.
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.
Bisakah kamu menemukan bug di Laravel?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri 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

Laravel Octane di 2026: Swoole, RoadRunner dan Optimisasi Performa
Kuasai Laravel Octane 2.19 dengan FrankenPHP, Swoole dan RoadRunner. Pelajari pemilihan server, pencegahan memory leak, concurrent tasks, dan pola deployment production untuk aplikasi PHP berperforma tinggi.

Pertanyaan Interview PHP Laravel Developer 2026: Panduan Persiapan Lengkap
Panduan komprehensif untuk mempersiapkan interview PHP Laravel developer tahun 2026. Mencakup pertanyaan teknis Eloquent ORM, Service Container, authentication, queue, dan testing.

Laravel Solutions: Pola Tingkat Lanjut, Debugging, dan Pertanyaan Interview 2026
Pelajari pola arsitektur Laravel tingkat lanjut, teknik debugging dengan Telescope, dan persiapan pertanyaan interview untuk posisi senior developer Laravel tahun 2026.