# 25 คำถามสัมภาษณ์งาน Laravel และ PHP ยอดนิยมในปี 2026 > รวม 25 คำถามสัมภาษณ์งาน Laravel และ PHP ที่พบบ่อยที่สุด ครอบคลุม Service Container, Eloquent ORM, middleware, queues และการ deploy ระบบ production พร้อมคำตอบและตัวอย่างโค้ดครบถ้วน - Published: 2026-01-30 - Updated: 2026-04-12 - Author: SharpSkill - Tags: laravel, php, interview, eloquent, technical interview - Reading time: 22 min --- การสัมภาษณ์งาน Laravel ประเมินความเชี่ยวชาญใน PHP framework ที่ได้รับความนิยมสูงสุด ความเข้าใจเกี่ยวกับ Eloquent ORM สถาปัตยกรรม MVC และความสามารถในการสร้างแอปพลิเคชันที่แข็งแกร่งและดูแลรักษาได้ง่าย คู่มือนี้ครอบคลุม 25 คำถามที่ถูกถามบ่อยที่สุด ตั้งแต่พื้นฐานของ Laravel ไปจนถึงรูปแบบการ deploy ระบบ production ขั้นสูง > **เคล็ดลับการสัมภาษณ์** > > ผู้สัมภาษณ์ให้ความสำคัญกับผู้สมัครที่สามารถอธิบายการตัดสินใจด้านสถาปัตยกรรมของ Laravel ได้ การเข้าใจว่าทำไม framework จึงใช้แนวทางบางอย่าง (Convention over Configuration, Service Container) สร้างความแตกต่างอย่างแท้จริงในการสัมภาษณ์ ## พื้นฐาน Laravel ### คำถามที่ 1: อธิบายวงจรชีวิตของ Request ใน Laravel วงจรชีวิตของ request ใน Laravel ผ่านหลายชั้นก่อนจะถึง controller การเข้าใจวงจรนี้มีความสำคัญอย่างยิ่งสำหรับการ debug และการปรับปรุงประสิทธิภาพ ```php // public/index.php // 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); ``` วงจรที่สมบูรณ์: `index.php` -> autoload -> bootstrap -> service providers -> middlewares -> routing -> controller -> response -> terminate แต่ละขั้นตอนสามารถสกัดกั้นและปรับแต่งได้ ### คำถามที่ 2: Service Container คืออะไร และ Dependency Injection ทำงานอย่างไร? Service Container เป็นหัวใจหลักของ Laravel ทำหน้าที่จัดการการสร้าง instance ของ class และ resolve dependency โดยอัตโนมัติผ่าน constructor injection ```php // app/Services/PaymentService.php // 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; } } } ``` ```php // app/Providers/AppServiceProvider.php // 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 แยก class ออกจากกันและช่วยให้การทำ unit testing ผ่าน mocking ง่ายขึ้น ### คำถามที่ 3: Facades กับ Dependency Injection แตกต่างกันอย่างไร? Facades ให้ syntax แบบ static เพื่อเข้าถึง service ใน container ขณะที่ dependency injection ทำให้ dependency ชัดเจน ```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')); } } ``` ควรใช้ dependency injection ใน business class เพื่อความสะดวกในการทดสอบ Facades เหมาะสำหรับ helper และโค้ดแบบ ad-hoc ### คำถามที่ 4: Service Providers ทำงานอย่างไรใน Laravel? Service Providers เป็นจุดกำหนดค่ากลางของแอปพลิเคชัน แต่ละ provider ลงทะเบียน service กำหนดค่า binding และเริ่มต้นส่วนประกอบต่างๆ ```php // app/Providers/PaymentServiceProvider.php 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'); } } ``` ลำดับการทำงาน: method `register()` ทั้งหมดทำงานก่อน จากนั้น method `boot()` ทั้งหมดจึงทำงาน ลำดับนี้ทำให้มั่นใจว่า dependency พร้อมใช้งานในระหว่าง boot ## Eloquent ORM ### คำถามที่ 5: อธิบายความสัมพันธ์ของ Eloquent และความแตกต่าง Eloquent มีหลายประเภทของ relationship เพื่อจำลองความสัมพันธ์ระหว่างตาราง แต่ละประเภทมีกรณีการใช้งานที่เฉพาะเจาะจง ```php // app/Models/User.php 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'); } } ``` Polymorphic relationships (`morphOne`, `morphMany`, `morphToMany`) ช่วยให้ model หนึ่งสามารถเชื่อมโยงกับ model หลายประเภทผ่าน relationship เดียว ### คำถามที่ 6: ปัญหา N+1 คืออะไร และแก้ไขด้วย Eager Loading อย่างไร? ปัญหา N+1 เกิดขึ้นเมื่อ query หลักหนึ่งรายการสร้าง N query เพิ่มเติมเพื่อโหลด relationship นี่เป็นสาเหตุที่พบบ่อยที่สุดของความช้าในแอปพลิเคชัน 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']; } ``` ใช้ `php artisan telescope:prune` ร่วมกับ Laravel Telescope เพื่อตรวจจับปัญหา N+1 ระหว่างการพัฒนา ### คำถามที่ 7: วิธีสร้าง Query Scopes และควรใช้เมื่อไหร่? Query Scopes ห่อหุ้มเงื่อนไข query ที่สามารถนำกลับมาใช้ซ้ำได้ในระดับ model ทำให้โค้ดอ่านง่ายขึ้นและเป็นไปตามหลัก DRY ```php // app/Models/Article.php 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 ช่วยเพิ่มความอ่านง่ายและรวมศูนย์ logic ของ query Global scopes เหมาะสำหรับ multi-tenancy หรือ soft delete ## Middleware และ Routing ### คำถามที่ 8: Middleware ทำงานอย่างไรใน Laravel? Middleware กรอง HTTP request ขาเข้าและสามารถแก้ไข response ขาออกได้ ทุก request ผ่าน middleware stack ```php // app/Http/Middleware/CheckSubscription.php 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, ]); }); ``` ```php // routes/web.php // Applying middlewares to routes Route::middleware(['auth', 'subscription:premium'])->group(function () { Route::get('/dashboard', DashboardController::class); Route::resource('projects', ProjectController::class); }); ``` ลำดับของ middleware มีความสำคัญ: ทำงานจากบนลงล่างเมื่อเข้า และจากล่างขึ้นบนเมื่อออก ### คำถามที่ 9: อธิบาย Route Model Binding และรูปแบบต่างๆ Route Model Binding inject Eloquent model เข้าสู่ controller โดยอัตโนมัติตาม parameter ใน URL ```php // routes/web.php // 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(); ``` ```php // app/Models/Article.php 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(); } } ``` ```php // app/Providers/RouteServiceProvider.php // 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 ลดโค้ด boilerplate และรวมศูนย์ logic การ resolve ## Queues และ Jobs ### คำถามที่ 10: วิธีใช้งาน Jobs และ Queues ใน Laravel? Queues ช่วยเลื่อนงานหนักไปทำงานในเบื้องหลัง ทำให้แอปพลิเคชันตอบสนองได้เร็วขึ้น ```php // app/Jobs/ProcessPodcast.php 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(); ``` เริ่ม worker ด้วย `php artisan queue:work --queue=high,default` เพื่อประมวลผลหลาย queue ตามลำดับความสำคัญ ### คำถามที่ 11: Jobs, Events และ Listeners แตกต่างกันอย่างไร? ทั้งสามแนวคิดนี้แยก code ออกจากกัน แต่มีจุดประสงค์ที่แตกต่างกัน ```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, ]); } } ``` ```php // app/Providers/EventServiceProvider.php 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 สนับสนุนสถาปัตยกรรมแบบหลวม: โค้ดที่ trigger event ไม่จำเป็นต้องรู้ผลที่ตามมา ## ความปลอดภัยและการยืนยันตัวตน ### คำถามที่ 12: Laravel ป้องกันการโจมตี CSRF อย่างไร? Laravel สร้าง token CSRF เฉพาะสำหรับแต่ละ session โดยอัตโนมัติ และตรวจสอบ token นี้ในทุก request ประเภท POST, PUT, PATCH, DELETE ```php // In Blade forms
// For AJAX requests, the token is in the meta tag // Axios configuration to automatically send the token axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').content; ``` ```php // bootstrap/app.php // 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 ]); }); ``` ห้ามปิดการป้องกัน CSRF ทั้งระบบ ใช้ข้อยกเว้นเฉพาะสำหรับ endpoint ที่มีการยืนยันตัวตนด้วยวิธีอื่น ### คำถามที่ 13: วิธีใช้งาน Authentication ด้วย Laravel Sanctum? Laravel Sanctum ให้การยืนยันตัวตนแบบเบาสำหรับ SPA แอปมือถือ และ API แบบ 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; } ``` ```php // app/Http/Controllers/AuthController.php 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 ยังรองรับการยืนยันตัวตนด้วย cookie สำหรับ SPA บนโดเมนเดียวกัน ซึ่งให้การป้องกัน CSRF โดยอัตโนมัติ ### คำถามที่ 14: วิธีรักษาความปลอดภัย API ด้วย Policies และ Gates? Policies และ Gates รวมศูนย์ logic การอนุญาต แยกกฎเกณฑ์ทางธุรกิจออกจาก controller ```php // app/Policies/ArticlePolicy.php 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; } } ``` ```php // app/Http/Controllers/ArticleController.php 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) Edit @endcan // Gates for non-model authorizations Gate::define('access-admin', function (User $user) { return $user->role === 'admin'; }); // Usage if (Gate::allows('access-admin')) { // ... } ``` Policies ผูกกับ model ส่วน Gates ใช้สำหรับการอนุญาตทั่วไป ## Validation และ Forms ### คำถามที่ 15: วิธีสร้าง Custom Validation Rules? Laravel มีหลายวิธีในการสร้าง validation แบบกำหนดเอง ขึ้นอยู่กับความซับซ้อนและความต้องการในการนำกลับมาใช้ ```php // app/Rules/StrongPassword.php // 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) . '.'); } } } ``` ```php // app/Http/Requests/RegisterRequest.php // 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 รวมศูนย์ validation การอนุญาต และข้อความ error ทำให้ controller เบาลง ## Testing ### คำถามที่ 16: วิธีจัดโครงสร้าง Tests ใน Laravel? Laravel มี PHPUnit พร้อม helper เฉพาะสำหรับทดสอบแต่ละชั้นของแอปพลิเคชัน ```php // tests/Feature/ArticleControllerTest.php 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); } } ``` ```php // tests/Unit/Services/PaymentServiceTest.php 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); } } ``` แยก Feature tests (HTTP, integration) ออกจาก Unit tests (class แบบแยกส่วนพร้อม mocks) ### คำถามที่ 17: วิธีใช้ Factories และ Seeders อย่างมีประสิทธิภาพ? Factories สร้างข้อมูลทดสอบที่สมจริง Seeders เติมข้อมูลลงในฐานข้อมูล ```php // database/factories/ArticleFactory.php 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 ช่วยให้สร้างรูปแบบต่างๆ ได้โดยไม่ต้องเขียน logic ของ factory ซ้ำ ## สถาปัตยกรรมและ Design Patterns ### คำถามที่ 18: วิธีใช้งาน Repository Pattern ใน Laravel? Repository Pattern ทำให้การเข้าถึงข้อมูลเป็นนามธรรมและช่วยให้การทดสอบง่ายขึ้นโดยอนุญาตให้ mock query ได้ ```php // app/Contracts/ArticleRepositoryInterface.php 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; } ``` ```php // app/Repositories/EloquentArticleRepository.php 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 มีประโยชน์สำหรับแอปพลิเคชันที่ซับซ้อน แต่อาจมากเกินไปสำหรับโปรเจกต์ง่ายๆ ควรประเมินอัตราส่วนต้นทุน/ผลประโยชน์ ### คำถามที่ 19: วิธีจัดการ Transactions และ Concurrency ใน Laravel? Transactions รับประกันความถูกต้องของข้อมูลระหว่างการดำเนินการหลายรายการ Laravel ทำให้การจัดการง่ายขึ้น ```php // app/Services/OrderService.php 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()` ป้องกันการอ่านพร้อมกันระหว่าง transaction ใช้อย่างระมัดระวังเพื่อหลีกเลี่ยง deadlock ### คำถามที่ 20: วิธีใช้งาน Caching อย่างมีประสิทธิภาพใน Laravel? Caching ช่วยเพิ่มประสิทธิภาพอย่างมากโดยหลีกเลี่ยง query ที่ซ้ำซ้อน ```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)); }); } } ``` ```php // config/cache.php - Recommended Redis configuration for production 'stores' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'cache', 'lock_connection' => 'default', ], ], ``` ใช้ cache tags สำหรับการยกเลิกแบบกลุ่มอย่างมีประสิทธิภาพ ระวัง cache stampede เมื่อหมดอายุพร้อมกัน ## ประสิทธิภาพและการปรับแต่ง ### คำถามที่ 21: วิธีปรับปรุงประสิทธิภาพแอปพลิเคชัน Laravel? การปรับแต่งครอบคลุมหลายระดับ: query, cache, การกำหนดค่า และโครงสร้างพื้นฐาน ```php // 1. Eloquent query optimization $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 ``` การปรับแต่งเหล่านี้สามารถลดเวลา boot ได้ถึง 50% หรือมากกว่าในสภาพแวดล้อม production ### คำถามที่ 22: วิธี Debug และ Profile แอปพลิเคชัน Laravel? Laravel มีเครื่องมือหลายอย่างสำหรับระบุปัญหาด้านประสิทธิภาพและ 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]); ``` ```php // config/logging.php - Channel configuration 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['daily', 'slack'], 'ignore_exceptions' => false, ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', 'days' => 14, ], ], ``` ใช้ Telescope ในขั้นตอนการพัฒนาและ APM (New Relic, Datadog) ใน production สำหรับการตรวจสอบอย่างต่อเนื่อง ## Deployment และ Production ### คำถามที่ 23: วิธีจัดการ Migrations ใน Production โดยไม่มี Downtime? Migrations ใน production ต้องได้รับความใส่ใจเป็นพิเศษเพื่อหลีกเลี่ยงการหยุดชะงักของบริการ ```php // database/migrations/2026_01_30_add_role_to_users_table.php // 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 }); } ``` กลยุทธ์ "expand-contract" ช่วยให้เพิ่มคอลัมน์โดยไม่มี downtime: เพิ่ม nullable -> migrate ข้อมูล -> ทำให้เป็น non-nullable ### คำถามที่ 24: วิธีตั้งค่า Laravel สำหรับ High Availability? สถาปัตยกรรม high availability ต้องการการแยกส่วนประกอบ stateless และการจัดการ state ที่ใช้ร่วมกัน ```php // config/session.php - Sessions shared between instances '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); } }); ``` ทุก instance ต้องเป็น stateless Session, cache และ queue ควรใช้ Redis หรือ shared storage ### คำถามที่ 25: แนวทางปฏิบัติที่ดีที่สุดสำหรับ Deployment Laravel? การ deploy ที่เชื่อถือได้รวมการทำงานอัตโนมัติ การตรวจสอบ และการ rollback ที่ง่ายดาย ```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!" ``` ```php // .env.production - Critical variables 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 - แคชการกำหนดค่า - php artisan route:cache - แคช routes - php artisan view:cache - คอมไพล์ views - composer install --no-dev - Dependencies สำหรับ production - ทดสอบอัตโนมัติก่อน deploy - ตั้งค่า health checks แล้ว - ระบบ monitoring และ alerting พร้อมใช้งาน ## สรุป 25 คำถามเหล่านี้ครอบคลุมสาระสำคัญของการสัมภาษณ์งาน Laravel และ PHP ตั้งแต่พื้นฐาน Service Container ไปจนถึงรูปแบบการ deploy ระบบ production Checklist การเตรียมตัว: - Service Container และ dependency injection - Eloquent ORM: relationships, scopes, eager loading - Middleware, routing และความปลอดภัย - Queues, jobs และ events แบบ asynchronous - Testing: Feature tests, Unit tests, Factories - Patterns ขั้นสูง: Repository, Transactions, Caching - Deployment: migrations, การปรับแต่ง, high availability > **ศึกษาเพิ่มเติม** > > แต่ละคำถามควรศึกษาเพิ่มเติมจากเอกสารทางการของ Laravel ผู้สัมภาษณ์ให้ความสำคัญกับผู้สมัครที่เข้าใจรายละเอียดปลีกย่อยของ framework และสามารถอธิบายเหตุผลของการเลือกทางเทคนิคได้ --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/laravel/laravel-php-interview-questions