25 pytań rekrutacyjnych z Laravel i PHP w 2026 roku
25 najczęściej zadawanych pytań rekrutacyjnych z Laravel: Service Container, Eloquent ORM, middleware, kolejki, bezpieczeństwo, testowanie i wzorce architektoniczne z przykładami kodu.

Rozmowy kwalifikacyjne dotyczące Laravel weryfikują znajomość frameworka, ekosystemu PHP oraz zdolność do projektowania skalowalnych aplikacji webowych. Poniższy przewodnik obejmuje 25 pytań najczęściej pojawiających się podczas rekrutacji — od podstaw frameworka po optymalizację wydajności i wdrażanie na produkcję.
Podstawy Laravel
1. Czym jest Service Container i jaką rolę pełni w Laravel?
Service Container to serce frameworka Laravel — potężny mechanizm zarządzania zależnościami klas i wykonywania dependency injection. Każda aplikacja Laravel posiada jeden kontener, który rejestruje i rozwiązuje powiązania między interfejsami a ich konkretnymi implementacjami. Dzięki temu kod pozostaje luźno powiązany i łatwy do testowania.
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);
}
}Kontener automatycznie rozwiązuje zależności na podstawie type-hintów. Jeśli klasa zależy od interfejsu zarejestrowanego w kontenerze, Laravel wstrzykuje odpowiednią implementację bez żadnej dodatkowej konfiguracji.
2. Jaka jest różnica między Service Provider a Service Container?
Service Container to mechanizm przechowujący i rozwiązujący zależności. Service Provider natomiast to klasa odpowiedzialna za rejestrowanie powiązań w kontenerze i konfigurację usług aplikacji. Każdy provider posiada dwie metody: register() do wiązania usług oraz boot() do działań wymagających już zarejestrowanych usług.
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'),
]);
}
}Provider jest wskazany w tablicy providers w config/app.php. Laravel ładuje je sekwencyjnie — najpierw wywołuje register() u wszystkich providerów, a następnie boot(), co gwarantuje dostępność wszystkich usług podczas konfiguracji.
3. Jak działa cykl życia żądania HTTP w Laravel?
Każde żądanie HTTP w Laravel przechodzi przez ściśle określoną sekwencję etapów. Plik public/index.php ładuje autoloader Composera i tworzy instancję aplikacji. Następnie żądanie przechodzi przez kernel HTTP, który wykonuje globalne middleware, a router dopasowuje żądanie do odpowiedniej trasy. Kontroler przetwarza logikę i zwraca odpowiedź, która ponownie przechodzi przez middleware w odwrotnej kolejności.
// 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);Metoda terminate() na końcu cyklu umożliwia wykonanie operacji po wysłaniu odpowiedzi — np. zapisanie logów czy zamknięcie połączeń.
4. Czym są Facade w Laravel i jak działają pod spodem?
Facade zapewniają statyczną składnię dostępu do usług zarejestrowanych w Service Container. Nie są to jednak prawdziwe klasy statyczne — pod spodem każda fasada deleguje wywołanie do odpowiedniego obiektu rozwiązanego z kontenera. Metoda getFacadeAccessor() zwraca klucz wiązania w kontenerze.
// 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 ułatwiają testowanie — Laravel umożliwia ich mockowanie za pomocą Cache::shouldReceive(), co sprawia, że testy jednostkowe pozostają proste i czytelne.
Eloquent ORM
5. Jakie są różnice między relacjami hasOne, hasMany, belongsTo i belongsToMany?
Eloquent definiuje relacje jako metody w modelach. hasOne oznacza relację jeden-do-jednego, gdzie klucz obcy znajduje się w tabeli powiązanej. hasMany to relacja jeden-do-wielu. belongsTo jest odwrotnością — model posiada klucz obcy. belongsToMany obsługuje relacje wiele-do-wielu z tabelą pośrednią (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();Dobór odpowiedniej relacji wpływa na wydajność zapytań i czytelność kodu. Eager loading za pomocą with() eliminuje problem N+1.
6. Jak działają Eloquent Scopes i kiedy warto ich używać?
Scopes pozwalają hermetyzować powtarzające się warunki zapytań w metodach modelu. Wyróżnia się dwa rodzaje: Local Scopes wywoływane jawnie w łańcuchu zapytań oraz Global Scopes stosowane automatycznie do każdego zapytania danego modelu.
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 poprawiają czytelność kodu i zapobiegają duplikacji logiki filtrowania. Global Scopes należy stosować ostrożnie — automatyczne filtrowanie może prowadzić do trudnych do wychwycenia błędów.
7. Czym jest Eloquent Mutators i Casts i jak wpływają na atrybuty modelu?
Mutators i Casts kontrolują sposób zapisu i odczytu atrybutów modelu. Casts definiują automatyczną konwersję typów, a Mutators (accessors i mutators) pozwalają na dowolną transformację wartości za pomocą metod 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 eliminują konieczność ręcznej konwersji typów w kontrolerach i widokach. Niestandardowe klasy Cast pozwalają modelować złożone typy wartości z pełną enkapsulacją.
Gotowy na rozmowy o Laravel?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Middleware i Routing
8. Jak działa middleware w Laravel i jak tworzyć własne?
Middleware to filtry przetwarzające żądanie HTTP przed lub po dotarciu do kontrolera. Laravel wyróżnia middleware globalne (wykonywane przy każdym żądaniu), middleware grupy tras oraz middleware przypisane do konkretnych tras. Kolejność wykonywania jest istotna — middleware mogą modyfikować żądanie, przerwać je lub zmodyfikować odpowiedź.
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']);
});Od Laravel 11 konfiguracja middleware przeniesiona została z klasy Kernel do pliku bootstrap/app.php, co upraszcza strukturę aplikacji.
9. Jak działają grupy tras i zasoby RESTful w Laravel?
Grupy tras pozwalają współdzielić atrybuty (middleware, prefiksy, przestrzenie nazw) między wieloma trasami. Resource controllers automatycznie generują trasy CRUD zgodne z konwencją 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 automatycznie rozwiązuje modele na podstawie parametrów URL. Parametr {user:username} wyszukuje użytkownika po kolumnie username zamiast id.
Kolejki i zadania (Queues & Jobs)
10. Jak działają kolejki w Laravel i kiedy warto ich używać?
Kolejki umożliwiają odroczenie czasochłonnych operacji — wysyłania e-maili, przetwarzania plików czy synchronizacji z zewnętrznymi API — aby nie blokować odpowiedzi HTTP. Laravel obsługuje wiele sterowników kolejek: Redis, Amazon SQS, bazę danych.
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 gwarantuje sekwencyjne wykonanie zadań, natomiast batching umożliwia równoległe przetwarzanie z obsługą zdarzeń zakończenia i błędu.
11. Czym są middleware kolejek i jak obsługiwać limity prędkości zadań?
Middleware kolejek pozwalają kontrolować sposób wykonywania zadań — ograniczać częstotliwość, zapobiegać duplikatom czy odraczać w przypadku przeciążenia. Są zdefiniowane w metodzie middleware() klasy zadania.
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 zapobiega równoczesnemu wykonywaniu zadań z tym samym kluczem, co eliminuje konflikty danych. ShouldBeUnique zapewnia, że w kolejce znajduje się maksymalnie jedno zadanie dla danego identyfikatora.
Bezpieczeństwo i uwierzytelnianie
12. Jak Laravel chroni przed atakami CSRF, XSS i SQL Injection?
Laravel zapewnia wielowarstwową ochronę przed najczęstszymi atakami webowymi. Tokeny CSRF zabezpieczają formularze, silnik Blade automatycznie eskejpuje dane wyjściowe (ochrona XSS), a Eloquent z Query Builderem stosują przygotowane zapytania (ochrona 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
]);Ochrona mass assignment za pomocą $fillable lub $guarded zapobiega nieautoryzowanej modyfikacji wrażliwych pól modelu. Zaleca się stosowanie $fillable jako podejścia typu whitelist.
13. Jak działa system uwierzytelniania w Laravel?
Laravel oferuje kompletny system uwierzytelniania z obsługą wielu strażników (guards) i dostawców (providers). Guards definiują sposób weryfikacji użytkownika w każdym żądaniu, a providers określają źródło danych użytkownika.
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() po logowaniu zapobiega atakom session fixation. session()->invalidate() przy wylogowywaniu kompletnie niszczy sesję.
14. Jak działa autoryzacja za pomocą Gates i Policies w Laravel?
Gates to zamknięcia (closures) definiujące uprawnienia ogólne, natomiast Policies to klasy grupujące logikę autoryzacji dla konkretnego modelu. Gates sprawdzają się przy prostych sprawdzeniach, a Policies przy operacjach 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>
@endcanMetoda before() w Policy umożliwia obejście wszystkich sprawdzeń dla administratorów. Zwrócenie null zamiast false pozwala przejść do standardowej logiki autoryzacji.
Walidacja i formularze
15. Jak działa walidacja w Laravel i jak tworzyć własne reguły?
Laravel oferuje rozbudowany system walidacji z dziesiątkami wbudowanych reguł. Form Requests izolują logikę walidacji od kontrolerów, a niestandardowe reguły pozwalają definiować własne walidatory.
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 są preferowanym podejściem w profesjonalnych projektach — oddzielają logikę walidacji od kontrolera i mogą zawierać również autoryzację.
Gotowy na rozmowy o Laravel?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Testowanie
16. Jak pisać testy w Laravel z użyciem PHPUnit i Pest?
Laravel zapewnia doskonałą infrastrukturę testową. Feature tests weryfikują pełne żądania HTTP, a unit tests testują izolowane klasy. Framework oferuje metody pomocnicze do testowania żądań, bazy danych, kolejek i wielu innych.
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');
});
});Trait RefreshDatabase wycofuje transakcje po każdym teście, co zapewnia izolację. actingAs() symuluje uwierzytelnienie bez potrzeby logowania.
17. Jak działają Model Factories i Seeders w testach?
Factories generują realistyczne dane testowe za pomocą biblioteki Faker. Seeders wypełniają bazę danych danymi początkowymi — zarówno do testów, jak i do inicjalizacji środowisk.
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,
]);
}
}Metody stanowe (state methods) w factories umożliwiają tworzenie różnych wariantów modelu w sposób czytelny i łatwy w utrzymaniu. Metoda has() automatycznie tworzy powiązane modele.
Architektura i wzorce
18. Czym jest wzorzec Repository i kiedy warto go stosować w Laravel?
Wzorzec Repository wprowadza warstwę abstrakcji między logiką biznesową a warstwą dostępu do danych. Choć Eloquent sam w sobie jest implementacją wzorca Active Record, dodanie Repository może być uzasadnione w dużych aplikacjach wymagających testowalności i możliwości wymiany źródła danych.
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(),
]);
}
}W mniejszych projektach Repository może stanowić zbędną abstrakcję nad Eloquent. W dużych aplikacjach ułatwia testowanie (mockowanie interfejsu) i utrzymanie czystej architektury.
19. Jak implementować wzorzec Action w Laravel?
Wzorzec Action izoluje pojedynczą operację biznesową w dedykowanej klasie z jedną publiczną metodą. Czyni to kod bardziej testowalnym, wielokrotnego użytku i czytelnym — każda klasa ma jedno, jasno określone zadanie.
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 nadają się szczególnie dobrze do logiki wykorzystywanej zarówno w kontrolerach, komendach Artisan, jak i zadaniach kolejkowych. Każda Action jest łatwa do przetestowania jednostkowo.
20. Jak działają Events i Listeners w Laravel?
System zdarzeń Laravel implementuje wzorzec Observer. Zdarzenia reprezentują fakty zaistniałe w aplikacji, a listenery reagują na te zdarzenia. Oddziela to akcję od jej konsekwencji.
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));
}
}
}Listenery implementujące ShouldQueue są automatycznie umieszczane w kolejce, co zapobiega spowolnieniu odpowiedzi HTTP. Model Observers centralizują obsługę zdarzeń cyklu życia modelu.
Wydajność i optymalizacja
21. Jak rozwiązać problem N+1 queries w Laravel?
Problem N+1 występuje, gdy aplikacja wykonuje jedno zapytanie po kolekcję, a następnie osobne zapytanie dla każdego elementu. Eager loading za pomocą with() rozwiązuje ten problem, ładując relacje w jednym lub dwóch zapytaniach.
// 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() to niezastąpione narzędzie w środowisku deweloperskim — wykrywa problem N+1 na etapie programowania, zanim trafi na produkcję.
22. Jak działa cachowanie w Laravel i jakie są strategie optymalizacji?
Laravel oferuje zunifikowany API do cachowania z obsługą wielu sterowników: Redis, Memcached, plik, baza danych. Strategia cachowania obejmuje zarówno cache aplikacyjny, jak i optymalizacje specyficzne dla frameworka.
// 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();
}
}Komendy config:cache i route:cache powinny być częścią skryptu wdrożeniowego. Cache tagów działa wyłącznie ze sterownikami obsługującymi tagi (Redis, Memcached).
Wdrażanie i produkcja
23. Jak wygląda proces wdrażania aplikacji Laravel na produkcję?
Profesjonalne wdrożenie Laravel obejmuje optymalizację autoloadera, cachowanie konfiguracji i tras, kompilację assetów oraz migrację bazy danych. Narzędzia takie jak Laravel Forge, Envoyer czy własne pipeline'y CI/CD automatyzują ten proces.
# 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,
],
],Flaga --force w php artisan migrate jest wymagana w środowisku produkcyjnym, ponieważ Laravel domyślnie blokuje migracje na produkcji. queue:restart sygnalizuje workerom łagodne zakończenie po bieżącym zadaniu.
24. Jak konfigurować i monitorować kolejki na produkcji?
Prawidłowa konfiguracja kolejek na produkcji wymaga narzędzia zarządzającego procesami (Supervisor), strategii obsługi błędów oraz monitoringu. Laravel Horizon zapewnia dashboard i zaawansowaną konfigurację dla kolejek 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 gwarantuje automatyczny restart workerów w przypadku awarii. Laravel Horizon dodaje auto-balancing, metryki i interfejs do zarządzania kolejkami Redis.
25. Jak zarządzać zmiennymi środowiskowymi i konfiguracją w różnych środowiskach?
Laravel wykorzystuje pliki .env do konfiguracji specyficznej dla środowiska. Na produkcji kluczowe jest cachowanie konfiguracji, bezpieczne przechowywanie sekretów i odpowiednia separacja ustawień między środowiskami.
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',
};Po uruchomieniu config:cache funkcja env() zwraca null poza plikami konfiguracyjnymi. Dlatego env() powinno być wywoływane wyłącznie w katalogach config/, a w kodzie aplikacji — zawsze config().
Każde z powyższych 25 pytań wymaga głębszej analizy z wykorzystaniem oficjalnej dokumentacji Laravel. Kandydaci, którzy potrafią wyjaśnić nie tylko "jak" ale przede wszystkim "dlaczego" framework podejmuje określone decyzje architektoniczne, wyróżniają się podczas rozmów kwalifikacyjnych.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

Laravel 11: Budowa kompletnej aplikacji od podstaw
Kompleksowy przewodnik po budowie aplikacji w Laravel 11 z uwierzytelnianiem, REST API, Eloquent ORM i wdrożeniem produkcyjnym. Praktyczny poradnik dla początkujących i średnio zaawansowanych programistów.

Pytania rekrutacyjne Django i Python: Top 25 w 2026
25 najczesciej zadawanych pytan rekrutacyjnych z Django i Pythona. ORM, widoki, middleware, DRF, sygnaly i optymalizacja z szczegolowymi odpowiedziami i przykladami kodu.

Pytania rekrutacyjne Rust: Kompletny przewodnik 2026
25 najczesciej zadawanych pytan rekrutacyjnych z Rust. Wlasnosc, pozyczanie, czasy zycia, cechy, async/await, wspolbieznosc z odpowiedziami i przykladami kodu.