Pertanyaan Wawancara Laravel dan PHP: 25 Teratas di 2026

25 pertanyaan wawancara Laravel dan PHP yang paling sering ditanyakan. Service Container, Eloquent ORM, middleware, queues, dan deployment produksi dengan jawaban lengkap beserta contoh kode.

Pertanyaan Wawancara Laravel dan PHP - Panduan Lengkap

Wawancara Laravel menguji penguasaan framework PHP paling populer, pemahaman terhadap Eloquent ORM, arsitektur MVC, serta kemampuan membangun aplikasi yang tangguh dan mudah dipelihara. Panduan ini membahas 25 pertanyaan yang paling sering muncul, mulai dari dasar-dasar Laravel hingga pola deployment produksi tingkat lanjut.

Tips Wawancara

Pewawancara menghargai kandidat yang mampu menjelaskan keputusan arsitektural Laravel. Memahami mengapa framework ini mengadopsi konvensi tertentu (Convention over Configuration, Service Container) menjadi pembeda nyata dalam wawancara.

Dasar-Dasar Laravel

Pertanyaan 1: Jelaskan Siklus Hidup Request di Laravel

Siklus hidup request Laravel melewati beberapa lapisan sebelum mencapai controller. Memahami siklus ini sangat penting untuk debugging dan optimasi performa.

public/index.phpphp
// Entry point for all HTTP requests
require __DIR__.'/../vendor/autoload.php';

// Load the Laravel application
$app = require_once __DIR__.'/../bootstrap/app.php';

// The HTTP kernel handles the request
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

Siklus lengkapnya: index.php → autoload → bootstrap → service providers → middlewares → routing → controller → response → terminate. Setiap tahap dapat dicegat dan dikustomisasi.

Pertanyaan 2: Apa Itu Service Container dan Bagaimana Dependency Injection Bekerja?

Service Container adalah inti dari Laravel. Komponen ini mengelola instansiasi kelas dan secara otomatis menyelesaikan dependensi melalui constructor injection.

app/Services/PaymentService.phpphp
// Service with automatically injected dependencies
namespace App\Services;

use App\Contracts\PaymentGatewayInterface;
use App\Repositories\OrderRepository;
use Illuminate\Support\Facades\Log;

class PaymentService
{
    public function __construct(
        private PaymentGatewayInterface $gateway, // Interface resolved by container
        private OrderRepository $orders           // Concrete class auto-resolved
    ) {}

    public function processPayment(int $orderId, float $amount): bool
    {
        $order = $this->orders->find($orderId);

        try {
            $result = $this->gateway->charge($amount, $order->customer);
            $order->markAsPaid($result->transactionId);
            return true;
        } catch (PaymentException $e) {
            Log::error('Payment failed', ['order' => $orderId, 'error' => $e->getMessage()]);
            return false;
        }
    }
}
app/Providers/AppServiceProvider.phpphp
// Binding an interface to a concrete implementation
public function register(): void
{
    // Simple binding: new instance on each injection
    $this->app->bind(
        PaymentGatewayInterface::class,
        StripeGateway::class
    );

    // Singleton: same instance shared everywhere
    $this->app->singleton(
        CacheService::class,
        fn($app) => new CacheService($app['config']['cache.driver'])
    );
}

Dependency injection memisahkan kelas-kelas dan mempermudah pengujian unit melalui mocking.

Pertanyaan 3: Apa Perbedaan antara Facades dan Dependency Injection?

Facades menyediakan sintaks statis untuk mengakses layanan container, sedangkan dependency injection membuat dependensi menjadi eksplisit.

php
// Using Facades: concise syntax but implicit dependencies
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class ReportController extends Controller
{
    public function generate()
    {
        // Facades: static access to services
        $data = Cache::remember('report_data', 3600, fn() => $this->fetchData());
        Log::info('Report generated');
        return view('report', compact('data'));
    }
}

// Dependency Injection: explicit and testable dependencies
use Illuminate\Contracts\Cache\Repository as CacheContract;
use Psr\Log\LoggerInterface;

class ReportController extends Controller
{
    public function __construct(
        private CacheContract $cache,
        private LoggerInterface $logger
    ) {}

    public function generate()
    {
        // Same functionality, explicit dependencies
        $data = $this->cache->remember('report_data', 3600, fn() => $this->fetchData());
        $this->logger->info('Report generated');
        return view('report', compact('data'));
    }
}

Gunakan dependency injection pada kelas bisnis untuk kemudahan pengujian. Facades cocok untuk helper dan kode ad-hoc.

Pertanyaan 4: Bagaimana Service Provider Bekerja di Laravel?

Service Providers adalah titik konfigurasi utama aplikasi. Setiap provider mendaftarkan layanan, mengonfigurasi binding, dan menginisialisasi komponen.

app/Providers/PaymentServiceProvider.phpphp
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\PaymentService;
use App\Contracts\PaymentGatewayInterface;
use App\Gateways\StripeGateway;

class PaymentServiceProvider extends ServiceProvider
{
    // Register method: bindings and registrations
    // Don't access other services here (not yet loaded)
    public function register(): void
    {
        $this->app->singleton(PaymentGatewayInterface::class, function ($app) {
            return new StripeGateway(
                config('services.stripe.key'),
                config('services.stripe.secret')
            );
        });
    }

    // Boot method: initialization after all providers
    // Full access to all application services
    public function boot(): void
    {
        // Register macros, event listeners, routes, etc.
        $this->loadRoutesFrom(__DIR__.'/../routes/payment.php');
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'payment');

        // Publish files for packages
        $this->publishes([
            __DIR__.'/../config/payment.php' => config_path('payment.php'),
        ], 'payment-config');
    }
}

Urutan eksekusi: semua metode register() dijalankan terlebih dahulu, kemudian semua metode boot(). Urutan ini memastikan dependensi tersedia saat boot.

Eloquent ORM

Pertanyaan 5: Jelaskan Relasi Eloquent dan Perbedaannya

Eloquent menyediakan beberapa tipe relasi untuk memodelkan asosiasi antar tabel. Setiap tipe memiliki kasus penggunaan yang spesifik.

app/Models/User.phpphp
class User extends Model
{
    // A user has one profile (1:1)
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }

    // A user has many articles (1:N)
    public function articles(): HasMany
    {
        return $this->hasMany(Article::class);
    }

    // A user has many roles via pivot table (N:N)
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class)
            ->withPivot('assigned_at')        // Additional pivot columns
            ->withTimestamps();                // created_at/updated_at on pivot
    }

    // A user has many comments through articles (HasManyThrough)
    public function comments(): HasManyThrough
    {
        return $this->hasManyThrough(
            Comment::class,    // Final model
            Article::class     // Intermediate model
        );
    }
}

// app/Models/Article.php
class Article extends Model
{
    // An article belongs to a user (inverse of hasMany)
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    // Polymorphic relationship: an article can have tags, like other models
    public function tags(): MorphToMany
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

Relasi polimorfik (morphOne, morphMany, morphToMany) memungkinkan sebuah model diasosiasikan dengan beberapa tipe model lain melalui satu relasi tunggal.

Pertanyaan 6: Apa Itu Masalah N+1 dan Bagaimana Mengatasinya dengan Eager Loading?

Masalah N+1 terjadi ketika satu query utama menghasilkan N query tambahan untuk memuat relasi. Ini adalah penyebab paling umum kelambatan pada aplikasi Laravel.

php
// ❌ PROBLEM: N+1 queries
// 1 query for articles + 1 query PER article for the author
$articles = Article::all();
foreach ($articles as $article) {
    echo $article->author->name; // SQL query on each iteration!
}

// ✅ SOLUTION 1: with() - Eager Loading
// Only 2 queries (articles + users with IN clause)
$articles = Article::with('author')->get();
foreach ($articles as $article) {
    echo $article->author->name; // Already loaded, no query
}

// ✅ SOLUTION 2: Nested Eager Loading
// Loads articles, their authors, and author roles
$articles = Article::with(['author.roles', 'comments.user'])->get();

// ✅ SOLUTION 3: Eager Loading with constraints
$articles = Article::with([
    'comments' => function ($query) {
        $query->where('approved', true)
              ->orderBy('created_at', 'desc')
              ->limit(5);
    }
])->get();

// ✅ SOLUTION 4: Default Eager Loading in the model
class Article extends Model
{
    // These relationships are always loaded automatically
    protected $with = ['author', 'category'];
}

Gunakan php artisan telescope:prune bersama Laravel Telescope untuk mendeteksi masalah N+1 selama pengembangan.

Pertanyaan 7: Bagaimana Membuat Query Scopes dan Kapan Menggunakannya?

Query Scopes mengenkapsulasi kondisi query yang dapat digunakan ulang di tingkat model, membuat kode lebih mudah dibaca dan DRY.

app/Models/Article.phpphp
class Article extends Model
{
    // Global Scope: automatically applied to ALL queries
    protected static function booted(): void
    {
        // Excludes soft-deleted articles by default
        static::addGlobalScope('published', function (Builder $builder) {
            $builder->where('status', 'published');
        });
    }

    // Local Scope: called explicitly via scopeScopeName
    public function scopePopular(Builder $query, int $minViews = 1000): Builder
    {
        return $query->where('view_count', '>=', $minViews);
    }

    public function scopeByAuthor(Builder $query, User $author): Builder
    {
        return $query->where('user_id', $author->id);
    }

    public function scopeRecent(Builder $query, int $days = 7): Builder
    {
        return $query->where('created_at', '>=', now()->subDays($days));
    }

    public function scopeWithStats(Builder $query): Builder
    {
        return $query->withCount('comments')
                     ->withSum('reactions', 'score');
    }
}

// Usage: fluent scope chaining
$articles = Article::popular(500)
    ->recent(30)
    ->byAuthor($user)
    ->withStats()
    ->orderByDesc('comment_count')
    ->paginate(20);

// Ignore a Global Scope
$allArticles = Article::withoutGlobalScope('published')->get();

Local scopes meningkatkan keterbacaan dan memusatkan logika query. Global scopes cocok untuk multi-tenancy atau soft delete.

Siap menguasai wawancara Laravel Anda?

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

Middleware dan Routing

Pertanyaan 8: Bagaimana Middleware Bekerja di Laravel?

Middleware menyaring request HTTP masuk dan dapat memodifikasi response keluar. Setiap request melewati sebuah tumpukan middleware.

app/Http/Middleware/CheckSubscription.phpphp
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class CheckSubscription
{
    public function handle(Request $request, Closure $next, string $plan = 'basic'): Response
    {
        $user = $request->user();

        // Check before the controller
        if (!$user || !$user->hasActiveSubscription($plan)) {
            if ($request->expectsJson()) {
                return response()->json(['error' => 'Subscription required'], 403);
            }
            return redirect()->route('subscription.plans');
        }

        // Pass to next middleware or controller
        $response = $next($request);

        // Modify response after the controller
        $response->headers->set('X-Subscription-Plan', $user->subscription->plan);

        return $response;
    }
}

// bootstrap/app.php (Laravel 11+)
return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // Global middleware (all requests)
        $middleware->append(LogRequestMiddleware::class);

        // Aliases for use in routes
        $middleware->alias([
            'subscription' => CheckSubscription::class,
            'role' => EnsureUserHasRole::class,
        ]);

        // Middleware groups
        $middleware->group('api', [
            ThrottleRequests::class.':api',
            SubstituteBindings::class,
        ]);
    });
routes/web.phpphp
// Applying middlewares to routes
Route::middleware(['auth', 'subscription:premium'])->group(function () {
    Route::get('/dashboard', DashboardController::class);
    Route::resource('projects', ProjectController::class);
});

Urutan middleware penting: mereka dieksekusi dari atas ke bawah saat masuk dan dari bawah ke atas saat keluar.

Pertanyaan 9: Jelaskan Route Model Binding dan Variasinya

Route Model Binding secara otomatis menyuntikkan model Eloquent ke controller berdasarkan parameter URL.

routes/web.phpphp
// Implicit binding: Laravel automatically resolves by ID
Route::get('/articles/{article}', [ArticleController::class, 'show']);

// Binding by slug instead of ID
Route::get('/articles/{article:slug}', [ArticleController::class, 'show']);

// Binding with relationship (automatic scope)
Route::get('/users/{user}/articles/{article}', function (User $user, Article $article) {
    // Laravel automatically verifies that the article belongs to the user
    return $article;
})->scopeBindings();
app/Models/Article.phpphp
class Article extends Model
{
    // Customize the default resolution key
    public function getRouteKeyName(): string
    {
        return 'slug'; // Resolves by slug instead of id
    }

    // Customize the resolution query
    public function resolveRouteBinding($value, $field = null): ?Model
    {
        return $this->where($field ?? 'slug', $value)
                    ->where('status', 'published')
                    ->firstOrFail();
    }
}
app/Providers/RouteServiceProvider.phpphp
// Explicit custom binding
public function boot(): void
{
    Route::bind('article', function (string $value) {
        return Article::where('slug', $value)
            ->published()
            ->with('author')
            ->firstOrFail();
    });
}

Route Model Binding mengurangi kode boilerplate dan memusatkan logika resolusi.

Queues dan Jobs

Pertanyaan 10: Bagaimana Mengimplementasikan Jobs dan Queues di Laravel?

Queues memungkinkan penundaan tugas-tugas berat ke eksekusi background, meningkatkan responsivitas aplikasi.

app/Jobs/ProcessPodcast.phpphp
namespace App\Jobs;

use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    // Number of attempts before final failure
    public int $tries = 3;

    // Timeout in seconds
    public int $timeout = 600;

    // Delay between attempts (exponential backoff)
    public array $backoff = [30, 60, 120];

    public function __construct(
        public Podcast $podcast
    ) {}

    public function handle(AudioProcessor $processor): void
    {
        // The job executes in the background
        $processor->transcode($this->podcast->audio_path);
        $processor->generateWaveform($this->podcast);
        $this->podcast->update(['status' => 'processed']);
    }

    // Failure handling
    public function failed(\Throwable $exception): void
    {
        $this->podcast->update(['status' => 'failed']);
        // Admin notification, logging, etc.
    }

    // Conditions for retrying the job
    public function retryUntil(): \DateTime
    {
        return now()->addHours(24);
    }
}
php
// Dispatching the job
ProcessPodcast::dispatch($podcast);                    // Default queue
ProcessPodcast::dispatch($podcast)->onQueue('audio'); // Specific queue
ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10)); // Delay

// Job chaining (sequential execution)
Bus::chain([
    new ProcessPodcast($podcast),
    new GenerateThumbnail($podcast),
    new NotifySubscribers($podcast),
])->dispatch();

// Job batch (parallel execution with tracking)
Bus::batch([
    new ProcessPodcast($podcast1),
    new ProcessPodcast($podcast2),
    new ProcessPodcast($podcast3),
])->then(function (Batch $batch) {
    // All jobs succeeded
})->catch(function (Batch $batch, \Throwable $e) {
    // First failure
})->finally(function (Batch $batch) {
    // All jobs finished (success or failure)
})->dispatch();

Jalankan worker dengan php artisan queue:work --queue=high,default untuk memproses beberapa queue berdasarkan prioritas.

Pertanyaan 11: Apa Perbedaan antara Jobs, Events, dan Listeners?

Ketiga konsep ini memisahkan kode, namun dengan tujuan yang berbeda.

php
// Jobs: single task to execute
// Used for heavy or deferred operations
class SendWelcomeEmail implements ShouldQueue
{
    public function handle(Mailer $mailer): void
    {
        $mailer->send(new WelcomeEmail($this->user));
    }
}

// Events: notification that something happened
// The event contains only data, not logic
class UserRegistered
{
    public function __construct(
        public User $user,
        public string $source
    ) {}
}

// Listeners: react to events
// An event can have multiple listeners
class SendWelcomeNotification implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        $event->user->notify(new WelcomeNotification());
    }
}

class TrackRegistration
{
    public function handle(UserRegistered $event): void
    {
        Analytics::track('user_registered', [
            'user_id' => $event->user->id,
            'source' => $event->source,
        ]);
    }
}
app/Providers/EventServiceProvider.phpphp
protected $listen = [
    UserRegistered::class => [
        SendWelcomeNotification::class,  // Queued
        TrackRegistration::class,         // Sync
        CreateDefaultSettings::class,     // Sync
    ],
];

// Triggering the event
event(new UserRegistered($user, 'web'));
// Or
UserRegistered::dispatch($user, 'web');

Events mendukung arsitektur yang terpisah: kode yang memicu event tidak perlu mengetahui konsekuensinya.

Keamanan dan Autentikasi

Pertanyaan 12: Bagaimana Laravel Melindungi dari Serangan CSRF?

Laravel secara otomatis menghasilkan token CSRF unik per sesi dan memverifikasi token ini pada setiap request POST, PUT, PATCH, DELETE.

php
// In Blade forms
<form method="POST" action="/profile">
    @csrf  {{-- Generates a hidden field with the token --}}
    @method('PUT')  {{-- HTTP method spoofing --}}
    <input type="text" name="name" value="{{ $user->name }}">
    <button type="submit">Update</button>
</form>

// For AJAX requests, the token is in the meta tag
<meta name="csrf-token" content="{{ csrf_token() }}">

// Axios configuration to automatically send the token
axios.defaults.headers.common['X-CSRF-TOKEN'] =
    document.querySelector('meta[name="csrf-token"]').content;
bootstrap/app.phpphp
// Exclude routes from CSRF verification (external webhooks)
return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->validateCsrfTokens(except: [
            'stripe/webhook',      // Stripe webhook authenticated by signature
            'api/*',               // API authenticated by token
        ]);
    });

Jangan pernah menonaktifkan proteksi CSRF secara global. Gunakan pengecualian hanya untuk endpoint yang diautentikasi dengan cara lain.

Pertanyaan 13: Bagaimana Mengimplementasikan Autentikasi dengan Laravel Sanctum?

Laravel Sanctum menyediakan autentikasi ringan untuk SPA, aplikasi mobile, dan API berbasis token.

php
// Installation and configuration
// php artisan install:api (Laravel 11+)

// app/Models/User.php
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}
app/Http/Controllers/AuthController.phpphp
class AuthController extends Controller
{
    public function login(Request $request): JsonResponse
    {
        $credentials = $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);

        if (!Auth::attempt($credentials)) {
            return response()->json(['message' => 'Invalid credentials'], 401);
        }

        $user = Auth::user();

        // Create a token with abilities (permissions)
        $token = $user->createToken('api-token', [
            'articles:read',
            'articles:write',
            'profile:update',
        ]);

        return response()->json([
            'user' => $user,
            'token' => $token->plainTextToken,
        ]);
    }

    public function logout(Request $request): JsonResponse
    {
        // Revoke the current token
        $request->user()->currentAccessToken()->delete();

        // Or revoke all tokens
        // $request->user()->tokens()->delete();

        return response()->json(['message' => 'Logged out']);
    }
}

// routes/api.php
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn(Request $r) => $r->user());
    Route::post('/logout', [AuthController::class, 'logout']);

    // Ability verification
    Route::middleware('ability:articles:write')->group(function () {
        Route::post('/articles', [ArticleController::class, 'store']);
    });
});

Sanctum juga mendukung autentikasi cookie untuk SPA satu domain, memberikan proteksi CSRF secara otomatis.

Pertanyaan 14: Bagaimana Mengamankan API dengan Policies dan Gates?

Policies dan Gates memusatkan logika otorisasi, memisahkan aturan bisnis dari controller.

app/Policies/ArticlePolicy.phpphp
namespace App\Policies;

use App\Models\Article;
use App\Models\User;

class ArticlePolicy
{
    // Pre-check: admins have all rights
    public function before(User $user, string $ability): ?bool
    {
        if ($user->isAdmin()) {
            return true; // Allow everything
        }
        return null; // Continue to specific method
    }

    public function view(?User $user, Article $article): bool
    {
        // Published articles visible to all
        if ($article->status === 'published') {
            return true;
        }
        // Drafts visible only to author
        return $user?->id === $article->user_id;
    }

    public function update(User $user, Article $article): bool
    {
        return $user->id === $article->user_id;
    }

    public function delete(User $user, Article $article): bool
    {
        return $user->id === $article->user_id
            && $article->comments()->count() === 0;
    }
}
app/Http/Controllers/ArticleController.phpphp
class ArticleController extends Controller
{
    public function update(Request $request, Article $article)
    {
        // Check policy, throws 403 if unauthorized
        $this->authorize('update', $article);

        $article->update($request->validated());
        return redirect()->route('articles.show', $article);
    }
}

// In Blade
@can('update', $article)
    <a href="{{ route('articles.edit', $article) }}">Edit</a>
@endcan

// Gates for non-model authorizations
Gate::define('access-admin', function (User $user) {
    return $user->role === 'admin';
});

// Usage
if (Gate::allows('access-admin')) {
    // ...
}

Policies terikat pada model, Gates digunakan untuk otorisasi yang bersifat umum.

Validasi dan Form

Pertanyaan 15: Bagaimana Membuat Custom Validation Rules?

Laravel menawarkan beberapa cara untuk membuat validasi kustom tergantung pada kompleksitas dan kebutuhan reusabilitas.

app/Rules/StrongPassword.phpphp
// Custom rule as a class (reusable)
namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class StrongPassword implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $errors = [];

        if (strlen($value) < 12) {
            $errors[] = 'at least 12 characters';
        }
        if (!preg_match('/[A-Z]/', $value)) {
            $errors[] = 'at least one uppercase letter';
        }
        if (!preg_match('/[a-z]/', $value)) {
            $errors[] = 'at least one lowercase letter';
        }
        if (!preg_match('/[0-9]/', $value)) {
            $errors[] = 'at least one digit';
        }
        if (!preg_match('/[@$!%*?&#]/', $value)) {
            $errors[] = 'at least one special character';
        }

        if (!empty($errors)) {
            $fail("The password must contain: " . implode(', ', $errors) . '.');
        }
    }
}
app/Http/Requests/RegisterRequest.phpphp
// Form Request with complex validation
namespace App\Http\Requests;

use App\Rules\StrongPassword;
use Illuminate\Foundation\Http\FormRequest;

class RegisterRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true; // Or authorization logic
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'unique:users,email'],
            'password' => ['required', 'confirmed', new StrongPassword()],
            'company' => ['required_if:account_type,business', 'string'],
            'vat_number' => [
                'nullable',
                'string',
                // Closure for inline validation
                function ($attribute, $value, $fail) {
                    if ($value && !$this->isValidVatNumber($value)) {
                        $fail('The VAT number is invalid.');
                    }
                },
            ],
        ];
    }

    public function messages(): array
    {
        return [
            'email.unique' => 'This email address is already in use.',
            'password.confirmed' => 'The passwords do not match.',
        ];
    }

    protected function prepareForValidation(): void
    {
        // Normalization before validation
        $this->merge([
            'email' => strtolower(trim($this->email)),
        ]);
    }
}

Form Requests memusatkan validasi, otorisasi, dan pesan error, sehingga meringankan controller.

Siap menguasai wawancara Laravel Anda?

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

Testing

Pertanyaan 16: Bagaimana Menyusun Struktur Test di Laravel?

Laravel menyediakan PHPUnit dengan helper khusus untuk menguji berbagai lapisan aplikasi.

tests/Feature/ArticleControllerTest.phpphp
namespace Tests\Feature;

use App\Models\Article;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ArticleControllerTest extends TestCase
{
    use RefreshDatabase; // Resets DB between each test

    public function test_guest_can_view_published_articles(): void
    {
        $article = Article::factory()->published()->create();

        $response = $this->get('/articles/' . $article->slug);

        $response->assertStatus(200);
        $response->assertSee($article->title);
    }

    public function test_authenticated_user_can_create_article(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)
            ->post('/articles', [
                'title' => 'My new article',
                'content' => 'Test content',
            ]);

        $response->assertRedirect();
        $this->assertDatabaseHas('articles', [
            'title' => 'My new article',
            'user_id' => $user->id,
        ]);
    }

    public function test_user_cannot_update_others_article(): void
    {
        $owner = User::factory()->create();
        $other = User::factory()->create();
        $article = Article::factory()->for($owner)->create();

        $response = $this->actingAs($other)
            ->put('/articles/' . $article->id, [
                'title' => 'Modified title',
            ]);

        $response->assertStatus(403);
    }
}
tests/Unit/Services/PaymentServiceTest.phpphp
namespace Tests\Unit\Services;

use App\Services\PaymentService;
use App\Contracts\PaymentGatewayInterface;
use App\Repositories\OrderRepository;
use Mockery;
use Tests\TestCase;

class PaymentServiceTest extends TestCase
{
    public function test_process_payment_charges_correct_amount(): void
    {
        // Mock dependencies
        $gateway = Mockery::mock(PaymentGatewayInterface::class);
        $gateway->shouldReceive('charge')
            ->once()
            ->with(99.99, Mockery::any())
            ->andReturn((object) ['transactionId' => 'tx_123']);

        $orders = Mockery::mock(OrderRepository::class);
        $orders->shouldReceive('find')
            ->with(1)
            ->andReturn($this->createOrder());

        $service = new PaymentService($gateway, $orders);

        $result = $service->processPayment(1, 99.99);

        $this->assertTrue($result);
    }
}

Pisahkan Feature tests (HTTP, integrasi) dari Unit tests (kelas terisolasi dengan mocks).

Pertanyaan 17: Bagaimana Menggunakan Factories dan Seeders Secara Efektif?

Factories menghasilkan data test yang realistis, Seeders mengisi database.

database/factories/ArticleFactory.phpphp
namespace Database\Factories;

use App\Models\Article;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class ArticleFactory extends Factory
{
    protected $model = Article::class;

    public function definition(): array
    {
        return [
            'user_id' => User::factory(),
            'title' => fake()->sentence(6),
            'slug' => fake()->unique()->slug(),
            'content' => fake()->paragraphs(5, true),
            'status' => 'draft',
            'view_count' => fake()->numberBetween(0, 10000),
            'created_at' => fake()->dateTimeBetween('-1 year'),
        ];
    }

    // States for different configurations
    public function published(): static
    {
        return $this->state(fn(array $attr) => [
            'status' => 'published',
            'published_at' => fake()->dateTimeBetween('-6 months'),
        ]);
    }

    public function draft(): static
    {
        return $this->state(['status' => 'draft', 'published_at' => null]);
    }

    public function popular(): static
    {
        return $this->state(['view_count' => fake()->numberBetween(10000, 100000)]);
    }

    // Relationship configuration
    public function configure(): static
    {
        return $this->afterCreating(function (Article $article) {
            // Create tags after article creation
            $article->tags()->attach(
                \App\Models\Tag::factory()->count(3)->create()
            );
        });
    }
}

// Usage in tests
$article = Article::factory()->published()->create();
$articles = Article::factory()->count(10)->for($user)->create();
$articleWithComments = Article::factory()
    ->has(Comment::factory()->count(5))
    ->create();

States memungkinkan pembuatan variasi tanpa menduplikasi logika factory.

Arsitektur dan Pola Desain

Pertanyaan 18: Bagaimana Mengimplementasikan Repository Pattern di Laravel?

Repository Pattern mengabstraksi akses data dan mempermudah pengujian dengan memungkinkan mocking query.

app/Contracts/ArticleRepositoryInterface.phpphp
namespace App\Contracts;

use App\Models\Article;
use Illuminate\Pagination\LengthAwarePaginator;

interface ArticleRepositoryInterface
{
    public function find(int $id): ?Article;
    public function findBySlug(string $slug): ?Article;
    public function getPublished(int $perPage = 20): LengthAwarePaginator;
    public function getByAuthor(int $userId, int $perPage = 20): LengthAwarePaginator;
    public function create(array $data): Article;
    public function update(Article $article, array $data): Article;
    public function delete(Article $article): bool;
}
app/Repositories/EloquentArticleRepository.phpphp
namespace App\Repositories;

use App\Contracts\ArticleRepositoryInterface;
use App\Models\Article;
use Illuminate\Pagination\LengthAwarePaginator;

class EloquentArticleRepository implements ArticleRepositoryInterface
{
    public function __construct(
        private Article $model
    ) {}

    public function find(int $id): ?Article
    {
        return $this->model->with('author')->find($id);
    }

    public function findBySlug(string $slug): ?Article
    {
        return $this->model
            ->where('slug', $slug)
            ->with(['author', 'tags'])
            ->firstOrFail();
    }

    public function getPublished(int $perPage = 20): LengthAwarePaginator
    {
        return $this->model
            ->published()
            ->with('author')
            ->orderByDesc('published_at')
            ->paginate($perPage);
    }

    public function create(array $data): Article
    {
        return $this->model->create($data);
    }

    public function update(Article $article, array $data): Article
    {
        $article->update($data);
        return $article->fresh();
    }

    public function delete(Article $article): bool
    {
        return $article->delete();
    }
}

// Binding in the ServiceProvider
$this->app->bind(
    ArticleRepositoryInterface::class,
    EloquentArticleRepository::class
);

Repository Pattern berguna untuk aplikasi kompleks, namun bisa berlebihan untuk proyek sederhana. Evaluasi rasio biaya/manfaatnya.

Pertanyaan 19: Bagaimana Menangani Transaksi dan Konkurensi di Laravel?

Transaksi memastikan integritas data selama operasi jamak. Laravel menyederhanakan pengelolaannya.

app/Services/OrderService.phpphp
use Illuminate\Support\Facades\DB;

class OrderService
{
    public function processOrder(Cart $cart, User $user): Order
    {
        // Transaction with closure: automatic rollback on exception
        return DB::transaction(function () use ($cart, $user) {
            // Create the order
            $order = Order::create([
                'user_id' => $user->id,
                'total' => $cart->total(),
                'status' => 'pending',
            ]);

            // Create order items
            foreach ($cart->items as $item) {
                $order->items()->create([
                    'product_id' => $item->product_id,
                    'quantity' => $item->quantity,
                    'price' => $item->product->price,
                ]);

                // Decrement stock with pessimistic locking
                $product = Product::lockForUpdate()->find($item->product_id);

                if ($product->stock < $item->quantity) {
                    throw new InsufficientStockException($product);
                }

                $product->decrement('stock', $item->quantity);
            }

            // Clear the cart
            $cart->clear();

            return $order;
        }, attempts: 3); // 3 attempts in case of deadlock
    }

    public function updateOrderStatus(Order $order, string $status): void
    {
        // Optimistic locking with version/timestamp
        $updated = DB::table('orders')
            ->where('id', $order->id)
            ->where('updated_at', $order->updated_at) // Version check
            ->update([
                'status' => $status,
                'updated_at' => now(),
            ]);

        if ($updated === 0) {
            throw new ConcurrencyException('The order was modified in the meantime');
        }
    }
}

lockForUpdate() mencegah pembacaan bersamaan selama transaksi. Gunakan secara hemat untuk menghindari deadlock.

Pertanyaan 20: Bagaimana Mengimplementasikan Caching Secara Efektif di Laravel?

Caching secara drastis meningkatkan performa dengan menghindari query yang berulang.

php
// Caching strategies
use Illuminate\Support\Facades\Cache;

class ArticleService
{
    public function getPopularArticles(): Collection
    {
        // Cache-Aside: check cache, otherwise load and store
        return Cache::remember('articles:popular', 3600, function () {
            return Article::published()
                ->popular()
                ->with('author')
                ->limit(10)
                ->get();
        });
    }

    public function getArticle(string $slug): Article
    {
        // Cache by dynamic key
        return Cache::remember("article:{$slug}", 1800, function () use ($slug) {
            return Article::where('slug', $slug)
                ->with(['author', 'comments.user'])
                ->firstOrFail();
        });
    }

    public function updateArticle(Article $article, array $data): Article
    {
        $article->update($data);

        // Cache invalidation after modification
        Cache::forget("article:{$article->slug}");
        Cache::forget('articles:popular');

        // Tags for grouped invalidation (Redis only)
        Cache::tags(['articles', "user:{$article->user_id}"])->flush();

        return $article;
    }

    public function getArticleWithLock(int $id): Article
    {
        // Atomic lock to avoid cache stampede
        return Cache::lock("article-lock:{$id}", 10)->block(5, function () use ($id) {
            return Cache::remember("article:{$id}", 3600, fn() => Article::findOrFail($id));
        });
    }
}
config/cache.php - Recommended Redis configuration for productionphp
'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
        'lock_connection' => 'default',
    ],
],

Gunakan cache tags untuk invalidasi grup yang efisien. Waspadai cache stampede saat ekspirasi bersamaan.

Performa dan Optimasi

Pertanyaan 21: Bagaimana Mengoptimalkan Performa Aplikasi Laravel?

Optimasi mencakup beberapa level: query, cache, konfigurasi, dan infrastruktur.

1. Eloquent query optimizationphp
$articles = Article::query()
    ->select(['id', 'title', 'slug', 'published_at', 'user_id']) // Specific columns
    ->with(['author:id,name,avatar'])  // Selective eager loading
    ->withCount('comments')             // Count in one query
    ->published()
    ->latest('published_at')
    ->cursorPaginate(20);               // Cursor pagination (more performant)

// 2. Chunking for mass operations
Article::query()
    ->where('status', 'published')
    ->chunkById(1000, function ($articles) {
        foreach ($articles as $article) {
            // Processing in batches of 1000
            ProcessArticle::dispatch($article);
        }
    });

// 3. Mass update without models
Article::where('published_at', '<', now()->subYear())
    ->update(['status' => 'archived']); // Single SQL query
bash
# Optimization commands for production
php artisan config:cache    # Cache configuration
php artisan route:cache     # Cache routes
php artisan view:cache      # Compile Blade views
php artisan event:cache     # Cache event mappings
php artisan optimize        # Run all optimizations

# Optimized autoloader
composer install --optimize-autoloader --no-dev

Optimasi ini dapat mengurangi waktu boot hingga 50% atau lebih di lingkungan produksi.

Pertanyaan 22: Bagaimana Melakukan Debug dan Profiling pada Aplikasi Laravel?

Laravel menawarkan beberapa tools untuk mengidentifikasi masalah performa dan bug.

php
// Laravel Telescope for development debugging
// Captures requests, jobs, exceptions, etc.

// Query debugging
DB::enableQueryLog();
$articles = Article::with('author')->get();
$queries = DB::getQueryLog();
dump($queries); // Shows all SQL queries

// Debug bar integrated with Blade
@dump($variable)   // Display and continue
@dd($variable)     // Dump and die

// Structured logging
use Illuminate\Support\Facades\Log;

Log::channel('slack')->critical('Payment failed', [
    'user_id' => $user->id,
    'amount' => $amount,
    'error' => $exception->getMessage(),
    'trace' => $exception->getTraceAsString(),
]);

// Context logging
Log::withContext(['request_id' => request()->id()]);
Log::info('Processing order', ['order_id' => $order->id]);
config/logging.php - Channel configurationphp
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily', 'slack'],
        'ignore_exceptions' => false,
    ],
    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'debug',
        'days' => 14,
    ],
],

Gunakan Telescope saat pengembangan dan APM (New Relic, Datadog) di produksi untuk pemantauan berkelanjutan.

Deployment dan Produksi

Pertanyaan 23: Bagaimana Menangani Migrasi di Produksi Tanpa Downtime?

Migrasi di produksi memerlukan perhatian khusus untuk menghindari gangguan layanan.

database/migrations/2026_01_30_add_role_to_users_table.phpphp
// Safe migration: add a nullable column first
public function up(): void
{
    Schema::table('users', function (Blueprint $table) {
        // Step 1: Add nullable column
        $table->string('role')->nullable()->after('email');
    });
}

// Step 2: Data migration (separate job)
// php artisan tinker
// User::whereNull('role')->update(['role' => 'user']);

// Step 3: Second migration for constraint
public function up(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('role')->nullable(false)->default('user')->change();
    });
}
php
// For column deletions (3 deployments)
// Deployment 1: Stop using the column in code
// Deployment 2: Delete the column
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('deprecated_field');
});

// Migration with timeout for large tables
public function up(): void
{
    DB::statement('SET lock_timeout TO \'5s\'');

    Schema::table('large_table', function (Blueprint $table) {
        $table->index('status'); // Concurrent index if PostgreSQL
    });
}

Strategi "expand-contract" memungkinkan penambahan kolom tanpa downtime: tambahkan nullable → migrasi data → buat non-nullable.

Pertanyaan 24: Bagaimana Mengonfigurasi Laravel untuk High Availability?

Arsitektur high availability memerlukan pemisahan komponen stateless dan pengelolaan state bersama.

config/session.php - Sessions shared between instancesphp
'driver' => env('SESSION_DRIVER', 'redis'),
'connection' => 'session',

// config/cache.php - Shared cache
'default' => env('CACHE_DRIVER', 'redis'),

// config/queue.php - Redis queues for distribution
'default' => env('QUEUE_CONNECTION', 'redis'),
'connections' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => 'default',
        'retry_after' => 90,
        'block_for' => null,
    ],
],

// config/filesystems.php - S3 storage for files
'disks' => [
    's3' => [
        'driver' => 's3',
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION'),
        'bucket' => env('AWS_BUCKET'),
    ],
],
php
// Health check endpoint for load balancer
Route::get('/health', function () {
    try {
        DB::connection()->getPdo();
        Cache::store('redis')->ping();
        return response()->json(['status' => 'healthy']);
    } catch (\Exception $e) {
        return response()->json(['status' => 'unhealthy'], 503);
    }
});

Setiap instance harus stateless. Session, cache, dan queue sebaiknya menggunakan Redis atau penyimpanan bersama.

Pertanyaan 25: Apa Saja Praktik Terbaik Deployment Laravel?

Deployment yang andal menggabungkan otomasi, pemeriksaan, dan kemampuan rollback yang mudah.

bash
# deploy.sh - Typical deployment script
#!/bin/bash
set -e
echo "Pulling latest code..."
git pull origin main

echo "Installing dependencies..."
composer install --no-dev --optimize-autoloader

echo "Running migrations..."
php artisan migrate --force

echo "Caching configuration..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

echo "Restarting queue workers..."
php artisan queue:restart

echo "Clearing old cache..."
php artisan cache:clear

echo "Deployment complete!"
.env.production - Critical variablesphp
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:... # Generated with php artisan key:generate

LOG_CHANNEL=stack
LOG_LEVEL=warning

// Never expose credentials in plain text
// Use secret managers (Vault, AWS Secrets Manager)

Checklist deployment:

  • php artisan config:cache - Cache konfigurasi
  • php artisan route:cache - Cache routes
  • php artisan view:cache - Kompilasi views
  • composer install --no-dev - Dependensi produksi
  • Automated tests sebelum deployment
  • Health checks terkonfigurasi
  • Monitoring dan alerting aktif

Kesimpulan

25 pertanyaan ini mencakup hal-hal esensial wawancara Laravel dan PHP, mulai dari dasar-dasar Service Container hingga pola deployment produksi.

Checklist persiapan:

  • Service Container dan dependency injection
  • Eloquent ORM: relasi, scopes, eager loading
  • Middleware, routing, dan keamanan
  • Queues, jobs, dan event asinkron
  • Testing: Feature tests, Unit tests, Factories
  • Pola lanjutan: Repository, Transaksi, Caching
  • Deployment: migrasi, optimasi, high availability
Pelajari Lebih Lanjut

Setiap pertanyaan layak dieksplorasi lebih dalam dengan dokumentasi resmi Laravel. Pewawancara menghargai kandidat yang memahami seluk-beluk framework dan mampu membenarkan pilihan teknis mereka.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tag

#laravel
#php
#interview
#eloquent
#technical interview

Bagikan

Artikel terkait