Beetje Coderen

Les 4 · Module 1 · Laravel-basis

Request lifecycle, service container en facades

Begrijp wat Laravel doet tussen verzoek en antwoord: de levenscyclus, middleware, de service container met dependency injection, service providers en facades.

⏱ 75 minuten leestijd + oefenen

Illustratie: tandwielen en pijlen tussen een browser en een server, de stappen die een verzoek doorloopt

🎯 Na deze les kun je

  • de stappen van een verzoek door Laravel benoemen
  • een eigen service via de container laten injecteren
  • een binding registreren in een service provider
  • uitleggen wat een facade is en hoe hij zich verhoudt tot injectie

Tot nu toe "werkte het gewoon": je zette Request $request in een methode en Laravel gaf je een request. Om fouten te begrijpen en je code netjes op te delen, moet je weten hoe dat gebeurt. In deze les volg je een verzoek door Laravel en maak je een eigen service voor de btw-berekening van de kassa.

De levenscyclus van een verzoek

public/index.php
  └─ bootstrap/app.php        app maken, providers registreren
      └─ HTTP-kernel           globale middleware (trim strings, sessie, CSRF …)
          └─ router            route zoeken, route-middleware (auth …)
              └─ controller    jouw code, met geïnjecteerde afhankelijkheden
          ◀─ response          terug door de middleware (cookies, headers)

Middleware is code die om elk verzoek heen zit, zoals lagen van een ui. Voorbeelden die standaard aanstaan: TrimStrings (spaties weg uit invoer), ConvertEmptyStringsToNull, StartSession, VerifyCsrfToken.

Een eigen middleware die de duur van elk verzoek logt:

php artisan make:middleware MeetDuur
public function handle(Request $request, Closure $next): Response
{
    $start = microtime(true);
    $response = $next($request);                     // hier draait de rest van de app
    $ms = round((microtime(true) - $start) * 1000);
    $response->headers->set('X-Duur-ms', (string) $ms);
    return $response;
}

Registreren in bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\App\Http\Middleware\MeetDuur::class);
})

De service container

De service container is een fabriek die objecten maakt en hun afhankelijkheden invult. Zet je een klasse als type in een controller-methode of constructor, dan vraagt Laravel de container om een exemplaar.

// app/Services/BtwCalculator.php
namespace App\Services;

class BtwCalculator
{
    public function __construct(private float $tarief = 0.21) {}

    public function btwDeel(float $inclusief): float
    {
        return round($inclusief * $this->tarief / (1 + $this->tarief), 2);
    }
}
class KassaController extends Controller
{
    public function __construct(private BtwCalculator $btw) {}

    public function totaal(int $bedrag)
    {
        return view('kassa.totaal', ['bedrag' => $bedrag, 'btw' => $this->btw->btwDeel($bedrag)]);
    }
}

Je schrijft nergens new BtwCalculator(). De container ziet het type, maakt het object en geeft het door. Dat heet dependency injection: de controller hoeft niet te weten hoe hij een calculator maakt. In een test kun je een andere calculator meegeven.

Voorspel eerst: wat gebeurt er als de constructor __construct(private float $tarief) zonder standaardwaarde heeft?

Uitkomst

BindingResolutionException: Unresolvable dependency resolving [Parameter #0 [ <required> float $tarief ]]. De container kan klassen automatisch maken, maar weet niet welk getal hij voor $tarief moet nemen. Dat vertel je hem in een service provider.

Bindings in een service provider

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(BtwCalculator::class, fn () => new BtwCalculator(config('kassa.btw')));
}

Met config/kassa.php:

return ['btw' => (float) env('KASSA_BTW', 0.21)];
  • bind: elke keer een nieuw object. singleton: één object per verzoek/proces.
  • register() is voor bindings, boot() voor dingen die andere services nodig hebben (view composers, macro's).
  • Gebruik env() alleen in configbestanden. Na php artisan config:cache geeft env() elders null.

Interfaces binden

Het echte voordeel: je kunt een interface binden aan een implementatie.

interface Betaalprovider { public function start(int $centen): string; }

$this->app->bind(Betaalprovider::class, MollieBetaalprovider::class);

Een controller vraagt om Betaalprovider; in tests bind je een NepBetaalprovider. De controller verandert niet.

Facades

Je zag al Route::get(...). Route is een facade: een statische snelweg naar een object in de container.

use Illuminate\Support\Facades\Cache;
Cache::put('omzet', 312.40, 60);        // is hetzelfde als app('cache')->put(...)

Facades zijn kort en goed testbaar (Cache::shouldReceive(...)). Nadeel: je ziet niet aan de constructor welke afhankelijkheden een klasse heeft. Vuistregel: facades in routes, views en kleine code; injectie in services met logica.

Typische fouten

  • BindingResolutionException: de container kan een parameter niet invullen (scalar zonder standaard, interface zonder binding).
  • config('kassa.btw') geeft null: het bestand heet anders, of de config is gecachet (php artisan config:clear).
  • Middleware draait niet: niet geregistreerd in bootstrap/app.php, of alleen op een route-groep gezet.

Oefenen

  1. Maak de middleware MeetDuur en controleer de header met curl -I.
  2. Maak BtwCalculator en laat KassaController hem via de constructor krijgen.
  3. Lees het tarief uit config/kassa.php en .env en bind de calculator als singleton.
  4. Bewijs met php artisan tinker dat app(BtwCalculator::class) === app(BtwCalculator::class) bij singleton true is en bij bind false.
Modelantwoord oefening 1
curl -sI http://127.0.0.1:8000/ | grep -i x-duur
X-Duur-ms: 14
Modelantwoord oefening 2

Zie het voorbeeld hierboven. /totaal/121 geeft nu btw: 21,00: het btw-deel van een bedrag inclusief btw.

Modelantwoord oefening 3
# .env
KASSA_BTW=0.09

Na php artisan config:clear toont /totaal/109 een btw van 9,00.

Modelantwoord oefening 4
> app(App\Services\BtwCalculator::class) === app(App\Services\BtwCalculator::class)
= true

Met bind is het false: twee verschillende objecten. Kies singleton voor objecten zonder staat per gebruiker.

Lokaal oefenen

Geen live lab. php artisan tinker is je speeltuin: probeer app(), config() en resolve() uit.

Samenvatting

  • Een verzoek gaat door middleware naar de router en de controller, en terug.
  • De container maakt objecten en injecteert afhankelijkheden; providers vertellen hem wat hij niet kan raden.
  • Facades zijn een statische toegang tot containerobjecten.
  • Volgende les: formulieren en validatie.

📝 Quiz: Request lifecycle, service container en facades

5 vragen · 70% nodig om te slagen

1. Wat is middleware?
2. Waarom kan de container BtwCalculator(float $tarief) zonder standaard niet maken?
3. Verschil tussen bind en singleton?
4. Waarom gebruik je env() alleen in configbestanden?
5. Wat is Cache::put(...)?