Laravel MVC en Database
Je zal je wellicht nog herinneren dat we in het vak Software Ontwerp in Java er op gehamerd werd dat we een goede architecture willen gebruiken voor onze applicaties om ze meer leesbaar, onderhoudbaar, schaalbaar, … te maken. In Java gebruikten we het Model-View-Controller (MVC) principe waarbij de logica van onze applicatie gedefinieerd stond in Modellen, wat de gebruiker te zien krijgt gedefinieerd stond in de Views en de interactie van de gebruiker met de modellen nooit rechtstreeks gebeurde maar altijd via een Controller die dan ook voor een update van de views ging zorgen. We hebben geluk want deze architectuur zit nu ook al sterk verweven in het Laravel framework. De Views (.blade.php
) hebben we hierboven al besproken en nu gaan we verder met de Controller (PHP class
). Hierna zullen we het over de Modellen (PHP class
) hebben aangezien we het tegelijk over database integratie moeten hebben, aangezien Model creation en database migration sterk verweven zijn in Laravel.
Controllers
In webapplicaties gemaakt met PHP gaat de interactie met de gebruiker voornamelijk via HTTP-requests verlopen. De logica van wat er juist bij welke request moet gebeuren gaan we nu organiseren in aparte Controller klassen. Een controller is meestal gekoppeld aan een Model, maar je kan echter ook een standalone controller maken in Laravel met het volgende commando: php artisan make:controller TestController
Er wordt een TestController.php
-klasse aangemaakt in de app/Http/controllers
-directory, met al de correcte boilerplate code. Hieronder zie je een voorbeeld van de implementatie van deze TestController die een GET-request behandelt waar enkel een naam
-parameter is meegegeven:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TestController extends Controller
{
public function handleNameRequest(Request $request)
{
$name = $request->get("name");
dump($request);
return view('hi', ['name_example' => $name]);
}
}
Om deze functionaliteit te gebruiken in onze web.php
, waar we nu dus geen logica gaan schrijven maar enkel de requests gaan forwarden naar de juiste controller functie, moeten we de controller klasse importeren en gebruiken op de volgende manier:
<?php
use Illuminate\Support\Facades\Route;
use Illuminate\Http\Request;
use App\Http\Controllers\TestController;
Route::get('/process-form-controller', [TestController::class, 'handleNameRequest']);
// ZONDER CONTROLLER
Route::get('/process-form', function (Request $request) {
$name = $request->get("name");
dump($request);
return view('hi', ['name_example' => $name]);
});
Database
Zorg ervoor dat je MySQL database van XAMPP aanstaat
We hebben bij het aanmaken van ons Laravel project al aangeduid dat we een database willen connecteren. Er wordt dan ook specifiek voor dat Laravel project een database gecreëerd in je database onder laravel -> project_naam
. Indien je gewenst kan je ook eerst zelf deze database aanmaken. We gaan zo dadelijk ook nog zien hoe je eventueel een andere database kan koppelen.
Alle info om een correcte verbinding te maken wordt opgeslagen in de .env
-file. Krijg je een error, dan moet je dus kijken of alle instellingen voor jouw database hier correct ingevuld zijn. De waarden die hier staan worden gebruikt in de config/database.php
file. Hieronder vind je een voorbeeld:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=database_naam
DB_USERNAME=root
DB_PASSWORD=
Door de DB_DATABASE=
variabele dus een andere waarde te geven kan je connecteren met een andere database.
Om je connectie met de database te testen kan je php artisan migrate:status
gebruiken. Dit geeft een error als er een probleem is.
Heb je een wijziging aangebracht aan de instellingen van je database in de .env file of heb je migration tables gewijzigd, verwijderd of toegevoegd? Gebruik dan php artisan migrate
om die wijzigingen te synchroniseren met de database.
Models
Nu gaan we een model aanmaken in Laravel. Aangezien de link tussen modellen en de database zo groot is gaan we bijna altijd ook onmiddellijk een migration table mee laten genereren met de -m
flag. (Migration tables zijn files waarin we uitleggen hoe de link tussen het model en de database tabel moet gelegd worden. Hierover meer hieronder):
php artisan make:model <modelName> -m
Er wordt een model klasse aangemaakt in app/Models
en een bijhorende migration file in database/migrations
.
We geven hieronder een voorbeeld voor een Student
model dat we al verder uitgewerkt hebben met de juiste datamembers:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Student extends Model
{
// Define the primary key (studnr instead of default 'id')
protected $primaryKey = 'studnr';
// Fields that can be mass-assigned
protected $fillable = [
'voornaam',
'naam',
'goedBezig',
];
// Cast 'goedBezig' to boolean
protected $casts = [
'goedBezig' => 'boolean',
];
}
Merk op dat we in het model al rekening houden met hoe de modellen opgeslagen worden in de database:
- We geven de attributen mee maar specificeren hun rol binnen de tabel ook zo wordt de
studnr
de$primaryKey
. - Andere attributen/kolommen geven we op onder
$fillable
- Databases zoals MySQL slaan booleans op als integers (0/1) of true/false (afhankelijk van de driver). Door
$casts = ['goedBezig' => 'boolean'];
te gebruiken verzekeren we dat de waar geconverteerd wordt naar een PHP boolean bij het ophalen uit de database en de waarde als een integer wordt opgeslagen in de database.
Merk op dat je hier geen info vind over bijvoorbeeld het type van naam
en voornaam
. Dit is allemaal gespecificeerd binnenin de migration table
Migration tables
De migration table die bij voorgaande voorbeeld hoort hebben we aangevuld om te passen bij het Student
model en ziet er als volgt uit:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('students', function (Blueprint $table) {
// Custom primary key (studnr) as auto-incrementing integer
$table->integer('studnr')->autoIncrement()->primary();
// voornaam (string, NOT NULL)
$table->string('voornaam')->nullable(false);
// naam (string, nullable)
$table->string('naam')->nullable();
// goedBezig (boolean with default value)
$table->boolean('goedBezig')->default(false);
// Timestamps (created_at and updated_at)
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('students');
}
};
BELANGRIJIK voer php artisan migrate
uit om de wijzigingen te synchroniseren met de database!
Met het commando: php artisan migrate:rollback
kan je de down
functies van je laatst gemigreerde migration tables oproepen die in de meeste gevallen een drop table if exists
gaan doen. Wil je van een specifieke migration table de rollback oproepen dan moet je ook het path naar de file meegeven bv. php artisan migrate:rollback --path=database/migrations/2025_03_26_111505_create_students_table.php
Je kan nu studenten createn, readen, updaten en deleten (C-R-U-D) met onderstaande PHP code:
// Create student
$student = Student::create([
'voornaam' => 'John',
'naam' => 'Doe',
'goedBezig' => true,
]);
// CREATE is a special function which is analogous to:
$student = new Student([
'voornaam' => 'John',
'achternaam' => 'Doe',
'goedBezig' => true,
]);
$student->save();
// Read: Query students
$students = Student::where('goedBezig', true)->get();
// Update student
//$data (bv. ['voornaam' => 'Jan', 'goedBezig' => false])
// Zoek de student
$student = Student::find($studnr);
// Update de velden
$student->update($data);
// Delete student
// Zoek de student
$student = Student::find($studnr);
// Verwijderen
$student->delete();
Nog veel meer info over migration tables vind je in de documentatie!
Wil je niet dat je Primary key ook autoincrement dan moet je autoIncrement()
verwijderen uit je migration table en public $incrementing = false;
toevoegen aan je model.
Api routes: Controller
Wanneer we bijvoorbeeld achtergrond logica willen laten draaien zonder noodzakelijk de view aan te passen worden dit meestal api-endpoints genoemd (Application Programming Interface) Die endpoints die we hiervoor aanspreken steken we dan meestal in een api.web
file in de routes
folder. Je kan de endpoints dan bereiken via /api/endpoint
. In het algemeen:
web.php
voor web forms, Blade views en stateful interactions.api.php
voor stateless API endpoints.
Om de api.php file te laten genereren en alle instellingen correct te zetten moet je php artisan install:api
gebruiken.
Bovendien kan je met php artisan route:list
bekijken welke endpoints allemaal beschikbaar zijn voor je Laravel webapplicatie
Zo kunnen we voor het studenten voorbeeld een /studenten
endpoint hebben met formulieren om studenten aan te maken, op te vragen, te updaten en te deleten. De route /studenten
komt dan in de web.php
te staan aangezien deze de view teruggeeft met de verschillende forms, maar de endpoints die door de forms opgeroepen worden, zitten dan in de api.php
bv. /api/create_student
, /api/get_students
, /api/update_student
, /api/delete_student
Aangezien je dus vaak acties wil uitvoeren op de modellen heb je vaak een controller om die acties uit te voeren. De controller heeft dan een naamgeving in de aard van ModelnaamController
. Je kan nu rechtstreeks bij het aanmaken van een model ook nog een leeg controller skeleton voor dat model laten genereren met de -c
flag:
php artisan make:model ModelNaam -mc
Je kan zelfs nog meer heavy lifting over laten aan Laravel/artisan door de -cr
flag te gebruiken wat een Resource Controller
-klasse aanmaakt waar ook al boilerplate code voor CRUD methoden aanwezig zijn.
Een simpele CRUD application
Een voorbeeld van hoe de api.php en Controller kunnen samenwerken vind je hieronder:
- Routes
// Send a student object to a controller method
use App\Http\Controllers\StudentController;
use Illuminate\Support\Facades\Route;
Route::get('/students/{student}', [StudentController::class, 'show']);
- Controller
public function show(Student $student)
{
// De $student wordt automatisch opgehaald op basis van studnr
dd(response()->json($student));
}
Oefening
Maak de nodige views, controllers, modellen, migration tables en routes aan om het voorbeeld van een CRUD aan te maken voor de gedemonstreerde Student
-klasse in Laravel
Solution: Een voorbeeld oplossing voor deze oefening (met ook een factory en een seeder voor de Student klasse) vind je in deze zip folder.
Factory
Een factory in Laravel is een hulpmiddel om nepdata te genereren voor test- of ontwikkeldoeleinden. Het definieert hoe een model (bijv. een Student
) automatisch aangemaakt kan worden met realistische, willekeurige waarden.
Waarvoor gebruik je het?
- Testen: Snel data maken voor unit- of featuretests.
- Database vullen: Bijv. via seeders (
php artisan db:seed
). - Prototypes bouwen: Zonder handmatige data-invoer.
Je kan voor bestaande modellen een Factory aanmaken met het volgende commando: php artisan make:factory ModelNaamFactory --model=ModelNaam
. Het is echter ook belangrijk dat je de trait HasFactory
toevoegd aan je model
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Student extends Model
{
use HasFactory; // Add this trait
...
}
Voor het Student
-model wordt er dan bijvoorbeeld een StudentFactory.php
file aangemaakt in de directory database/factories/
. En voor onze Student klasse zal die er als volgt uitzien:
<?php
namespace Database\Factories;
use App\Models\Student;
use Illuminate\Database\Eloquent\Factories\Factory;
class StudentFactory extends Factory
{
protected $model = Student::class;
public function definition()
{
return [
'voornaam' => $this->faker->firstName(),
'naam' => $this->faker->lastName(),
'goedBezig' => $this->faker->boolean(),
];
}
}
Stel dat je geen autoincrement primary key hebt dan zou je deze op de volgende manier kunnen fabriceren: 'studnr' => $this->faker->unique()->numberBetween(1000, 2000),
bijvoorbeeld waarbij zeker unique()
belangrijk is.
Hoe werkt het? Met de faker library kan je geloofwaardige fake data aanmaken zoals namen, achternamen, emails, wachtwoorden … (meer info hier).
Om dan deze factory te gebruiken om een PHP object aan te maken, kan je volgende syntax gebruiken:
Student::factory()->make();
Met make()
wordt enkel een PHP object aangemaakt. Als je in de plaats hiervan create()
gebruikt, dan wordt het object ook onmiddellijk in de database gestoken.
Wil je dan toch nog zelf enkele vaste waarden kiezen zoals de goedBezig
bijvoorbeeld kan je dit als parameter meegeven:
Student::factory()->make(['goedBezig' => true]);
Je kan zelfs eenvoudig een lijst van PHP objecten aanmaken met de count
-functie:
Student::factory()->count(10)->make();
We gaan deze functionaliteit vooral in seeders gebruiken om snel dummy data in onze database te steken!
Seeder
Een seeder in Laravel is een tool om testdata in een database te laden. Het wordt vaak gecombineerd met factories om automatisch dummygegevens aan te maken, bijvoorbeeld voor ontwikkel-, test- of demodoelen.
Je maakt een seeder aan met: php artisan make:seeder ModelNaamSeeder
. Er wordt bijvoorbeeld een StudentSeeder.php
file aangemaakt in de directory database/seeders/
. Een seeder voor het Student
-model ziet er zo uit:
<?php
namespace Database\Seeders;
use App\Models\Student;
use Illuminate\Database\Seeder;
class StudentSeeder extends Seeder
{
public function run()
{
// Maak 10 studenten aan via de factory
Student::factory()
->count(10)
->create();
}
}
Belangrijk bij seeders:
Gebruik create()
in plaats van make()
om data persistent in de database op te slaan.
Hoe activeer je het? Voer php artisan db:seed
uit om alle seeders te draaien. Voor een specifieke seeder: php artisan db:seed --class=StudentSeeder
Let op bij unieke velden: Gebruik unique()
in je factory (bijv. voor studnr
) om dubbele entries te voorkomen. Seeders falen anders tijdens herhaald gebruik!
Extra functionaliteit: Seeders kunnen ook bestaande data gebruiken (bijv. CSV-bestanden inladen). Combineer met Model::updateOrCreate()
om dubbele data te vermijden. Hier gaan we niet dieper op in.
Seeders combineren
Combineer meerdere seeders via de DatabaseSeeder
-klasse:
public function run()
{
$this->call([
StudentSeeder::class,
CursusSeeder::class,
]);
}
Op die manier moet je maar één seeder klasse activeren om je hele applicatie van dummy data te voorzien.
Database met relaties
We tonen dit aan de hand van volgend project dat je hier kan terugvinden als zip bestand.
Belangrijk is nu wel dat je niet alle migrations tegelijk uitvoert maar via de --path=database/migrations/name.php
flag één na één de correcte migrations uitvoert zodat je geen problemen krijgt met de Foreign Key Constraints. In dit project dus eerst Opleiding en Vak, daarna Student en als laatste student_vak.
De relaties die hier dus belangrijk zijn:
- is de one-to-many relation tussen Opleiding en Student
- en de many-to-many relation tussen Vak en Student
Voor die many-to-many relation moeten we nu ook een aparte tabel voorzien in de database. Hiervoor moeten we dus ook een migration table aanmaken (die nu niet gekoppeld is aan een specifiek model). Je kan een migration table aanmaken met volgend commando: php artisan make:migration create_table_name_table
. Volgens conventie is de naam “create_” + “table name” + “_table”. Voor bovenstaande project wordt dit bijvoorbeeld:
php artisan make:migration create_student_vak_table
Bekijk het project om erachter te komen hoe je dit correct weergeeft in de corresponderende Models, Migration tables, Factories en Seeders.
De manier waarop je hier werk met de Object to Table mapping komt heel sterk overeen met dezelfde tool die we hiervoor in Java kunnen gebruiken JPA/Hibernate, wat we in de cursus Databases nog te zien gaan krijgen.