# 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. - Published: 2026-01-30 - Updated: 2026-04-12 - Author: SharpSkill - Tags: laravel, php, interview, eloquent, technical interview - Reading time: 22 min --- 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ę. > **Wskazówka rekrutacyjna** > > Rekruterzy cenią kandydatów, którzy potrafią wyjaśnić architektoniczne decyzje Laravel. Zrozumienie, dlaczego framework stosuje określone konwencje (Convention over Configuration, Service Container), robi realną różnicę podczas rozmowy kwalifikacyjnej. ## 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. ```php // AppServiceProvider.php 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') ); }); } ``` ```php // OrderController.php 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. ```php // AnalyticsServiceProvider.php 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. ```php // 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. ```php // 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 container ``` Facade 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). ```php // User.php 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); } } ``` ```php // 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. ```php // Post.php - Local 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); ``` ```php // 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`. ```php // User.php - Attribute Casting and Mutators 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)), ); } } ``` ```php // 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ą. ## 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ź. ```php // EnsureUserIsSubscribed.php 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; } } ``` ```php // bootstrap/app.php (Laravel 11+) 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. ```php // routes/web.php // 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']); ``` ```php // ArticleController.php - Resource Controller 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. ```php // ProcessPodcastJob.php 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(), ]); } } ``` ```php // 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. ```php // RateLimitedJob.php 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); } } ``` ```php // 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). ```php // CSRF Protection - automatic in forms
@csrf {{-- Generates hidden token field --}} @method('PUT')
// 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 ``` ```php // 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. ```php // config/auth.php 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, ], ], ]; ``` ```php // 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. ```php // 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 denied ``` ```php // PostPolicy.php class 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) Edit @endcan ``` Metoda `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. ```php // StoreArticleRequest.php - Form Request 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), ]); } } ``` ```php // 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ę. ## 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. ```php // Feature test - tests/Feature/ArticleTest.php 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'); } } ``` ```php // 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. ```php // database/factories/ArticleFactory.php 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)); } } ``` ```php // 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. ```php // Contracts/ArticleRepositoryInterface.php 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(); } } ``` ```php // 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. ```php // Actions/CreateArticleAction.php 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; } } ``` ```php // 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. ```php // Events/OrderPlaced.php 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(), ]); } } ``` ```php // 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. ```php // 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(); ``` ```php // 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 query ``` `Model::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. ```php // 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(); } } ``` ```php // 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. ```bash # 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 ``` ```php // config/database.php - Production database configuration '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. ```bash # 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 ``` ```php // config/horizon.php - Laravel Horizon configuration '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 failed ``` Supervisor 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. ```php // .env - Environment-specific values 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 ``` ```php // 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. > **Dalsze kroki** > > Każde pytanie zasługuje na głębszą eksplorację z oficjalną dokumentacją Laravel. Rekruterzy cenią kandydatów, którzy znają subtelności frameworka i potrafią uzasadnić swoje wybory techniczne. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/laravel/laravel-php-interview-questions