Inleiding
Webapplicatie bouwen
Opzet cursus
In deze cursus zal je stap voor stap leren door middel van een MVC opzet een webapplicatie te bouwen. Laat je niet afschrikken door af en toe best ingewikkelde code. Veel code hoeft maar eenmalig geschreven te worden. Daarna zal het voor alle situaties werken.
Aan het eind van de cursus kan je een webapplicatie bouwen met PHP en een MySQL database en kan je deze uitbreiden naar eigen wens.
Voor de opmaak zal gebruik worden gemaakt van tailwindcss. Dit is een CSS framework die zeer veel lijkt op standaard CSS code.
Er zal steeds een stuk code worden uitgelegd. Betreffende een versie van de uiteindelijke applicatie.
De volledige code is steeds aan het eind van een blok uitleg te downloaden.
De code is gebaseerd om de PHP cursus van Jeffrey Way. Dit is een video cursus waarin stap voor stap een PHP- framework wordt gebouwd. Zeker een aanrader.
MVC
Opzet
Er zijn meerdere methode om software te ontwikkelen. Een veel gebruikte methode is het ontwikkelen via het MVC-model. MVC staat voor Model View Controller
Model
Staat voor de informatie waarmee de applicatie werkt. Ook kan hier enige logica in de data worden gedefinieerd. De werkelijke opslag zal meestal in een database gebeuren. Deze valt buiten de applicatie. Er zal worden gestreeft naar zo min mogelijk strikte koppeling tussen de database en de applicatie. Het Model zorgt voor het ophalen en wegschrijven van data.
View
Een view wordt enkel gebruikt om informatie te tonen. Dus geen bewerkingen, berekeningen of iets dergelijks.
Controller
De controller reageert op gebeurtenissen (events). Meestal worden deze getriggerd door de gebruiker. In de controller kan logica opgenomen worden.
Interactie
Schematisch kan het MVC worden weergegeven als de afbeelding hieronder

Als we uitgaan van een webapplicatie.
De gebruiker bezoekt een pagina (request). Het bezoek aan de pagina wordt naar de controller gestuurd. Indien er gegevens nodig zijn zal de controller gebruik maken van één of meer models.
Na het uitvoeren van alle logica zal het de data worden verstuurd naar de view. De view zorgt ervoor dat de gebruiker de informatie netjes opgemaakt op het scherm te zien krijgt.
De bestandsflow ziet er zo uit

Directory structuur
Inleiding
Onze webapplicatie zal bestaan uit een een groot aantal .php bestanden. Doordat we veel bestanden zullen aanmaken moeten we een goede structuur en naamgeving gebruiken om overzicht te houden. Uiteraard is de naamgeving van de bestanden een persoonlijke voorkeur. Maar om overzicht te houden is het raadzaam om de geadviseerde naamgeving te volgen.
index.php
Indien je geen verzoek doet naar een specifieke webpagina zal standaard worden doorgestuurd naar het bestand index.php. Dit bestand zal onze ingang zijn en we zullen alle verzoeken via dit bestand laten verlopen. Doordat alle verzoeken via index.php verlopen kunnen we op één plaats in de code een controlle doen op alle verzoeken. En variabele en functies op één plaats toevoegen en die later overal in de code kunnen gebruiken.
Webroot directory
Na het intypen van een url bv. https://www.google.com kom je in de webroot directory van de webserver terrecht. Standaard zal hier een bestand index.php staan. (of een andere extensie bij gebruik van een andere programmeertaal). Dit bestand zal worden uitgevoerd.
Het ligt aan de webserver welke naam de webroot directory heeft. Bij gebruik van XAMPP zal het 'htdocs' zijn. In het geval van USBwebserver zal het 'root' zijn.
Directory structuur
We gaan ervanuit dat je webroot directory 'root' heet. Alle bestanden die onze applicatie gebruikt waar de gebruiker niet bij hoeft te kunnen staan in 'src'. In 'config.php' komen oa de gebruikersnamen en wachtwoorden te staan. Zodat we die op een plaats kunnen aanpassen mocht er iets wijzigen.

- /images hier komen al onze afbeeldingen
- .htaccess bevat wat informatie voor de webserver. De inhoud hoef je niet te kunnen. Dit valt buiten de cursus.
- index.php vanuit hier gaat alles gebeuren
- /controllers hier komen onze controllers
- /models hier komen onze models
- /view hier komen onze views
- functions.php hier komen de functies die we eigenlijk altijd willen kunnen gebruiken
- router.php dit bestand verwijst ons door naar het jusite bestand
- style.css de opmaak code kan je hier kwijt.
Bestandsinhoud
index.php
<?php
//Hier kunnen we alles inladen wat we op elke pagina nodig hebben
//configuratie bestand
$config = require "config.php";
//Onze veel gebruikte functies
require "functions.php";
// Hier gaat de doorverwijzing naar andere pagina's
require "router.php";
Dit bestand zal bij elk verzoek aan de webserver worden ingeladen.
config.php en functions.php zijn tools die we in onze applicatie zullen gebruiken.
De verdere afhandeling laten we over aan router.php
router.php
<?php
//ROUTER
// Hier doen we een controle of een bepaalde URL bestaat en we verwijzen door naar een controller of een view
//Uitlezen van de huidige URL
$uri = parse_url($_SERVER['REQUEST_URI'])['path'];
//Als de uri voldoet aan de volgende voorwaarde dan ...
switch ($uri) {
case "/":
case "/home": //als de url /home bevat dan
require "controllers/home.php";
break;
case "/about":
require "controllers/about.php";
break;
case "/contact":
require "controllers/contact.php";
break;
default: //niet bekende uri dan ...
http_response_code(404);
require "view/404.view.php";
}
Als eerste zal de aangevraagde URL worden ontleed. Stel het verzoek is http://localhost/pagina?id=1
Hier zal van worden gemaakt '/pagina'. Dat is het verzoek dat we zullen gebruiken.
We gebruiken een switch om te kijken welke URI binnenkomt. Je mag hier natuurlijk ook if en else gebruiken.
Indien de URI overeenkomt met een bepaalde string zal de router je doorverwijzen naar de betreffende controller of view.
Rol van gebruiker
In je router is het ook eenvoudig om de rol van een gebruiker te controleren. Als een gebruiker niet de juiste rol heeft verwijs je de gebruiker naar een view "geen-toegang.view.php". Deze moet je wel nog even aanmaken. Bijvoorbeeld
case "/berichten":
if(hasRole('admin')) {
require "controllers/bericht.index.php";
}
require "view/geen-toegang.view.php";
break;
functions.php
<?php
//handig om te debuggen welke inhoud er in een bepaalde variabele zit
function dd($variable = null)
{
echo "<pre>";
var_dump($variable);
echo "</pre>";
die();
}
function isUri($pad)
{
if (parse_url($_SERVER['REQUEST_URI'])['path'] === $pad) {
return true;
}
return false;
}
We starten met deze twee functies.
dd($variabele); is handig om even de inhoud van een variabele op het scherm te tonen. Dit wordt veel gebruikt om je code te debuggen (fouten eruit te halen)
isUri($pad) kan je bijvoorbeeld in je nav-bar gebruiken om te kijken of je op dat moment op de betreffende pagina bent.
dd() functie
Mocht je maar even twijfelen wat de inhoud van een variabele in je code is gebruik dan de dd() functie om even te kijken. Deze schrijft de inhoud op je scherm. Bijvoorbeeld:
dd($_POST);
dd($_SESSION);
config.php
<?php
return [
'app' => [
'name' => 'Web verbeteraar',
],
'database' => [
'user' => 'root',
'password' => 'usbw',
'port' => 3306,
'host' => 'localhost',
'dbname' => 'databasenaam',
'charset' => 'utf8mb4',
]
];
Dit bestand is een voorbeeld van een aantal configuratie parameters die je op meerdere plekken in je applicatie waarschijnlijk gebruikt. Hier kan je op één plaats het overal aanpassen. Je kan hier natuurlijk je eigen parameters toevoegen.
Stel dat je in een view de naam van je app op het schem wilt tonen kan dat d.m.v.
<?= $config['app']['name'] ?>
Je kan ook aan functions.php onderstaande functie toevoegen.
function config($param)
{
global $config;
$path_items = explode(".", $param);
$result = $config;
foreach ($path_items as $item) {
if (isset($result[$item]))
$result = $result[$item];
else {
dd("config param " . $param . " bestaat niet");
}
}
return $result;
}
Indien deze functie is toegevoegd kan je configuratie parameters gebruiken door
<?= config("app.name") ?>
Net iets eenvoudiger, maar beide opties blijven werken.
.htaccess
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
Homepage
/home
Root pagina
Onze home pagina of te wel root pagina gaat home.php heten.
Hiervoor maken we in de map controllers een bestand home.php.
<?php
$title = "Home";
//hier volgt later nog meer logica
require "view/home.view.php";
Boven aan in deze pagina doen we onze logica. Daarna sturen we deze logica naar een view. Deze is geplaatst in de view directory. Als naamgeving gebruiken home.view.php doordat we in de naam van de view. 'view' gebruiken kun je snel zien dat het de view betreft.
<?php
require "parts/header.view.php";
require "parts/menu.view.php";
?>
<!-- Hier komt al je html code -->
<?php
require "parts/footer.view.php";
Op elke pagina komen een aantal dingen steeds weer terug. Het starten met HTML met de <head>
Al deze code hebben we ondergebracht in het bestand 'header.view.php'. Op bijna elke pagina komt een navigatie menu deze code hebben we in 'menu.view.php' geplaatst. Het zelfde geldt voor de footer. Als we het navigatie menu willen aanpassen kan dat in één bestand en dan wijzigt het menu op elke pagina.
parts/header.view.php
<!doctype html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><?= $title??'website' ?></title>
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>
<link href="style.css" rel="stylesheet" type="text/css"/>
</head>
<body>
Natuurlijk kan je hier je eigen header kwijt. Pas aan naar eigen wens. Let op dat de header een variabele 'title' gebruikt. Deze moet natuurlijk wel in de controller worden gedefinieerd.
parts/menu.view.php
<?php
//hier komt je menu, ontwerp zelf wat of leen wat code van andere pagina's bv van https://tailwindui.com/components/preview
?>
<nav>
<div class="flex gap-8 bg-blue-50 p-2">
<div class="flex">
<!-- Logo van je website -->
<img class="h-8 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="bedrijfslogo">
<span class="ml-4 font-medium"><?= $config['app']['name'] ?></span>
</div>
<div>
<a href="/" class="hover:text-indigo-700 <?= isUri("/") ? 'bg-purple-200 rounded p-2' : '' ?>">Home</a>
</div>
<div>
<a href="/about" class="hover:text-indigo-700 <?= isUri("/about") ? 'bg-purple-200 rounded p-2' : '' ?>">About</a>
</div>
<div>
<a href="/contact" class="hover:text-indigo-700 <?= isUri("/contact") ? 'bg-purple-200 rounded p-2' : '' ?>">Contact</a>
</div>
</div>
</nav>
Ons menu bevat een logo en naam van onze website. Daarnaast drie links naar de pagina's van onze website. Deze moeten we natuurlijk wel nog aanmaken.
In ons menu wordt de isUri() functie gebruikt. Deze kijkt of we op de betreffende pagina zijn. Zodat de opmaak kan worden aangepast.
isUri('/home') ? 'style bij waar' : 'style bij niet waar'
Download Versie 1
Download nu het start bestand en plaats de code in je /root. Let op dat je geen map in je root directory kan plaatsen met deze bestanden erin. Heb je bestanden die je graag wilt bewaren. Maak dan een backup van je root directory.
Download Router - Controller - View start bestand
Speel wat met de bestanden zodat je ongeveer begrijpt wat er gebeurt. De structuur is misschien even wennen (lastig). Maar als je hier iets langer mee hebt gewerkt wil je niet meer anders.
Start deel 2 - Database gebruiken
We gaan nu starten met deel 2 van de cursus. Hierin zullen we een database object maken. Om onze queries eenvoudig uit te kunnen voeren, zonder onszelf steeds te herhalen.
Om dit voor elkaar te krijgen gaan we wat toevoegingen doen aan ons voorgaande code.
Aan het eind van deel 2 is de volledige versie weer te downloaden
Database en models
Database connectie en tools
Database extensies
Om met PHP connectie met een database te maken maak je gebruik van een database extensie. Voor veel verschillende databases zijn connecties beschikbaar. Wij zullen met MySQL werken. Daarvoor hebben we eigenlijk twee keuzes:
Omdat wij onze code zo veel mogelijk onafhankelijk van de de database gaan maken. Zal er maar een klein deelte deel gebruik maken van één van deze extensies. Zou je een andere database gaan gebruiken hoeft er ook maar een klein stukje code aangepast te worden.
Database mangement tools
Database management
Om een database aan te maken is het handig om een database mangement applicatie te gebruiken.
Voor de MS Windows gebruikers is Heidi-sql een aanrader. Uiteraard kan je ook PHPMyAdmin gebruiken die waarschijnlijk ingebakken is in je webserver.
Wij zullen in de cursus Heidi-sql gebruiken. Heidi is gratis te downloaden. Pak uit en open
Open je webserver en MySQL database.
Open Heidi-sql. Nu moet je een connectie met de database maken. Aan de linkerkant van je scherm kan je op 'nieuw' klikken en daarmee een nieuwe sessie aanmaken. Aan de rechterkant van het scherm geef je jou gegevens. Voor ons:
- hostname: localhost of 127.0.0.1
- gebruiker: root
- Wachtwoord: usbw (of iets anders als je het gewijzigd hebt)
- port: 3306
Sla op deze gegevens op zodat je dit volgende keer niet nog eens hoeft in te voeren.
Database aanmaken
Klik aan de linker kant op localhost met de rechter muisknop en volg het menu

Geef je database een naam en kies de juiste Collatie. Indien beschikbaar utf8mb4_unicode_ci. Maar de utf8mb4_0900_ai_ci is ook oké. Eventueel een utf8_general_ci als alternatief.

Tabel aanmaken
We gaan nu een tabel aanmaken. Klik daarvoor met rechtermuisknop op je database en selecteer 'nieuwe maken' => 'tabel'

We komen nu in het tabel edit scherm.
Geef als eerste de tabel een naam, bv 'users' (bijna elke webdatabase heeft een users tabel)
Wij voegen de volgende kolommen toe met deze types.

De primaire sleutel (geel) bij id kan je toevoegen door met de rechtermuis op id te klikken 'maak nieuwe index'=> 'primairy'. De unieke sleutel van email kan op een vergelijkbare manier worden toegevoegd.
Indien je de tabel snel wilt creeeren zonder veel toestanden kan je ook onderstaande SQL code uitvoeren.
CREATE TABLE IF NOT EXISTS `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`voornaam` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
`tussenvoegsel` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`achternaam` varchar(80) COLLATE utf8mb4_unicode_ci NOT NULL,
`gbdatum` date DEFAULT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Uiteraard kan je zelf nog meer tabellen toevoegen.
Database vullen
Om te starten kan je een paar gebruikers toevoegen. Dit kan je zelf doen of je kan onderstaande insert queries draaien.
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 'ctromp@example.org', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Ted', NULL, 'Rau', NULL, '2022-11-17 22:11:44', '2022-11-17 22:11:44', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (2, 'dhansen@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Kurt', NULL, 'Brown', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, 'koss.michale@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Eve', NULL, 'Collier', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (4, 'lhickle@example.org', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Destin', NULL, 'Wuckert', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (5, 'crona.kaylie@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Aylin', NULL, 'Weber', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (6, 'kory.crooks@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Brooks', NULL, 'Ward', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (7, 'hills.allan@example.net', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Liliana', NULL, 'Hagenes', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (8, 'kilback.jewel@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Nikolas', NULL, 'Gislason', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (9, 'nebert@example.net', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Jermaine', NULL, 'Wiegand', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
INSERT INTO `users` (`id`, `email`, `password`, `voornaam`, `tussenvoegsel`, `achternaam`, `gbdatum`, `created_at`, `updated_at`, `deleted_at`) VALUES (10, 'nrunolfsdottir@example.net', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Larry', NULL, 'Kemmer', NULL, '2022-11-17 22:12:14', '2022-11-17 22:12:14', NULL);
Elke gebruiker krijgt een wachtwoord HASH. Daarbij is het wachtwoord 'password'
Database class
PDO
Hieronder een korte uitleg van een query dmv van de PDO extensie.
Om dit te laten werken is het belangrijk dat in config.php de juiste database connecties zijn ingevuld.
Daarnaast wordt gebruik gemaakt van de config() functie. Let op dat je die in je functions.php komt te staan.
Toevoegen aan functions.php
function config($param)
{
global $config;
$path_items = explode(".", $param);
$result = $config;
foreach ($path_items as $item) {
if (isset($result[$item]))
$result = $result[$item];
else {
dd("config param " . $param . " bestaat niet");
}
}
return $result;
}
Hieronder staat een Database class die we hieronder kort zullen bespreken. Een class is een blauwdruk van een object. Database.php komt in de root directory te staan. Let op de hoofdletter in de bestandsnaam. Zo geef je aan dat in het een class betreft.
<?php
class Database
{
//is de PDO connectie
private $connection;
//wordt aangeroepen als je een nieuw database object aanmaakt
public function __construct()
{
$dns = "mysql:host=" . config('database.host') . ";" .
"port=" . config('database.port') . ";" .
"dbname=" . config('database.dbname') . ";" .
"charset=" . config('database.charset');
try { //probeer een verbinding te maken
$this->connection = new PDO($dns, config('database.user'), config('database.password'), [
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (Exception $e) { //als het niet lukt dan...
$this->showException($e);
}
}
public function query($sql, $params = [])
{
try { //probeer de query uit te voeren
//aanmaken van een query
$query = $this->connection->prepare($sql);
//query uitvoeren
$query->execute($params);
return $query;
} catch (Exception $e) { //als het niet lukt dan ...
$this->showException($e);
}
}
public function lastInsertId()
{
return $this->connection->lastInsertId();
}
//tonen van een mislukking
private function showException($exception)
{
//pas aan voor een productie omgeving
dd($exception->getMessage());
//productie
//echo "Er is een fout opgetreden, ga <a href='/'>terug</a> naar de website";
//die();
}
}
Een class is een beschrijving van een object dat je steeds kan herbruiken. Omdat wij regelmatig onze database gaan aanroepen is het handig om dit in een class te zetten. Deze is eenvoudig aan te roepen. Zonder dat we steeds weer moeten kijken hoe alles werkt.
Als je niet snapt wat er gebeurt is dit helemaal niet erg. Dit is een eenmalige actie en we kunnen deze telkens opnieuw gebruiken.
Database class gebruiken
<?php //dit is een voorbeeld die je kan uitvoeren in index.php
//Hier kunnen we alles inladen wat we op elke pagina nodig hebben
//configuratie bestand
$config = require "config.php";
//Onze veel gebruikte functies
require "functions.php";
require "Database.php";
//nieuw object Database aanmaken, we stoppen hier de configuratie gegevens in om verbinding met de database te maken
$db = new Database($config['database']);
//gegevens in een variabele zetten (meerdere users)
$users = $db->query("SELECT * FROM users")->fetchAll();
//dd($users);
//enkele user
$user = $db->query("SELECT * FROM users WHERE id=1")->fetch();
//dd($user);
//dynamische query die is beschermt tegen SQL-injecties
//voor de ? zal in dit geval 1 worden ingevuld. (dit zal meestal een variabele zijn die je via een formulier meestuurd.
//zie voor verdere uitleg https://laracasts.com/series/php-for-beginners-2023-edition/episodes/20
$user = $db->query("SELECT * FROM users WHERE id = ?", [1])->fetch();
//dd($user);
//met meerdere parameters: maakt query SELECT * FROM users WHERE email='john@mail.nl' and password='doe'
$user = $db->query("SELECT * FROM users WHERE email = ? AND password = ?", ['john@mail.nl', 'doe'])->fetch();
//dd($user); //zal false op het scherm schrijven omdat deze user niet bestaat
In index.php kan je even spelen met bovenstaande code. Hiermee kan je een query op de database uitvoeren. Met de dd() functie kan je het resultaat op het scherm schrijven. Het verschil tussen fetch() en fetchAll() is het ophalen van één resultaat of meerdere resultaten. Let op dit is alleen om even te testen. Daarna verwijderen we de code uiteraard uit index.php.
Uiteraard kan je ook update queries op deze manier uitvoeren
$db = new Database();
$db->query("UPDATE users SET voornaam = ? WHERE id=?",['Piet',1]);
Deze query zal de voornaam van id=1 wijzigen in Piet
Aantal gewijzigde rijen
Als je wilt weten hoeveel rijen er gewijzigd
$db = new Database();
$aantal = $db->query("UPDATE users SET voornaam = ? WHERE id=?", ['Pietje', 1])->rowCount();
$aantal zal een getal zijn. Indien 0 dan is er iets mislukt met de update.
Id van een insert
Als je een insert-query doet met een autoincreament kolom dan wil je af en toe weten welk id is aangemaakt.
$db->query("INSERT INTO users (email,password,voornaam,achternaam) VALUES (?,?,?,?)", [
'pietje@mail.nl',
password_hash('password', PASSWORD_BCRYPT),
'Pietje',
'Puck'
]);
$id = $db->lastInsertId();
dd($id);
$id krijgt het id'nr van de laatst ingevoerde user.
Gegevens verwijderen
$db = new Database();
$aantal = $db->query("DELETE FROM users WHERE id=?", [1])->rowCount();
dd($aantal);
Dit zal user met id=1 weggooien. Indien deze user bestaat dan zal $aantal gelijk zijn aan 1 (1 rij verwijderd).
MYSQLI
Under construction...
Database object gebruiken
Berichten voorbeeld
We gaan onze database uitbreiden met een 'berichten' tabel. Voor onderstaan de query uit op je database.
CREATE TABLE `berichten` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`titel` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`bericht` MEDIUMTEXT NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`van_id` INT(10) UNSIGNED NOT NULL,
`aan_id` INT(10) UNSIGNED NOT NULL,
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `FK_berichten_users` (`van_id`) USING BTREE,
INDEX `FK_berichten_users_2` (`aan_id`) USING BTREE,
CONSTRAINT `FK_berichten_users` FOREIGN KEY (`van_id`) REFERENCES `users` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT `FK_berichten_users_2` FOREIGN KEY (`aan_id`) REFERENCES `users` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
;
Controllers, views en routes toevoegen
CRUD
In onze applicatie willen we meestal CRUD acties kunnen uitvoeren. CRUD staat voor CREATE, READ, UPDATE, DELETE.
Voor de berichten gaan we een CRUD maken, daarmee heb je een voorbeeld voor jou applicatie waar je waarschijnlijk dezelfde soort acties wil uitvoeren. Daarnaast wil je een overzicht hebben van al je berichten, dit noemen we vaak INDEX.
Controllers aanmaken
We maken 5 controllers (in de controller directory)
- bericht.read.php
- bericht.create.php
- bericht.delete.php
- bericht.update.php
- bericht.index.php
Views aanmaken
We maken 5 views (in de view directory)
- bericht.read.view.php
- bericht.create.view.php
- bericht.delete.view.php
- bericht.update.view.php
- bericht.index.view.php
Router aanpassen
In de router moeten we nieuwe routes toevoegen
//berichten
case "/berichten":
require "controllers/bericht.index.php";
break;
case "/lees-bericht":
require "controllers/bericht.read.php";
break;
case "/schrijf-bericht":
require "controllers/bericht.create.php";
break;
case "/wijzig-bericht":
require "controllers/bericht.update.php";
break;
case "/verwijder-bericht":
require "controllers/bericht.delete.php";
break;
Start deel 3 - Inloggen
Wat zouden we willen
In het start template staat op de 'home' pagina een inlogformulier. Deze werkt nog niet, maar deze willen we graag laten werken. Schematisch zal het inloggen er zo uitzien.

Bij stap inlog formulier moeten we de 'action' van het formulier laten wijzen naar een 'route' die we moeten aanmaken in onze router.
case "/login":
require "controllers/login.php";
break;
We moeten de login controller aanmaken
- controle op invullen van email en wachtwoord
- selecteer de gebruiker met het betreffende email adres
- controleer of het wachtwoord van deze gebruiker overeenkomt met het ingevoerde wachtwoord
Indien één van deze stappen vaalt dan wordt de gebruiker teruggestuurd naar het inlog formulier.
Als alle stappen goed zijn doorlopen dan wil je dat er onthouden wordt dat de gebruiker is ingelogd. Zodat hij niet bij het surfen door je website bij elke pagina opnieuw moet inloggen.
Session
Dat onthouden dat een gebruiker is inlogd doen we in een session. Om hiervan gebruik te maken hebben we aan index.php helemaal boven in session_start(); toegevoegd.
Sessie variabele komen in $_SESSION te staan. Dit is een array die wij zelf kunnen vullen. En bewaard blijven zolang de browser open staat en de tijdsduur van de sessie niet is verstreken.
Omdat we de gegevens van de gebruiker op veel pagina's gaan gebruiken is het handig om alle gegevens van een gebruiker op te halen uit de database en in de sessie te zetten. (alleen wachtwoord liever niet)
Succesvol
Als alles goed is verlopen verwijzen we de gebruiker door middel van een redirect(...) door naar de pagina waar we de gebruiker na het inloggen heen willen sturen.
Voorbeeld van login controller
<?php
//controlle op aanwezigheid van email en wachtwoord
if (!(isset($_POST['email']) and isset($_POST['password']))) {
require "view/login.view.php";
die();
} else {
if (empty($_POST['email'])) {
$error = "Email mag niet leeg zijn";
require "view/login.view.php";
die();
}
if (empty($_POST['password'])) {
$error = "Wachtwoord mag niet leeg zijn";
require "view/login.view.php";
die();
}
}
//selecteren van de gebruiker in de database adh van email
$db = new Database();
$user = $db->query("SELECT * FROM users WHERE email=?", [$_POST['email']])->fetch();
// bestaat deze gebruiker?
if (!$user) {
$error = "Gebruiker bestaat niet";
require "view/login.view.php";
die();
}
//controleren of wachtwoord juist is
if (!password_verify($_POST['password'], $user['password'])) {
$error = "Onjuist wachtwoord";
require "view/login.view.php";
die();
}
//gebruiker in session zetten, maar het wachtwoord laten we weg
unset($user['password']);
$_SESSION['user'] = $user;
//doorverwijzen waar we de ingelogde gebruiker naar toe willen laten gaan
redirect("/home");
Rollen
In de applicatie die we bouwen kan een gebruiker meerdere rollen hebben. Een gebruiker kan een administator zijn of een gewone gebruiker. Deze rol moeten we aan een gebruiker toekennen. Daarvoor moeten we aan onze users tabel een veld 'role' toevoegen.
Dit toevoegen kan met het volgende SQL-commando
ALTER TABLE `users`
ADD COLUMN `role` ENUM('admin','user') NULL DEFAULT 'user' AFTER `password`;
Dit kan ook handmatig bv via Heidi-SQL
Handige functies
Omdat we vaak willen weten of een gebruiker een bepaalde 'role' heeft is het handig om daar een functie voor te maken
function hasRole($role): bool
{
if (isset($_SESSION['user']) and $_SESSION['user']['role'] == $role) {
return true;
}
return false;
}
Daarnaast is het handig om een functie te hebben die teruggeeft of een gebruiker is inloged of niet.
function isLogin(): bool
{
if ($_SESSION['user']['id'] ?? false) {
return true;
}
return false;
}
Ook voor het tonen van gebruikersgegevens is een helper functie misschien handig, bijvoorbeeld voor de naam van de gebruiker
function username(): string
{
if (isLogin()) {
return $_SESSION['user']['voornaam'] . " " .
$_SESSION['user']['tussenvoegsel'] . " " .
$_SESSION['user']['achternaam'];
}
return '';
}
Uiteraard kan je ook gegegevens van de gebruiker direct uit de session ophalen door bv
$_SESSION['user']['email']
Uitloggen
Om uit te loggen moet de user verwijderd worden uit de sessie.
Je zou hiervoor een route \logout kunnen maken die verwijst naar 'logout' controller.
In deze controller zorg je ervoor dat de sessie van de user verwijderd wordt dmv
unset($_SESSION['user']);
Daarna kan je de gebruiker doorverwijzen naar een pagina naar keus
redirect("/");
Download - Versie 3
Omdat het veel bestanden betreft is het handig om de volledige code te downloaden.
Let op de wijzigingen in versie 3
- toegevoegd aantal functies in function.php
- toegevoegd Database.php
- Nieuwe routes in router.php
- Nieuwe controllers
- Nieuwe views
- Aanpassingen aan index.php
- view/parts/menu.view.php uitgebreid
Download versie 3
Extra toevoegingen
Drop down menu
Voor een drop down menu hebben we een stukje javascript nodig. In dit voorbeeld maken we gebruik van alpinejs. Omdat we niet alles zelf willen schrijven
Stap 1
Voeg de javascript file toe in onze header van elke pagina dus in view/parts/header.view.php
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
Als we tailwind gebruiken kan je in je menu bijvoorbeeld onderstaande code gebruiken
<div class="mr-2 py-2" x-data="{open: false}" @click="open = true" @mouseleave="open = false">
<div class="relative flex items-center space-x-1 cursor-pointer text-gray-700 hover:bg-pink-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
<!-- Items waarop je kan klikken om uit te klappen -->
<div class="flex items-center">
<span><?= username() ?></span>
<svg xmlns="http://www.w3.org/2000/svg" class="pl-1 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<!-- Uitklap blok dat verschijnt bij klikken -->
<div class="origin-top-right absolute top-10 right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-50" x-show="open" x-cloak role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
<!-- Active: "bg-gray-100", Not Active: "" -->
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full inline-flex items-center" role="menuitem" tabindex="-1" id="user-menu-item-0">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
Profiel</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full inline-flex items-center" role="menuitem" tabindex="-1" id="user-menu-item-1">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
</svg>
Wijzig wachtwoord</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full inline-flex items-center" role="menuitem" tabindex="-1" id="user-menu-item-2">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
Berichten</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full inline-flex items-center" role="menuitem" tabindex="-1" id="user-menu-item-3">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
</svg>
Sign out</a>
</div>
</div>
</div>
flash bar
Het kan mooi zijn om gebruikers over bepaalde acties te informeren door een flash-bar te tonen.
Een flash-bar is een balk die heel even in beeld verschijnt en na een x aantal seconden weer verdwijnt.
Als je alle stappen doorloopt kan je de gebruiker informeren door het aanroepen van de flash functie.
flash('Dat heb je goed gedaan');
Als er iets misgaat
flash('Oeps, er is iets misgegaan',false);
En eventueel de tijdsduur dat het in beeld blijft in ms (default 2500ms)
flash('hallo', true, 4000);
Het toevoegen is vrij eenvoudg in drie stappen toe te voegen
Stap 1:
Voeg de javascript file toe in onze header van elke pagina dus in view/parts/header.view.php
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
Stap 2:
voeg de flash functie toe aan functions.php
function flash(string $msg, bool $succes = true): void
{
$_SESSION['flash']['msg'] = $msg;
if ($succes) {
$_SESSION['flash']['success'] = $msg;
} else {
$_SESSION['flash']['error'] = $msg;
}
}
Stap 3:
Vvoeg het tonen van de flash functie toe aan de footer.view.php (let er wel op dat je de footer.view.php op elke pagina waar je de 'flash' wilt gebruiken toevoegd dmv een require.)
</footer>
<!-- indien er een flash message is -->
<?php if ($_SESSION['flash'] ?? false): ?>
<div x-data="{ nofifiction: true }"
x-init="setTimeout(function(){ nofifiction=false; }, <?= $_SESSION['flash']['duration']; ?>)"
class="fixed top-12 w-5/6 ml-6 z-20">
<div x-show="nofifiction"
x-transition
class="flex items-center justify-between gap-6 p-4 w-full bg-white border rounded-md shadow-sm <?= ($_SESSION['flash']['error'] ?? false) ? 'bg-red-100' : 'bg-green-50' ?>">
<?php if ($_SESSION['flash']['error'] ?? false): ?>
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<p class="ml-3 text-sm font-bold text-red-600"><?php echo $_SESSION['flash']['msg']; ?></p>
</div>
<?php else: ?>
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8 text-green-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"/>
</svg>
<p class="ml-3 text-sm font-bold text-green-600"><?= $_SESSION['flash']['msg']; ?></p>
</div>
<?php endif ?>
<span @click="nofifiction=false;" class="inline-flex items-center cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</span>
</div>
</div>
<?php endif ?>
<?php unset($_SESSION['flash']); //verwijderen zodat het volgende keer niet nog eens wordt getoond ?>
</body>
</html>
We kunnen nu onze flash bar gebruiken in elke controller
flash('hallo wereld!');
CSRF-protection
CSRF protectie is een beveiliging dat niet iedereen via andere websites een POST kan doen op formulieren van jou website. Dit geeft meer veiligheid tegen hackers.
Alle ins en outs over CSRF protectie kan je hier lezen.
We gaan onze eigen CSRF protectie inbouwen in onze site. Daarvoor moeten we bij elke POST request een controle doen of deze van onze site afkomstig is. Dit doen we door aan elk formulier een de volgende code toe te voegen
<form method="post">
<?= csrf() ?>
...
</form>
We gaan een nieuw bestand maken csrf.php en voegen een require toe aan index.php. Let op dat je dit doet nadat de functions.php wordt toegevoegd.
//csrf protectie
require "csrf.php";
functions.php
Dit bestand krijgt twee nieuwe functies. Een functie om de token te maken en toe te voegen aan het formulier. En een functie om te kijken of een meegestuurde token geldig is.
//valideren van een token
//valideren van een token
function validateToken(): bool
{
if (in_array($_POST['_token'] ?? '', $_SESSION['tokens'] ?? [])) {
$_SESSION['tokens'] = []; //alle tokens wissen, zodat ze niet hergebruikt kunnen worden
return true;
}
if (isset($_SERVER['HTTP_X_CSRF_TOKEN'])) {
if ($_SERVER['HTTP_X_CSRF_TOKEN'] == ($_SESSION['api_token'] ?? "")) {
return true;
}
}
return false;
}
//aanmaken van een token voor je formulier
function csrf($field = true): string
{
$bytes = random_bytes(256);
$token = bin2hex($bytes);
$_SESSION['tokens'][] = $token;
if ($field) {
return "<input type=\"hidden\" name=\"_token\" value=\"$token\">";
} else {
return $token;
}
}
csrf.php
Dit bestand doet een controle of er een POST is en roept de valideer functie aan.
Indien niet geldig zal het script de gebruiker een 401 error geven.
<?php
//CSRF protection
if ($_POST != null) {
if (!validateToken()) {
http_response_code(401);
$error = "CSRF-token mismatch error.";
require "view/401.view.php";
die();
}
}
status 401 view
Voeg een view/401.view.php toe met bijvoorbeeld onderstaande code
<?php
require "parts/header.view.php";
require "parts/menu.view.php";
?>
<!-- hier de about pagina -->
<div class="mx-auto max-w-7xl py-20 sm:px-6 lg:px:8">
<h1 class="text-2xl font-bold mb-4">401 Geen toegang: <?= $error ?? '' ?></h1>
<p class="mt-2">
<a href="/" class="text-indigo-800 hover:text-indigo-600">Ga terug naar home</a>
</p>
</div>
<?php
require "parts/footer.view.php";
Nu kan je de CSRF protectie makkelijk aan en uit zetten door wel of niet in je index een // voor require "csrf.php" te zetten
//csrf protectie
//require "csrf.php";
Content Security Policy
Problemen
Omdat we van tailwind een CDN gebruiken is dit lastig te implementeren. Gebruik je geen tailwind dan is deze toevoeging wel een aanrader
Content Security Policy (CSP) maakt de applicatie voor eindgebruikers veiliger. Er kunnen dan geen onbekende javascript injecties plaatsvinden in de applicatie.
Om dit te laten werken moeten een aantal stappen worden genomen.
Voeg aan je index.php een require toe de volgende header tag toe. Doe nadat de functions.php is ingeladen.
require "csp.php";
csp.php
header("Content-Security-Policy: base-uri 'self';" .
"connect-src 'self';" .
"default-src 'self';" .
"form-action 'self';" .
"img-src 'self' tailwindui.com ;" .
"media-src 'self';" .
"object-src 'none';" .
"script-src cdn.jsdelivr.net cdn.tailwindcss.com unpkg.com 'nonce-" . getNonce() . "' 'unsafe-eval';" .
"style-src cdn.jsdelivr.net cdn.tailwindcss.com 'self' 'nonce-" . getNonce() . "'"
);
Aan functions.php voeg je de functie getNonce() toe
//wordt gebruikt voor Content Security Policy
function getNonce(): string
{
if (!isset($_SESSION['nonce'])) {
$bytes = random_bytes(20);
$_SESSION['nonce'] = bin2hex($bytes);
}
return $_SESSION['nonce'];
}
Aan alle javascripts en css bestanden die je toevoegd bij je project moet je toevoegen
<script nonce="<?php echo getNonce(); ?>">
// je script code
</script>
Indien je op een speficieke pagina een stukje CSS wilt toevoegen kan dat alsvolgt
<style nonce="<?php echo getNonce(); ?>">
/* je script code */
</style>
Inline styles/scripts worden default geblokkeerd. Kijk regelmatig even bij developer tools -> console
hier kan je zien dat bepaalde code wordt geblokkeerd. Wanneer je dit toch wilt toestaan kan dit door het aanpassen van de CSP header in csp.php
Dynamisch zoeken
Als je zoekt naar iets zijn we tegenwoordig gewend om de resultaten direct te krijgen terwijl we aan het typen zijn. Als we dit voor elkaar willen krijgen dan is dat niet mogelijk door een formulier met een submitbutton te gebruiken.
We gaan nu stap voor stap een dynamisch zoekveld maken. We gebruiken voor deze code de tabel 'users' en we gaan er vanuit dat de kolommen email, voornaam, achternaam bestaan.
Stap 1
Zorg ervoor dat je header.view.php in de <head> de volgende scripts toevoegd
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://unpkg.com/axios@1.1.2/dist/axios.min.js"></script>
We maken een nieuwe directory 'api'. In deze directory komen controllers te staan die via een api worden aangeroepen.
Stap 2
In onze api directory maken we een bestand users.search.php met de volgende inhoud
<?php
//gebruik van database object
$db = new Database();
//we willen met like zoeken dus even % er voor en achter
$name = "%" . $_GET['name'] . "%";
//users opzoeken die aan onze zoek query voldoen en deze in $users zetten
$users = $db->query(
"SELECT id, voornaam, achternaam, email
FROM users
WHERE voornaam LIKE ?
OR achternaam LIKE ? LIMIT 10", [
$name, $name] //we hebben twee plekken waar we $name in moeten vullen
)->fetchAll();
// het resultaat geven we terug in json formaat
echo json_encode($users);
Stap 3
Aan onze router.php voegen we twee nieuwe routes toe
case "/users":
require "view/users.zoeken.view.php";
break;
case "/search-users":
require "api/users.search.php";
break;
Stap 4
We maken een bestand view/users.zoeken.view.php
<?php
require "parts/header.view.php";
require "parts/menu.view.php";
?>
<h1 class="font-2xl font-bold m-10">Zoek users</h1>
<div x-data="searchUsers()" class="m-10">
Zoek gebruikers: <input type="text" @keyup="fetchUsers()" x-model="searchfield">
<!-- indien er resultaten gevonden zijn dan tonen -->
<template x-if="users.length">
<table class="w-1/2">
<tr>
<td class="font-bold">Email</td>
<td class="font-bold">Voornaam</td>
<td class="font-bold">Achternaam</td>
</tr>
<!-- Loop door alle gevonden users -->
<template x-for="user in users">
<tr @click="goto(user.id)" class="hover:cursor-pointer hover:bg-blue-50">
<td x-text="user.email"></td>
<td x-text="user.voornaam"></td>
<td x-text="user.achternaam"></td>
</tr>
</template>
</table>
</template>
<!-- Geen resultaten -->
<template x-if="!users.length">
<div class="mt-10">Geen gebruikers gevonden</div>
</template>
</div>
<script>
function searchUsers() {
return {
searchfield: '', //inhoud van het zoekveld
users: [], //dit wordt gevuld met de resultaten afkomstig van api/users.search.php
ok: false, //gaat alles goed?
fetchUsers() {
axios.get('/search-users?name=' + this.searchfield)
.then((response) => {
this.ok = true;
this.users = response.data;
setTimeout(() => {
this.ok = false;
}, 5000);
}).catch((e) => {
console.log(e);
})
},
goto(id) {
//Deze url bestaat nog niet maar kan je zelf aanmaken
location.href = '/user-profile?id=' + id;
}
}
}
</script>
<?php
require "parts/footer.view.php";
Je kan nu naar http://localhost/users navigeren en uitproberen.
Door gebruik van axios en alpinejs wordt ons zoekveld dynamisch. Lees de documentatie van alpinejs om de code beter te begrijpen. Of kijk een in een video hoe het werkt
Model
Wat is een model?
Een model is een object die gerelateerd is aan een database tabel.
Voorbeeld model User (let op naam is in enkelvoud en met hoofdletter) heeft in de database de tabel 'users'
Omdat we met models object georienteerd programmeren is het mogelijk om overerving te gebruiken. Elke model moet kunnen worden aangemaakt, opgeslagen, gewijzigd etc.
Dit kunnen we één keer doen in een Model class. Deze Model class kunnen we als blauwdruk voor onze User model of Bericht model gebruiken.
Aanmaken van een Model
Het is netjes om al je Models in een directory 'models' te zetten. Dit is overzichtelijk, maar niet noodzakelijk.
Een voorbeeld van een user Model
<?php
class User extends Model
{
protected $table = "users";
}
Je ziet dat er 'extends Model' achter staat. Dat neemt alle eigenschappen van Model over.
Omdat je alle bestanden moet kunnen gebruiken. Moet je deze wel allemaal toevoegen aan je index.php
require "Model.php";
require "models/User.php";
require "models/Bericht.php";
Model voor queries
Je kan met je Model queries bouwen. Hieronder een voorbeeldje
//query op users tabel
$users = (new User())
->where('voornaam', 'LIKE', '%p%') //voornaam LIKE '%p%'
->where('role', 'user') // role = 'user'
->whereNull('tussenvoegsel') // tussenvoegsel IS NULL
->get(); //uitvoeren van de query
dd($users);
Niet alle resultaten ophalen (limit)
$users = (new User)
->limit(3)
->get();
Zal maximaal 3 users tonen.
Alle users ophalen
$users = (new User)->all();
dd($users);
User aanmaken
//aanmaken van een user
$user = (new User())->create([
'voornaam' => 'piet',
'achternaam' => 'puck',
'email' => 'testttw@mail.nl',
'password' => password_hash('password', PASSWORD_BCRYPT)
]);
dd($user);
Zoeken van een user op id
//zoeken van een user op id
$user = (new User)->find(2);
dd($user);
Gegevens van een opgehaalde user tonen
$user = (new User)->find(2);
//voornaam van de gebruiker op het scherm schrijven
echo $user->voornaam;
Gegevens van een user aanpassen
$user = (new User)->find(2);
//voornaam wijzigen
$user->voornaam = 'Piet';
//opslaan in database (alleen als er werkelijk iets gewijzigd is)
$user->save();
User verwijderen
//user met id=6 ophalen
$user = (new User)->find(6);
//de zojuist opgehaalde user verwijderen uit de database
$user->delete();
Zelf eigenschappen toevoegen
Bij een User zullen we vaak de volledige naam op het scherm willen tonen. We kunnen hier een methode toevoegen aan User.php
<?php
class User extends Model
{
protected $table = "users";
public function name(){
return $this->voornaam." ".
$this->tussenvoegsel." ".
$this->achternaam;
}
}
Nu kun je in je code een user selecteren en snel de naam op het scherm schrijven
$user = (new User)->find(4);
echo $user->name();
Debuggen
Soms wil je graag even zien welke queries er allemaal worden uitgevoerd. Dat kan door de dumpQuerLog() methode te gebruiken. Dit zal alle uitvoerde queries op het scherm schrijven en het script verder afbreken.
$user = (new User)->find(2);
//uitgevoerde queries bekijken
$user->dumpQueryLog();
De Model.php
Dit script is de 'parent' en vrij ingewikkeld qua code. Maar dat is niet erg, want je hoeft dit ook niet allemaal te snappen. Als je bovenstaande kan gebruiken weet je genoeg.
Sla onderstaande script op als Model.php en plaats in je root directory
<?php
class Model
{
//Private parameters zijn kan je alleen binnen het object gebruiken
private $query_log = [];
private $query = '';
private $bind_params = [];
private $limit = '';
private $where = [];
private $original = [];
//deze kan je overschrijven in je child class
protected $table = '';
protected $primaryKey = 'id';
//ophalen van één object dmv de primaire sleutel
public function find($id): self
{
$db = new Database();
$this->setQuery("SELECT * FROM {$this->getTable()} WHERE `{$this->primaryKey}` = ?");
$this->original = $db->query($this->query, [$id])->fetch();
if ($this->original) {
foreach ($this->original as $k => $v) {
$this->$k = $v;
}
} else {
echo "id=$id not found";
die();
}
return $this;
}
//Object opslaan in de database
public function save(): self
{
if ($this->original[$this->primaryKey]) {
$db = new Database();
$update = [];
$cols = [];
foreach ($this->original as $k => $v) {
if ($this->$k != $v) {
$cols[] = "`$k` = ?";
$update[] = $this->$k;
}
}
if (!empty($cols)) {
$update[] = $this->original[$this->primaryKey];
$this->setQuery("UPDATE {$this->getTable()} SET " .
implode(",", $cols) .
" WHERE `{$this->primaryKey}`=?"
);
$db->query($this->query, $update);
$this->find($this->original[$this->primaryKey]);
}
}
return $this;
}
//nieuw object aanmaken (en opslaan in de database)
public function create($array): self
{
$placeholders = [];
$cols = [];
$values = [];
foreach ($array as $k => $v) {
$cols[] = "`" . $k . "`";
$values[] = $v;
$placeholders[] = " ?";
}
if (!empty($cols)) {
$db = new Database();
$this->setQuery("INSERT INTO {$this->getTable()} (" . implode(",", $cols) . ") " .
"VALUES (" . implode(",", $placeholders) . ")");
$db->query($this->query, $values);
$id = $db->lastInsertId();
$this->find($id);
}
return $this;
}
//Object verwijderen
public function delete()
{
if ($this->original[$this->primaryKey]) {
$db = new Database();
$this->setQuery("DELETE FROM {$this->getTable()} WHERE `{$this->primaryKey}` = ?");
$db->query($this->query, [$this->original[$this->primaryKey]]);
$this->destruct();
}
return null;
}
//alle objecten ophalen
public function all()
{
$db = new Database();
$this->setQuery("SELECT * FROM {$this->getTable()}");
return $db->query($this->query)->fetchAll();
}
//bouwen van een query op de betreffende tabel
public function where(...$args): self
{
if (!isset($args[2])) {
$value = $args[1];
$operator = "=";
} else {
$value = $args[2];
$operator = $args[1];
}
$this->where[] = ['column' => $args[0], 'value' => $value, 'operator' => $operator];
return $this;
}
//bouwen van een query op de betreffende tabel
public function whereNull($col): self
{
$this->where[] = ['column' => $col, 'operator' => ' IS NULL'];
return $this;
}
//bouwen van een query op de betreffende tabel
public function whereNotNull($col): self
{
$this->where[] = ['column' => $col, 'operator' => ' IS NOT NULL'];
return $this;
}
public function limit(...$args): self
{
if (isset($args[1])) {
$this->limit = " LIMIT {$args[0]},{$args[1]}";
} else {
$this->limit = " LIMIT {$args[0]}";
}
return $this;
}
//Werkelijk uitvoeren van de query
public function get(): array
{
$this->buildQuery();
$db = new Database();
return $db->query($this->query, $this->bind_params)->fetchAll();
}
//inplaats van de get() kan je deze gebruiken om de querie te dumpen ipv uitvoeren
public function dumpQuery(): void
{
$this->buildQuery();
dd($this->query);
}
//alle uitgevoerde queries dumpen
public function dumpQueryLog(): void
{
dd($this->query_log);
}
//niets mee doen
protected function getTable(): string
{
return $this->table ?? rtrim(strtolower(get_class($this)), "s");
}
//om een query log op te bouwen (wordt intern gebruikt)
private function setQuery($query)
{
$this->query = $query;
$this->query_log[] = $query;
}
//bouwen van een query aan de hand van where
private function buildQuery(): void
{
$where = [];
$this->bind_params = [];
foreach ($this->where as $v) {
$where[] = "`{$v['column']}` {$v['operator']}" . (isset($v['value']) ? ' ?' : '');
if ($v['value'] ?? null) {
$this->bind_params[] = $v['value'];
}
}
$this->setQuery("SELECT * FROM {$this->getTable()} " .
(!empty($where) ? ' WHERE ' . implode(" AND ", $where) : '') .
$this->limit
);
}
//object leegmaken
private function destruct(): void
{
foreach ($this->original as $k => $v) {
unset($this->$k);
}
$this->original = [];
}
}
Models en relaties
Een User heeft verzonden berichten en ontvangen berichten. We kunnen ons User model uitbreiden om deze berichten snel op te kunnen halen. Onderstaande methode doen al het werk
public function verzonden()
{
return (new Bericht)->where('van_id', $this->id)->get();
}
public function ontvangen()
{
return (new Bericht)->where('aan_id', $this->id)->get();
}
Uiteraard moet je wel een Model Bericht aanmaken.
Alle verzonden berichten van gebruiker 2 ophalen is nu heel eenvoudig
$user = (new User)->find(2);
dd($user->verzonden());
Router
Ons router.php file is redelijk onoverzichtelijk. Hieronder een mogelijkheden om het op te schonen.
Maak een functie in functions.php
function route($route, $endpoint, $toegang = true)
{
$uri = trim(parse_url($_SERVER['REQUEST_URI'])['path'], "/");
if ($uri != $route) {
return;
}
if (!$toegang) {
http_response_code(403);
require "view/403.view.php";
return;
}
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/" . $endpoint)) {
require $endpoint;
die(); //niet verder zoeken onze route is gevonden
return;
}
http_response_code(404);
require "view/404.view.php";
return;
}
Uitleg code:
- Kijk of de route de huidige route is
Ja -> Ga door
Nee -> Stop met uitvoeren (dit is niet de gevraagde route)
- Is er toegang
Ja -> ga door
Nee -> 403 reponse (geen toegang)
- Bestaat het bestand (controller of view)
Ja -> require ...
Nee -> 404 reponse (pagina niet gevonden)
Omdat we in het begin alle / verwijderen van de route hoeft deze ook niet meer vergelijken te worden.
Onze route.php krijgt dan de inhoud
<?php
//ROUTER
// Hier doen we een controle of een bepaalde URL bestaat en we verwijzen door naar een controller of een view
route("index", "controllers/home.php");
route("home", "controllers/home.php");
route("", "controllers/home.php");
route("contact", "controllers/contact.php");
route("about", "controllers/about.php");
//Alleen als je ingelogd bent
route('berichten', "controllers/bericht.index.php", isLogin());
route('lees-bericht', "controllers/bericht.read.php", isLogin());
route('schrijf-bericht', "controllers/bericht.create.php", isLogin());
route('wijzig-bericht', "controllers/bericht.update.php", isLogin());
route('verwijder-bericht', "controllers/bericht.delete.php", isLogin());
route('login', 'controllers/login.php');
//alleen toegankelijk als administrator
route('users', 'view/users.zoeken.view.php', hasRole('admin'));
route('search-users', "api/users.search.php" . hasRole('admin'));
route('inactief', "controller/dummy.php", false);
//niets gevonden dan...
http_response_code(404);
require "view/404.view.php";
die();
Door in de route als derde parameter een boolean mee te geven kan je een route ook tijdelijk inactief maken.
route('inactief',"controller/dummy.php",false);