25 запитань на співбесіді з Laravel та PHP у 2026 році
25 найпоширеніших запитань на співбесіді з Laravel: Service Container, Eloquent ORM, middleware, черги, безпека, тестування та архітектурні патерни з прикладами коду.

Співбесіди з Laravel перевіряють володіння фреймворком, знання екосистеми PHP та здатність проєктувати масштабовані веб-додатки. Цей посібник охоплює 25 запитань, які найчастіше зустрічаються на технічних інтерв'ю — від основ фреймворку до оптимізації продуктивності та розгортання на продакшені.
Основи Laravel
1. Що таке Service Container і яку роль він виконує в Laravel?
Service Container — це серце фреймворку Laravel, потужний механізм управління залежностями класів та виконання dependency injection. Кожен додаток Laravel має єдиний контейнер, який реєструє та розв'язує зв'язки між інтерфейсами та їхніми конкретними реалізаціями. Завдяки цьому код залишається слабо зв'язаним і легким для тестування.
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
public function register(): void
{
// Bind interface to concrete implementation
$this->app->bind(PaymentGateway::class, StripePaymentGateway::class);
// Singleton: resolved once, same instance reused
$this->app->singleton(PaymentGateway::class, function ($app) {
return new StripePaymentGateway(
config('services.stripe.secret')
);
});
}class OrderController extends Controller
{
// Automatic injection via type-hinting
public function __construct(
private readonly PaymentGateway $payment
) {}
public function store(Request $request): JsonResponse
{
$result = $this->payment->charge(
$request->validated('amount')
);
return response()->json($result);
}
}Контейнер автоматично розв'язує залежності на основі type-hint'ів. Якщо клас залежить від інтерфейсу, зареєстрованого в контейнері, Laravel ін'єктує відповідну реалізацію без додаткової конфігурації.
2. Яка різниця між Service Provider та Service Container?
Service Container — це механізм зберігання та розв'язання залежностей. Service Provider — це клас, відповідальний за реєстрацію зв'язків у контейнері та конфігурацію сервісів додатку. Кожен провайдер має два методи: register() для прив'язки сервісів та boot() для дій, що потребують вже зареєстрованих сервісів.
class AnalyticsServiceProvider extends ServiceProvider
{
public function register(): void
{
// register() - only bind things into the container
$this->app->singleton(AnalyticsTracker::class, function ($app) {
return new AnalyticsTracker(
$app->make(HttpClient::class),
config('analytics.key')
);
});
}
public function boot(): void
{
// boot() - all providers are registered, safe to use them
$tracker = $this->app->make(AnalyticsTracker::class);
$tracker->registerDefaultEvents();
// Publish config for package users
$this->publishes([
__DIR__.'/../config/analytics.php' => config_path('analytics.php'),
]);
}
}Провайдер вказується в масиві providers у config/app.php. Laravel завантажує їх послідовно — спочатку викликає register() у всіх провайдерів, а потім boot(), що гарантує доступність усіх сервісів під час конфігурації.
3. Як працює життєвий цикл HTTP-запиту в Laravel?
Кожен HTTP-запит у Laravel проходить через чітко визначену послідовність етапів. Файл public/index.php завантажує автозавантажувач Composer і створює екземпляр додатку. Далі запит проходить через HTTP kernel, який виконує глобальні middleware, а маршрутизатор зіставляє запит з відповідним маршрутом. Контролер обробляє логіку та повертає відповідь, яка знову проходить через middleware у зворотному порядку.
// Simplified request lifecycle
// 1. public/index.php - Entry point
$app = require_once __DIR__.'/../bootstrap/app.php';
// 2. HTTP Kernel handles the request
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
);
// 3. Inside Kernel: middleware pipeline
// Global middleware -> Route middleware -> Controller
// 4. Router matches the request
Route::get('/orders/{order}', [OrderController::class, 'show'])
->middleware(['auth', 'verified']);
// 5. Controller returns response
// 6. Response passes back through middleware
// 7. Response sent to browser
$response->send();
$kernel->terminate($request, $response);Метод terminate() в кінці циклу дозволяє виконувати операції після відправки відповіді — наприклад, запис логів чи закриття з'єднань.
4. Що таке Facade в Laravel і як вони працюють під капотом?
Facade надають статичний синтаксис доступу до сервісів, зареєстрованих у Service Container. Проте це не справжні статичні класи — під капотом кожен фасад делегує виклик відповідному об'єкту, розв'язаному з контейнера. Метод getFacadeAccessor() повертає ключ прив'язки в контейнері.
// How facades work under the hood
// Using the Cache facade
Cache::put('user_123', $userData, 3600);
// Is equivalent to:
app('cache')->put('user_123', $userData, 3600);
// The facade class:
class Cache extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'cache'; // Key in the service container
}
}
// Real-time facades - any class can become a facade
use Facades\App\Services\PaymentGateway;
PaymentGateway::charge(1000); // Resolves from containerFacade полегшують тестування — Laravel дозволяє їх мокати за допомогою Cache::shouldReceive(), що робить модульні тести простими та зрозумілими.
Eloquent ORM
5. Які відмінності між відношеннями hasOne, hasMany, belongsTo та belongsToMany?
Eloquent визначає відношення як методи в моделях. hasOne означає відношення один-до-одного, де зовнішній ключ знаходиться у пов'язаній таблиці. hasMany — це відношення один-до-багатьох. belongsTo є зворотнім — модель містить зовнішній ключ. belongsToMany обслуговує відношення багато-до-багатьох з проміжною таблицею (pivot).
class User extends Model
{
// One-to-One: users.id -> profiles.user_id
public function profile(): HasOne
{
return $this->hasOne(Profile::class);
}
// One-to-Many: users.id -> posts.user_id
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
// Many-to-Many: users <-> role_user <-> roles
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class)
->withPivot('assigned_at')
->withTimestamps();
}
}
// Post.php
class Post extends Model
{
// Inverse of hasMany
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}// Usage examples
$user = User::find(1);
// Lazy loading
$profile = $user->profile; // Single query
$posts = $user->posts; // Collection of posts
// Eager loading to avoid N+1
$users = User::with(['posts', 'roles'])->get();
// Querying relationships
$activeAuthors = User::whereHas('posts', function ($query) {
$query->where('published', true)
->where('created_at', '>=', now()->subMonth());
})->get();Вибір правильного відношення впливає на продуктивність запитів та читабельність коду. Eager loading за допомогою with() усуває проблему N+1.
6. Як працюють Eloquent Scopes і коли варто їх використовувати?
Scopes дозволяють інкапсулювати повторювані умови запитів у методах моделі. Є два види: Local Scopes, що викликаються явно в ланцюжку запитів, та Global Scopes, що застосовуються автоматично до кожного запиту моделі.
class Post extends Model
{
// Local scope - called explicitly
public function scopePublished(Builder $query): Builder
{
return $query->where('status', 'published')
->whereNotNull('published_at');
}
public function scopeByAuthor(Builder $query, int $userId): Builder
{
return $query->where('user_id', $userId);
}
// Chainable scopes
public function scopeRecent(Builder $query, int $days = 7): Builder
{
return $query->where('created_at', '>=', now()->subDays($days));
}
}
// Usage: clean, readable queries
$posts = Post::published()
->recent(30)
->byAuthor($userId)
->paginate(15);// Global Scope - applied to ALL queries on the model
class ActiveScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
$builder->where('is_active', true);
}
}
// In the model
class Subscription extends Model
{
protected static function booted(): void
{
static::addGlobalScope(new ActiveScope());
}
}
// All queries automatically filter active
Subscription::all(); // WHERE is_active = true
// Remove global scope when needed
Subscription::withoutGlobalScope(ActiveScope::class)->get();Scopes покращують читабельність коду та запобігають дублюванню логіки фільтрації. Global Scopes слід застосовувати обережно — автоматична фільтрація може призводити до помилок, які важко виявити.
7. Що таке Eloquent Mutators та Casts і як вони впливають на атрибути моделі?
Mutators та Casts контролюють спосіб запису та читання атрибутів моделі. Casts визначають автоматичне перетворення типів, а Mutators (accessor та mutator) дозволяють виконувати довільну трансформацію значень через методи Attribute.
class User extends Model
{
// Automatic type casting
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'preferences' => 'array', // JSON <-> array
'is_admin' => 'boolean',
'salary' => 'decimal:2',
'metadata' => AsCollection::class,
'secret' => 'encrypted', // Auto encrypt/decrypt
];
}
// Modern accessor (Laravel 9+)
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}
// Accessor + Mutator combined
protected function email(): Attribute
{
return Attribute::make(
get: fn (string $value) => strtolower($value),
set: fn (string $value) => strtolower(trim($value)),
);
}
}// Custom Cast class for complex transformations
class MoneyCast implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes): Money
{
return new Money(
amount: $attributes[$key],
currency: $attributes['{$key}_currency'] ?? 'USD'
);
}
public function set(Model $model, string $key, mixed $value, array $attributes): array
{
return [
$key => $value->amount,
"{$key}_currency" => $value->currency,
];
}
}
// Usage in model
protected function casts(): array
{
return [
'price' => MoneyCast::class,
];
}Casts усувають необхідність ручного перетворення типів у контролерах і view. Користувацькі класи Cast дозволяють моделювати складні типи значень з повною інкапсуляцією.
Готовий до співбесід з Laravel?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Middleware та маршрутизація
8. Як працює middleware в Laravel і як створювати власні?
Middleware — це фільтри, що обробляють HTTP-запит до або після потрапляння до контролера. Laravel розрізняє глобальні middleware (виконуються при кожному запиті), middleware груп маршрутів та middleware, призначені конкретним маршрутам. Порядок виконання є важливим — middleware можуть модифікувати запит, перервати його або змінити відповідь.
class EnsureUserIsSubscribed
{
public function handle(Request $request, Closure $next): Response
{
if (!$request->user()?->isSubscribed()) {
if ($request->expectsJson()) {
return response()->json([
'message' => 'Subscription required.'
], 403);
}
return redirect()->route('subscribe');
}
return $next($request);
}
}
// After middleware - runs after the response
class AddSecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('Strict-Transport-Security', 'max-age=31536000');
return $response;
}
}return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
// Global middleware
$middleware->append(AddSecurityHeaders::class);
// Named middleware for routes
$middleware->alias([
'subscribed' => EnsureUserIsSubscribed::class,
]);
// Middleware groups
$middleware->appendToGroup('api', [
ThrottleRequests::class.':api',
]);
})
->create();
// Usage in routes
Route::middleware('subscribed')->group(function () {
Route::get('/premium', [PremiumController::class, 'index']);
});З Laravel 11 конфігурація middleware перенесена з класу Kernel до файлу bootstrap/app.php, що спрощує структуру додатку.
9. Як працюють групи маршрутів та RESTful ресурси в Laravel?
Групи маршрутів дозволяють спільно використовувати атрибути (middleware, префікси, простори імен) між кількома маршрутами. Resource controllers автоматично генерують CRUD-маршрути відповідно до конвенцій REST.
// Route grouping with shared attributes
Route::prefix('admin')
->middleware(['auth', 'admin'])
->name('admin.')
->group(function () {
Route::get('/dashboard', [AdminController::class, 'dashboard'])
->name('dashboard');
// Resource controller: 7 RESTful routes
Route::resource('articles', ArticleController::class);
// API resource: excludes create/edit (no forms)
Route::apiResource('categories', CategoryController::class);
});
// Nested resources
Route::resource('posts.comments', CommentController::class)
->scoped() // Auto scope bindings
->shallow(); // Shallow nesting for simplicity
// Route model binding with custom resolution
Route::get('/users/{user:username}', [UserController::class, 'show']);class ArticleController extends Controller
{
// GET /admin/articles
public function index(): View { /* list */ }
// GET /admin/articles/create
public function create(): View { /* form */ }
// POST /admin/articles
public function store(StoreArticleRequest $request): RedirectResponse
{
$article = Article::create($request->validated());
return redirect()->route('admin.articles.show', $article);
}
// GET /admin/articles/{article}
public function show(Article $article): View { /* display */ }
// GET /admin/articles/{article}/edit
public function edit(Article $article): View { /* form */ }
// PUT /admin/articles/{article}
public function update(UpdateArticleRequest $request, Article $article): RedirectResponse
{
$article->update($request->validated());
return redirect()->route('admin.articles.show', $article);
}
// DELETE /admin/articles/{article}
public function destroy(Article $article): RedirectResponse
{
$article->delete();
return redirect()->route('admin.articles.index');
}
}Route model binding автоматично розв'язує моделі на основі параметрів URL. Параметр {user:username} шукає користувача за колонкою username замість id.
Черги та завдання (Queues & Jobs)
10. Як працюють черги в Laravel і коли варто їх використовувати?
Черги дозволяють відкласти часомісткі операції — надсилання електронних листів, обробку файлів, синхронізацію із зовнішніми API — щоб не блокувати HTTP-відповідь. Laravel підтримує кілька драйверів черг: Redis, Amazon SQS, базу даних.
class ProcessPodcast implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public int $backoff = 60; // seconds between retries
public int $timeout = 120;
public function __construct(
private readonly Podcast $podcast
) {}
public function handle(AudioProcessor $processor): void
{
$processed = $processor->process(
$this->podcast->audio_path
);
$this->podcast->update([
'processed_path' => $processed->path,
'duration' => $processed->duration,
'status' => 'ready',
]);
}
public function failed(\Throwable $exception): void
{
// Notify admin of failure
$this->podcast->update(['status' => 'failed']);
Log::error('Podcast processing failed', [
'podcast_id' => $this->podcast->id,
'error' => $exception->getMessage(),
]);
}
}// Dispatching jobs
// Basic dispatch
ProcessPodcast::dispatch($podcast);
// Delayed dispatch
ProcessPodcast::dispatch($podcast)
->delay(now()->addMinutes(10));
// Specific queue
ProcessPodcast::dispatch($podcast)
->onQueue('audio-processing');
// Job chaining - sequential execution
Bus::chain([
new ProcessPodcast($podcast),
new OptimizeAudio($podcast),
new PublishPodcast($podcast),
])->dispatch();
// Job batching - parallel with completion tracking
Bus::batch([
new ProcessChapter($podcast, 1),
new ProcessChapter($podcast, 2),
new ProcessChapter($podcast, 3),
])->then(function (Batch $batch) {
Log::info('All chapters processed!');
})->catch(function (Batch $batch, Throwable $e) {
Log::error('Batch failed: '.$e->getMessage());
})->dispatch();Job chaining гарантує послідовне виконання завдань, тоді як batching дозволяє паралельну обробку з відстеженням завершення та помилок.
11. Що таке middleware черг і як керувати обмеженням швидкості завдань?
Middleware черг дозволяють контролювати спосіб виконання завдань — обмежувати частоту, запобігати дублікатам чи відкладати у разі перевантаження. Вони визначаються в методі middleware() класу завдання.
class CallExternalApi implements ShouldQueue
{
use Queueable;
public function middleware(): array
{
return [
// Max 10 jobs per minute for this API
new ThrottlesExceptions(10, 5),
// Rate limiting by key
(new RateLimited('external-api'))
->dontRelease(), // Don't retry if rate limited
// Prevent overlapping - only one at a time
new WithoutOverlapping($this->userId),
];
}
public function handle(): void
{
// API call logic
}
public function retryUntil(): DateTime
{
return now()->addHours(1);
}
}// Define rate limiter in AppServiceProvider
RateLimiter::for('external-api', function (object $job) {
return Limit::perMinute(30);
});
// Unique jobs - prevent duplicates in queue
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
use Queueable;
// Unique for 1 hour
public int $uniqueFor = 3600;
public function uniqueId(): string
{
return $this->product->id;
}
public function handle(): void
{
// Only one job per product in queue at a time
}
}Middleware WithoutOverlapping запобігає одночасному виконанню завдань з однаковим ключем, усуваючи конфлікти даних. ShouldBeUnique гарантує, що в черзі перебуває максимум одне завдання для певного ідентифікатора.
Безпека та автентифікація
12. Як Laravel захищає від атак CSRF, XSS та SQL Injection?
Laravel забезпечує багаторівневий захист від найпоширеніших веб-атак. Токени CSRF захищають форми, шаблонізатор Blade автоматично екранує вихідні дані (захист від XSS), а Eloquent з Query Builder використовують підготовлені запити (захист від SQL Injection).
// CSRF Protection - automatic in forms
<form method="POST" action="/profile">
@csrf {{-- Generates hidden token field --}}
@method('PUT')
<input type="text" name="name" value="{{ $user->name }}">
<button type="submit">Update</button>
</form>
// XSS Protection - Blade auto-escapes
{{ $userInput }} {{-- Escaped: <script> --}}
{!! $trustedHtml !!} {{-- Raw: use only for trusted content --}}
// SQL Injection Protection
// Safe - parameterized query
$users = User::where('email', $request->email)->get();
$users = DB::select('SELECT * FROM users WHERE email = ?', [$email]);
// DANGEROUS - raw unescaped input
$users = DB::select("SELECT * FROM users WHERE email = '$email'"); // NEVER DO THIS// Mass Assignment Protection
class User extends Model
{
// Only these fields can be mass-assigned
protected $fillable = ['name', 'email', 'avatar'];
// Or protect specific fields
protected $guarded = ['is_admin', 'role'];
}
// Rate limiting for API protection
// bootstrap/app.php
$middleware->appendToGroup('api', [
ThrottleRequests::class.':60,1', // 60 requests per minute
]);Захист mass assignment через $fillable або $guarded запобігає несанкціонованій зміні чутливих полів моделі. Рекомендується використовувати $fillable як підхід типу whitelist.
13. Як працює система автентифікації в Laravel?
Laravel пропонує повноцінну систему автентифікації з підтримкою кількох guard'ів та provider'ів. Guard'и визначають спосіб перевірки користувача в кожному запиті, а provider'и вказують джерело даних користувача.
return [
'defaults' => [
'guard' => 'web',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'sanctum',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
],
];// Authentication in controllers
class AuthController extends Controller
{
public function login(LoginRequest $request): RedirectResponse
{
$credentials = $request->validated();
if (Auth::attempt($credentials, $request->boolean('remember'))) {
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
public function logout(Request $request): RedirectResponse
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
// Multi-guard authentication
Auth::guard('admin')->attempt($credentials);
Auth::guard('api')->user();session()->regenerate() після входу запобігає атакам session fixation. session()->invalidate() при виході повністю знищує сесію.
14. Як працює авторизація за допомогою Gates та Policies в Laravel?
Gates — це замикання (closures), що визначають загальні дозволи, тоді як Policies — це класи, що групують логіку авторизації для конкретної моделі. Gates підходять для простих перевірок, а Policies — для CRUD-операцій.
// AuthServiceProvider or AppServiceProvider boot()
Gate::define('manage-settings', function (User $user): bool {
return $user->is_admin;
});
Gate::define('update-post', function (User $user, Post $post): bool {
return $user->id === $post->user_id;
});
// Using gates
if (Gate::allows('manage-settings')) { /* ... */ }
Gate::authorize('update-post', $post); // Throws 403 if deniedclass PostPolicy
{
// Runs before all other checks
public function before(User $user, string $ability): ?bool
{
if ($user->is_super_admin) {
return true; // Bypass all checks
}
return null; // Fall through to specific method
}
public function view(User $user, Post $post): bool
{
return $post->published || $user->id === $post->user_id;
}
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id
&& $post->comments_count === 0;
}
}
// Using policies in controllers
public function update(Request $request, Post $post): RedirectResponse
{
$this->authorize('update', $post); // Auto-resolves PostPolicy
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
// In Blade templates
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcanМетод before() у Policy дозволяє обійти всі перевірки для адміністраторів. Повернення null замість false дозволяє перейти до стандартної логіки авторизації.
Валідація та форми
15. Як працює валідація в Laravel і як створювати власні правила?
Laravel пропонує розгалужену систему валідації з десятками вбудованих правил. Form Requests ізолюють логіку валідації від контролерів, а користувацькі правила дозволяють визначати власні валідатори.
class StoreArticleRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', Article::class);
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255', 'unique:articles'],
'slug' => ['required', 'string', 'regex:/^[a-z0-9-]+$/'],
'body' => ['required', 'string', 'min:100'],
'category_id' => ['required', 'exists:categories,id'],
'tags' => ['array', 'max:5'],
'tags.*' => ['string', 'max:30'],
'publish_at' => ['nullable', 'date', 'after:now'],
'cover_image' => ['nullable', 'image', 'max:2048', 'dimensions:min_width=800'],
];
}
public function messages(): array
{
return [
'title.unique' => 'An article with this title already exists.',
'body.min' => 'Article content must be at least 100 characters.',
];
}
// Transform data before validation
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->title),
]);
}
}// Custom validation rule
class StrongPassword implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (strlen($value) < 12) {
$fail('The :attribute must be at least 12 characters.');
}
if (!preg_match('/[A-Z]/', $value)) {
$fail('The :attribute must contain an uppercase letter.');
}
if (!preg_match('/[0-9]/', $value)) {
$fail('The :attribute must contain a number.');
}
}
}
// Usage
'password' => ['required', new StrongPassword()],
// Conditional validation
$request->validate([
'role' => 'required|in:user,admin',
'admin_code' => 'required_if:role,admin|string',
'company' => Rule::requiredIf($request->role === 'admin'),
]);Form Requests є кращим підходом у професійних проєктах — вони відокремлюють логіку валідації від контролера та можуть також містити логіку авторизації.
Готовий до співбесід з Laravel?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Тестування
16. Як писати тести в Laravel з використанням PHPUnit та Pest?
Laravel забезпечує відмінну інфраструктуру тестування. Feature tests перевіряють повні HTTP-запити, а unit tests тестують ізольовані класи. Фреймворк пропонує допоміжні методи для тестування запитів, бази даних, черг та багато іншого.
class ArticleTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_create_article(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/articles', [
'title' => 'Test Article',
'body' => str_repeat('Content ', 20),
'category_id' => Category::factory()->create()->id,
]);
$response->assertRedirect();
$this->assertDatabaseHas('articles', [
'title' => 'Test Article',
'user_id' => $user->id,
]);
}
public function test_guest_cannot_create_article(): void
{
$response = $this->post('/articles', [
'title' => 'Test',
]);
$response->assertRedirect('/login');
}
}// Same tests with Pest syntax
uses(RefreshDatabase::class);
it('allows authenticated users to create articles', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/articles', [
'title' => 'Test Article',
'body' => str_repeat('Content ', 20),
'category_id' => Category::factory()->create()->id,
]);
$response->assertRedirect();
expect(Article::where('title', 'Test Article')->exists())->toBeTrue();
});
it('prevents guests from creating articles', function () {
$this->post('/articles', ['title' => 'Test'])
->assertRedirect('/login');
});
// Testing with mocks
it('sends welcome email after registration', function () {
Mail::fake();
$this->post('/register', [
'name' => 'John',
'email' => 'john@example.com',
'password' => 'SecurePass123!',
]);
Mail::assertSent(WelcomeEmail::class, function ($mail) {
return $mail->hasTo('john@example.com');
});
});Трейт RefreshDatabase відкочує транзакції після кожного тесту, забезпечуючи ізоляцію. actingAs() імітує автентифікацію без необхідності реального входу.
17. Як працюють Model Factories та Seeders у тестах?
Factories генерують реалістичні тестові дані за допомогою бібліотеки Faker. Seeders заповнюють базу даних початковими даними — як для тестів, так і для ініціалізації середовищ.
class ArticleFactory extends Factory
{
protected $model = Article::class;
public function definition(): array
{
return [
'user_id' => User::factory(), // Auto-creates related user
'category_id' => Category::factory(),
'title' => fake()->sentence(),
'slug' => fake()->slug(),
'body' => fake()->paragraphs(5, true),
'status' => 'draft',
'published_at' => null,
];
}
// State methods for variations
public function published(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'published',
'published_at' => fake()->dateTimeBetween('-1 year'),
]);
}
public function withComments(int $count = 3): static
{
return $this->has(Comment::factory()->count($count));
}
}// Using factories in tests
// Single model
$article = Article::factory()->create();
// With specific attributes
$article = Article::factory()->published()->create([
'title' => 'Specific Title',
]);
// With relationships
$user = User::factory()
->has(Article::factory()->count(5)->published())
->has(Article::factory()->count(2)) // drafts
->create();
// Complex seeder
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Create admin
User::factory()->create([
'email' => 'admin@example.com',
'is_admin' => true,
]);
// Create users with articles
User::factory(10)
->has(Article::factory()->count(rand(1, 10))->published())
->create();
$this->call([
CategorySeeder::class,
TagSeeder::class,
]);
}
}Станові методи (state methods) у factories дозволяють створювати різні варіанти моделі читабельно та зручно в підтримці. Метод has() автоматично створює пов'язані моделі.
Архітектура та патерни
18. Що таке патерн Repository і коли варто його застосовувати в Laravel?
Патерн Repository створює рівень абстракції між бізнес-логікою та рівнем доступу до даних. Хоча Eloquent сам по собі є реалізацією патерну Active Record, додавання Repository може бути виправданим у великих додатках, що потребують тестованості та можливості заміни джерела даних.
interface ArticleRepositoryInterface
{
public function findById(int $id): ?Article;
public function findPublished(int $perPage = 15): LengthAwarePaginator;
public function create(array $data): Article;
public function update(int $id, array $data): Article;
public function delete(int $id): bool;
}
// Repositories/EloquentArticleRepository.php
class EloquentArticleRepository implements ArticleRepositoryInterface
{
public function __construct(
private readonly Article $model
) {}
public function findById(int $id): ?Article
{
return $this->model
->with(['author', 'category', 'tags'])
->find($id);
}
public function findPublished(int $perPage = 15): LengthAwarePaginator
{
return $this->model
->published()
->with(['author', 'category'])
->latest('published_at')
->paginate($perPage);
}
public function create(array $data): Article
{
return $this->model->create($data);
}
public function update(int $id, array $data): Article
{
$article = $this->model->findOrFail($id);
$article->update($data);
return $article->fresh();
}
public function delete(int $id): bool
{
return $this->model->findOrFail($id)->delete();
}
}// Bind in ServiceProvider
$this->app->bind(
ArticleRepositoryInterface::class,
EloquentArticleRepository::class
);
// Use in controller
class ArticleController extends Controller
{
public function __construct(
private readonly ArticleRepositoryInterface $articles
) {}
public function index(): View
{
return view('articles.index', [
'articles' => $this->articles->findPublished(),
]);
}
}У невеликих проєктах Repository може бути зайвою абстракцією над Eloquent. У великих додатках він полегшує тестування (мокання інтерфейсу) та підтримку чистої архітектури.
19. Як реалізувати патерн Action в Laravel?
Патерн Action ізолює окрему бізнес-операцію у виділеному класі з одним публічним методом. Це робить код більш тестованим, повторно використовуваним та читабельним — кожен клас має одне, чітко визначене завдання.
class CreateArticleAction
{
public function __construct(
private readonly ImageUploader $uploader,
private readonly SlugGenerator $slugs
) {}
public function execute(User $author, array $data): Article
{
$slug = $this->slugs->generate($data['title']);
$coverPath = isset($data['cover_image'])
? $this->uploader->upload($data['cover_image'], 'articles')
: null;
$article = $author->articles()->create([
'title' => $data['title'],
'slug' => $slug,
'body' => $data['body'],
'cover_image' => $coverPath,
'category_id' => $data['category_id'],
'status' => $data['publish'] ? 'published' : 'draft',
'published_at' => $data['publish'] ? now() : null,
]);
if (!empty($data['tags'])) {
$article->tags()->sync($data['tags']);
}
if ($article->status === 'published') {
event(new ArticlePublished($article));
}
return $article;
}
}// Usage in controller
class ArticleController extends Controller
{
public function store(
StoreArticleRequest $request,
CreateArticleAction $action
): RedirectResponse {
$article = $action->execute(
$request->user(),
$request->validated()
);
return redirect()->route('articles.show', $article);
}
}
// Reusable in Artisan commands
class ImportArticleCommand extends Command
{
protected $signature = 'articles:import {file}';
public function handle(CreateArticleAction $action): int
{
$articles = json_decode(
file_get_contents($this->argument('file')),
true
);
foreach ($articles as $data) {
$action->execute($this->getSystemUser(), $data);
}
return Command::SUCCESS;
}
}Actions особливо добре підходять для логіки, що використовується як у контролерах, так і в командах Artisan та завданнях черг. Кожну Action легко протестувати модульно.
20. Як працюють Events та Listeners в Laravel?
Система подій Laravel реалізує патерн Observer. Events представляють факти, що відбулися в додатку, а Listeners реагують на ці події. Це відокремлює дію від її наслідків.
class OrderPlaced
{
use SerializesModels;
public function __construct(
public readonly Order $order
) {}
}
// Listeners/SendOrderConfirmation.php
class SendOrderConfirmation implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
Mail::to($event->order->user)
->send(new OrderConfirmationMail($event->order));
}
}
// Listeners/UpdateInventory.php
class UpdateInventory
{
public function handle(OrderPlaced $event): void
{
foreach ($event->order->items as $item) {
$item->product->decrement('stock', $item->quantity);
}
}
}
// Listeners/NotifyWarehouse.php
class NotifyWarehouse implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
Http::post('https://warehouse.api/orders', [
'order_id' => $event->order->id,
'items' => $event->order->items->toArray(),
]);
}
}// Event registration (EventServiceProvider or auto-discovery)
protected $listen = [
OrderPlaced::class => [
SendOrderConfirmation::class,
UpdateInventory::class,
NotifyWarehouse::class,
],
];
// Dispatching events
event(new OrderPlaced($order));
// Or using the static dispatch method
OrderPlaced::dispatch($order);
// Model observers - events for Eloquent models
class OrderObserver
{
public function created(Order $order): void
{
event(new OrderPlaced($order));
}
public function updated(Order $order): void
{
if ($order->wasChanged('status')) {
event(new OrderStatusChanged($order));
}
}
}Listeners, що реалізують ShouldQueue, автоматично потрапляють до черги, запобігаючи уповільненню HTTP-відповіді. Model Observers централізують обробку подій життєвого циклу моделі.
Продуктивність та оптимізація
21. Як вирішити проблему N+1 запитів у Laravel?
Проблема N+1 виникає, коли додаток виконує один запит для отримання колекції, а потім окремий запит для кожного елемента. Eager loading за допомогою with() розв'язує цю проблему, завантажуючи зв'язки в одному або двох запитах.
// N+1 Problem - 1 query for posts + N queries for authors
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // Each access = 1 query
}
// Result: 1 + N queries (101 queries for 100 posts!)
// Solution: Eager Loading
$posts = Post::with('author')->get(); // 2 queries total
// Nested eager loading
$posts = Post::with([
'author',
'comments.user', // Nested: comments AND their users
'tags',
])->get();
// Conditional eager loading
$posts = Post::with(['comments' => function ($query) {
$query->where('approved', true)
->latest()
->limit(5);
}])->get();// Prevent N+1 in development
// AppServiceProvider boot()
public function boot(): void
{
// Throws exception when lazy loading happens
Model::preventLazyLoading(!app()->isProduction());
// Log instead of throwing
Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
Log::warning("Lazy loading {$relation} on {$model::class}");
});
}
// Lazy eager loading - when you already have the collection
$posts = Post::all();
$posts->load('author', 'tags'); // Eager load after initial query
// withCount for counting without loading
$posts = Post::withCount('comments')
->having('comments_count', '>', 10)
->get();
echo $posts[0]->comments_count; // No extra queryModel::preventLazyLoading() — незамінний інструмент у середовищі розробки. Він виявляє проблему N+1 на етапі розробки, перш ніж вона потрапить на продакшен.
22. Як працює кешування в Laravel і які стратегії оптимізації існують?
Laravel пропонує уніфіковане API для кешування з підтримкою кількох драйверів: Redis, Memcached, файл, база даних. Стратегія кешування охоплює як кеш додатку, так і оптимізації, специфічні для фреймворку.
// Basic caching operations
Cache::put('user:123', $userData, now()->addHours(1));
$user = Cache::get('user:123');
$user = Cache::get('user:123', 'default value');
// Remember pattern - get or compute and store
$articles = Cache::remember('popular-articles', 3600, function () {
return Article::published()
->withCount('views')
->orderByDesc('views_count')
->limit(10)
->get();
});
// Cache tags for grouped invalidation
Cache::tags(['articles', 'homepage'])->put('featured', $articles, 3600);
Cache::tags('articles')->flush(); // Clear all article caches
// Atomic locks for race condition prevention
$lock = Cache::lock('processing-order-'.$orderId, 10);
if ($lock->get()) {
try {
// Process order - guaranteed exclusive access
$this->processOrder($orderId);
} finally {
$lock->release();
}
}// Framework optimization commands
// Cache configuration files
php artisan config:cache
// Cache routes
php artisan route:cache
// Cache views
php artisan view:cache
// Cache events
php artisan event:cache
// Optimize all at once
php artisan optimize
// Database query caching strategy
class ArticleService
{
public function getFeatured(): Collection
{
return Cache::tags('articles')
->remember('articles:featured', 1800, function () {
return Article::published()
->featured()
->with(['author', 'category'])
->latest('published_at')
->limit(6)
->get();
});
}
public function clearCache(): void
{
Cache::tags('articles')->flush();
}
}Команди config:cache та route:cache повинні бути частиною скрипта розгортання. Кеш тегів працює лише з драйверами, що підтримують теги (Redis, Memcached).
Розгортання та продакшен
23. Як виглядає процес розгортання Laravel-додатку на продакшен?
Професійне розгортання Laravel включає оптимізацію автозавантажувача, кешування конфігурації та маршрутів, компіляцію ресурсів та міграцію бази даних. Інструменти на кшталт Laravel Forge, Envoyer або власні CI/CD-пайплайни автоматизують цей процес.
# Production deployment script
#!/bin/bash
set -e
# Pull latest code
git pull origin main
# Install dependencies (no dev)
composer install --no-dev --optimize-autoloader
# Run migrations
php artisan migrate --force
# Clear and rebuild caches
php artisan optimize:clear
php artisan optimize
# Build frontend assets
npm ci
npm run build
# Restart queue workers
php artisan queue:restart
# Reload PHP-FPM (zero downtime)
sudo systemctl reload php8.3-fpm'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'search_path' => 'public',
// Connection pooling
'pool' => [
'min' => 2,
'max' => 10,
],
],
// Logging configuration for production
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'warning', // Only warnings+ in production
'days' => 14,
],
],Прапорець --force у php artisan migrate є обов'язковим у продакшен-середовищі, оскільки Laravel за замовчуванням блокує міграції на продакшені. queue:restart сигналізує воркерам про м'яке завершення після поточного завдання.
24. Як налаштовувати та моніторити черги на продакшені?
Правильне налаштування черг на продакшені потребує менеджера процесів (Supervisor), стратегії обробки помилок та моніторингу. Laravel Horizon надає дашборд та розширену конфігурацію для черг Redis.
# Supervisor configuration: /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/worker.log
stopwaitsecs=3600'environments' => [
'production' => [
'supervisor-default' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
'supervisor-emails' => [
'connection' => 'redis',
'queue' => ['emails'],
'maxProcesses' => 3,
],
'supervisor-heavy' => [
'connection' => 'redis',
'queue' => ['audio-processing', 'video-processing'],
'maxProcesses' => 5,
'timeout' => 600,
],
],
],
// Failed job handling
// config/queue.php
'failed' => [
'driver' => 'database-uuids',
'database' => 'pgsql',
'table' => 'failed_jobs',
],
// Retry failed jobs
// php artisan queue:retry all
// php artisan queue:retry {uuid}
// php artisan queue:flush // Delete all failedSupervisor гарантує автоматичний перезапуск воркерів у разі збою. Laravel Horizon додає автобалансування, метрики та інтерфейс керування чергами Redis.
25. Як керувати змінними середовища та конфігурацією в різних середовищах?
Laravel використовує файли .env для конфігурації, специфічної для середовища. На продакшені критично важливе кешування конфігурації, безпечне зберігання секретів та належне розмежування налаштувань між середовищами.
APP_NAME=MyApp
APP_ENV=production
APP_DEBUG=false
APP_URL=https://myapp.com
DB_CONNECTION=pgsql
DB_HOST=db.internal
DB_DATABASE=myapp_prod
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
MAIL_MAILER=ses
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret// Accessing configuration
$appName = config('app.name');
$dbHost = config('database.connections.pgsql.host');
// With default fallback
$timeout = config('services.api.timeout', 30);
// NEVER use env() outside config files!
// Bad - breaks with config:cache
$key = env('API_KEY');
// Good - always use config()
// config/services.php
return [
'payment' => [
'key' => env('PAYMENT_API_KEY'),
'secret' => env('PAYMENT_API_SECRET'),
'sandbox' => env('PAYMENT_SANDBOX', false),
],
];
// Then use:
$key = config('services.payment.key');
// Environment-specific configuration
if (app()->isProduction()) {
// Production-only logic
}
// Custom environment detection
$environment = match(true) {
app()->environment('production') => 'prod',
app()->environment('staging') => 'staging',
default => 'dev',
};Після виконання config:cache функція env() повертає null поза файлами конфігурації. Тому env() слід викликати виключно в каталозі config/, а в коді додатку — завжди використовувати config().
Кожне з наведених 25 запитань потребує глибшого аналізу з використанням офіційної документації Laravel. Кандидати, які здатні пояснити не лише "як", а передусім "чому" фреймворк приймає певні архітектурні рішення, виділяються під час співбесід.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Теги
Поділитися
Пов'язані статті

Laravel 11: Створення повноцінного застосунку з нуля
Вичерпний посібник зі створення застосунку на Laravel 11 з автентифікацією, REST API, Eloquent ORM та розгортанням у продакшн. Практичний підручник для початківців і розробників середнього рівня.

Питання на співбесіді з Django та Python: Топ 25 у 2026 році
25 найпоширеніших питань на співбесіді з Django та Python. ORM, представлення, middleware, DRF, сигнали та оптимізація з детальними відповідями та прикладами коду.

Zapytannia na spivbesidi z Rust: Povnyi posibnyk 2026
25 naiposhyrenishykh zapytan na spivbesidi z Rust. Vlastnist, zapozychennia, chasy zhyttia, treity, async/await, paralelizm z detalianymy vidpovidyamy ta prykladamy kodu.