De uitwerkingen van de cursus zijn op een github pagina te vinden
De hoofdstukken/paragrafen met een * zijn vooral informatief. Het is zeker niet noodzakelijk daarbij de opdrachten te maken. Wel wordt aangeraden dit te lezen.
USBwebserver is een software bundle. Het bevat Apache webserver, PHP en een MySQL database.
Start USBwebserver
LET OP. De downloads zijn .zip bestanden. Deze moeten eerst uitgepakt worden. Doe dit in documenten of in je OneDrive (zet dan wel de optie aan "altijd bewaren op dit apparaat")
Wanneer je USBwebserver hebt opgestart krijg je onderstaand scherm
Als het goed is met twee groene vinkjes.
Met Apache maak je van je computer een webserver die standaard toegankelijk is via http://localhost
Geen groene vinkjes
Misschien is het port nr al in gebruik. Pas aan in Settings.
OneDrive bestanden wel gesynchroniseerd?
Probeer je USBwebserver vanuit een ZIP bestand uit te voeren? (werkt niet)
Zijn alle mappen van USBwebserver wel aanwezig
Settings
Eventueel kan je in de settings het portnr van de webserver wijzigen. Default is dat port 80. Als je meerdere webservers wilt gebruiken (of als port 80 al ergens anders voor wordt gebruikt) dan kan je de port 8000 t/m 8080 daarvoor gebruiken. Je kan je website dan bereiken dmv http://localhost:8000 (bij gebruik port 8000). Hetzelfde geldt voor de MySQL port, standaard 3306 maar port 3307 kan je ook gebruiken bij conflicten
Configuratie aanpassingen van php (indien nodig)
Open in de map "php" het bestand php.ini
Zoek naar "extension=pdo_mysql" en zorg dat daarvoor geen ; komt te staan (; betekend wordt niet uitgevoerd)
Zoek naar date.timezone zorg dat daar komt te staan
date.timezone = Europe/Amsterdam
Test werking
Open een browser en ga naar http://localhost. Als het goed is krijg je de webpagina te zien die in je "root" mapje staat van usbwebserver.
XAMPP
MAC OS alternatief
Wanneer je gebruik maakt van MacBook kan je de volgende software gebruiken.
Als alternatief voor USBwebserver kan je gebruik maken van Mamp
Bij USBwebserver is de webroot 'root', bij het gebruik van Mamp is de webroot 'htdocs'
Standaard staat de webpoort op 8888, dit kan je bij instellingen aanpassen naar 80.
Apache server
Let op om het project te laten werken moeten er een paar instellingen worden aangepast in het bestand httpd.conf
In httpd.conf on /Applications/MAMP/conf/apache, find:
VScode is ook voor MacOS dus hier kan je gewoon gebruik van maken.
C Applicaties
VScode
Visual Studio code
Dit is een mogelijke tekst editor die je kunt gebruiken om code te schrijven. VScode biedt ondersteuning (extensies) voor zeer veel programmeertalen. Het is een gratis programma en biedt zeer veel mogelijkheden.
Het is aanbevollen om onderstaande extensies daarna te installeren
Intelephense
Tailwind CSS IntelliSense
Format HTML in PHP
Live Share (zie onderaan)
PHP
Configuratie aanpassen
Bij VScode is de editor volledig te configureren. Het is handig om onderstaande parameters te wijzigen. Ga naar file->preferences->settings (ctrl + ,)
Zoek naar "auto save" wijzig deze in "onWindowChange"
Zoek naar "format" vink "Format On Save" aan
Zoek naar "HTML-CSS-class-completion" vink "Enables completion when ..." aan
Working director
Kies open folder
Kies de root directory van USBwebserver
VScode is er nu klaar voor
Live Share
Om samen te werken kan het handig zijn om de extensie Live Share te installeren.
Na het installeren kan een teamgenoot Live Share aanzetten. (Let op hiervoor moet je wel inloggen met een Microsoft account of Github account. Als Microsoft account kan je je school email gebruiken).
Live Share Server (dus diegene die deelt met andere)
Klik links onder op Live Share
Er wordt nu een link aangemaakt op je Clipboard die directe te plakken is in een Teams chat naar je maatje waarmee je samenwerkt.
Live Share Client (dus diegene die mee mag doen)
Klik op
Daarna op
Plak de URL uit de Teamschat in de balk en de samenwerking kan beginnen
PHPStorm
PHPstorm kan je als student gratis gebruiken. Je kunt het hier downloaden.
Standaard is het een 30 dagen gratis probeer versie. Maar als je je registreert met je school email dan kan je het gratis gebruiken (studenten licence).
PHPstorm is zeer uitgebreid en maken het leven van een programmeur echt een stuk makkelijker. Wel een paar nadelen.
PC gebonden
Niet bruikbaar tijdens de toets
Heidi-SQL
Heidi-SQL is een gratis database mangement tool voor Windows. Heidi-SQL maakt het voor ons mogelijk om op een visuele manier onze database te beheren..
Download Heidi-SQL portable 64 bit. Pak het zip bestand uit en zet het in OneDrive of in je Document map. (Pak alle bestanden uit en niet alleen heidi.exe, zonder de overige bestanden werkt het niet).
Interface voor database
Let op Heidi is alleen een tool om met de database te werken. Zonder database is Heidi niet bruikbaar. Dus als je met Heidi gaat werken moet er een database opgestart zijn (bijvoorbeeld die van je USBwebserver)
Eerste keer
Bij de eerste keer dat je Heid-SQL gebruikt moet je de verbinding instellen. Dit is maar eenmalig nodig. Daarna kan je de verbinding herbruiken. Open Heidi-SQL en klik op "nieuw".
Onderstaand scherm zal verschijnen.
laat default of wijzig in "localhost"
gebruiker: root wachtwoord: usbw (bij gebruik usbwebserver)
Klik op opslaan
Klik op openen
Heidi-SQL zal openen en je krijgt een overzicht van al je database te zien.
PHPMyAdmin
Bij USBwebserver is PHPMyAdmin ingebouwd. PHPMyAdmin is een web database mangement tool. Een website waarmee je de database kunt beheren. Via USBwebserver te open door http://localhost/phpmyadmin.
D Eerste opzet
Inleiding
Bij deze cursus ga je leren een website te maken voor de programmeerclub 'code wizards'.
De website zal klein beginnen, maar zal in de loop van de cursus steeds meer uitbreidingen krijgen. Uiteraard kan je alle kennis die je bij het maken van deze website opdoet, gebruiken voor het maken van andere websites.
Aan het eind van paragraaf D ziet je website er ongeveer zo uit
Opdracht D1 home-about-contact
We gaan starten met een eenvoudige website die bestaat uit drie pagina's
Home
About
Contact
Op de home pagina komt een korte inleiding van de 'code wizards'
Op de about pagina komt het informatie over het ontstaan van de club en de doelen die worden nagestreefd.
Op de contact pagina komt informatie om contact op te nemen met de vereniging.
Om deze 3 pagina's te maken starten we met het maken van drie bestanden
index.php
about.php
contact.php
Hierbij is index.php onze home pagina. (index.php zal altijd als eerst worden geladen als er geen verzoek is voor een andere pagina)
Alle pagina's hebben als bestandsextensie .php daarmee kunnen ze HTML code maar ook PHP code bevatten en uitvoeren.
Maak de drie bestanden aan in de webroot van je webserver. Dit is voor USBwebserver 'root' en voor XAMPP 'htdocs'. Let op de bestanden mogen nog leeg zijn. Bij de volgende opdracht gaan we inhoud toevoegen.
Opdracht D2 home-about-contact invullen
We hebben nu drie lege pagina's het wordt tijd om deze wat inhoud te geven.
In een eerdere paragraaf hebben we de VScode extensie Emmet geïnstalleerd. Hiermee kunnen we snel HTML code genereren.
Index.php
We starten met de index.php
Type ! gevolgd door de <tab> toets. Dit zal de volgende code aanmaken.
We passen de <title> aan naar home (dit is de titel van de pagina die op het tabblad van de webbrowser wordt getoond.) In de body komt de inhoud van de pagina.
Daar maken we een met een h1 tag de zichtbare titel van de pagina met daaronder een tekst.
De tekst komt binnen een p tag.
Bijvoorbeeld zoiets als hieronder
<!doctype html>
<html lang="en">
<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>Document</title>
</head>
<body>
<h1>Home</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab accusamus aspernatur cupiditate delectus deleniti dignissimos dolor, dolore fuga fugiat magni maiores minima mollitia nihil obcaecati quia quidem recusandae repellendus totam? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam exercitationem iusto molestias nemo nostrum perspiciatis quas quia quibusdam veritatis? At debitis ducimus eos molestiae mollitia numquam pariatur praesentium quas vitae!</p>
</body>
</html>
Om dit snel zelf aan te maken type je h1<tab> dit geeft <h1></h1>
Voor de <p></p> gebruik je p<tab> en voor de lorem tekst lorem<tab>
About.php en contact.php
Geef de pagina's about en contact ook een vergelijkbare inhoud. We vullen deze later op met wat zinvollere teksten en opmaak.
Om het menu op onze drie pagina's te krijgen hebben we onszelf drie keer moeten herhalen. Dit is niet wenselijk. Ten eerste is het saai om op elke pagina het menu te kopieëren. Daarnaast is het onhandig. Stel dat onze website er een nieuwe pagina bij krijgt. Dan moeten we dit op elke pagina gaan aanpassen.
We willen daarom zo min mogelijk herhaalde code in onze applicatie. Omdat we PHP tot onze beschikking hebben is dit eenvoudig op te lossen.
We kunnen hiervoor een nieuw bestand aanmaken met bijvoorbeeld de naam navigatie-menu.php.
In het bestand zetten we de code van ons menu.
Op onze drie pagina's vervangen we het HTML menu door een stukje PHP code
<?php
require "navigatie-menu.php";
?>
Het PHP require commando roept het bestand navigatie-menu.php aan en voert de code uit. In dit geval is dat alleen HTML code. Waar de require staat komt dus gewoon de HTML code van het menu te staan.
Je ziet dat het require commando tussen <?php en ?> komt te staan. Op deze manier wordt in onze PHP code aangegeven dat we PHP gaan gebruiken.
Het voordeel is dat we nu ons menu op één plaats kunnen aanpassen. De bezoeker van de website ziet echter nog geen verschil.
Opdracht D4 navigatie-menu.php toevoegen
Maak het bestand navigatie-menu.php aan en vervang het menu in index.php, about.php en contact.php voor de require.
Het enige wat verschillend is dat is de inhoud van het title-element. Maar hier is wel een goede oplossing voor.
We kunnen het title-element vervangen voor
<title><?= $title ?></title>
dit is de snelle notatie van
<title><?php echo $title; ?></title>
In de title-tag wordt PHP code gestart. Het echo commando of de = in het eerste voorbeeld geeft aan dat er op het scherm geschreven moet worden. In dit geval moet de inhoud van variabele $title op het scherm worden geschreven.
In PHP beginnen variabele altijd met een $ teken.
Stel $title="Home" Dan zal tussen de title-tag Home worden geschreven. Dus <title>Home</title>
Opdracht D5 toevoegen van header.php
Header.php aanmaken
We gaan nu een bestand header.php toevoegen aan onze webapplicatie. In dit bestand komt alle code van <!doctype tot en met <body>
Het title-element vervangen we voor <title><?= $title ?></title>
Onze header.php is nu klaar. Nu moeten we dit nog toevoegen aan onze drie pagina's.
Plaats op alle drie de pagina's (index.php, about.php, contact.php)
Als we nu onze pagina bron bekijken (bv dmv F12 kiezen voor tabblad inspector) als we dan de head tag open klappen en de title tag dan zien we daar een foutmelding <b>Warning</b>: Undefined variable $title in <b...
PHP variabel aanmaken
We hebben in header.php een $title gebruikt. Maar deze bestaat nog niet bij het uitvoeren van de code. Om de foutmelding nu even snel op te lossen voegen we aan onze drie pagina's de volgende code toe:
Uiteraard krijgt de $title in elke pagina een andere tekst.
Je ziet dat elk PHP commando wordt afgesloten met een punt-komma ; En dat de tekst tussen quotes komt te staan. Dit mogen enkele of dubbele quotes zijn. Enkele en dubbele quotes hebben een andere betekennis, maar daar komen we later op terug.
En webpagina moet natuurlijk ook een klein beetje prettig ogen. Hiervoor wordt CSS gebruikt. Uiteraard kunnen we deze CSS code zelf schrijven. In deze cursus maken we echter gebruik van een CSS framework die bijna alle CSS code alvast voor ons heeft geschreven. Toepassen is het enige wat we nog moeten doen. Er zijn veel verschillende CSS frameworks. Bijvoorbeeld
Wij zullen in deze cursus tailwindcss gebruiken. Dit is niet altijd even eenvoudig, maar de documentatie is goed. Er zijn voorbeelden en bijna alles is ermee mogelijk.
In de volgende opdracht gaan we ons menu wat mooier maken en onze h1 wat opmaak geven
Opdracht D7 Tailwindcss opmaak
Tailwindcss CDN
We gaan op onze site gebruik maken van tailwindcss. Hiervoor moeten we in onze header.php tailwindcss importeren. Omdat we geen gebruik gaan maken van CLI commando's gebruiken wij een CDN. (zie tailwindcss documentatie)
Voeg toe in je header.php binnen de header tag <head> </head>
Als je de tailwind CSS IntelliSense extensie hebt geïnstalleerd kun je een bestandje met de naam tailwind.config.js aanmaken in je root. Daarna worden tailwindcss classes automatisch aangevuld in je editor.
In het menu wordt een logo gebruikt. Je kan het logo hier downloaden. Zet je logo in een map images. (of gebruik je eigen logo). Het menu maakt gebruik van flexbox. In de docs van tailwindcss kan je alle opties bekijken.
Later in de cursus zullen we dit menu nog aanpassen, zodat het ook bruikbaar is op kleinere beeldschermen.
Titel tekst
Doordat we tailwindcss zijn gaan gebruiken is alle opmaak van elke HTML tag verdwenen. Laten we de pagina titel een klein beetje opmaak geven.
<h1 class="text-3xl my-4">About</h1>
Grote letters en wat margin onder en boven.
Omdat de tekst wel erg dicht tegen de rand aan staat kan je ook de h1 en de p-tag binnen een div tag plaatsen en deze een margin geven
<div class="sm:mx-10">
<h1 class="text-3xl my-4">About</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Autem commodi consequuntur cumque dolor eligendi,
et
magni
minus nobis numquam quia, quisquam quo repudiandae unde. Numquam odit quae recusandae rerum vel! Lorem ipsum
dolor
sit amet, consectetur adipisicing elit. At aut eum facere iusto, laborum magni neque nesciunt quibusdam
quis,
similique suscipit tenetur voluptate? Error esse ipsa, iste rem vel velit.</p>
</div>
sm:mx-10 betekend vanaf een scherm breder dan 640px dan wordt er een stukje ruimte (margin) aan de linker en rechterkant van het scherm toegevoegd.
Als we nu een pagina van onze webapplicatie bezoeken komt er boven in de balk bv te staan http://localhost/home.php
Dit zorgt ervoor dat het php bestand 'home.php' wordt uitgevoerd.
Dit werkt prima, maar heeft ook wat beperkingen, zoals
In de URL is meteen te zien dat je PHP gebruikt
De URL is niet zo goed leesbaar
Kan moeilijk code uitvoeren die je op elke pagina zou willen uitvoeren (bv configuratie, wachtwoorden, etc)
Een oplossing hiervoor is het toevoegen van een 'router'. Eén bestand waar alle webverzoeken op binnenkomen.In dat bestand kunnen we veel gebruikte functies en configuratie bestanden inladen. En daarna het verzoek doorverwijzen naar de betreffende pagina.
Router
Om dit voor elkaar te krijgen gaan we een router maken. Hiervoor moeten we eerst wat aanpassen in onze configuratie.
De router gaat een web request: localhost/home uitlezen en ons doorsturen naar localhost/home.php. Of naar elke andere gewenste pagina.
Omdat je deze code maar één keer maakt is het niet zo belangrijk om dit zelf te kunnen. (copy-paste) is genoeg.
In PHP zitten een groot aantal voorgedefinieerde variabele. Eén daarvan, $_SERVER, deze hebben we nodig. Let op onderstaande code is best ingewikkeld. Als je het niet helemaal begrijpt geen zorgen. Copy-paste is genoeg.
index.php
<pre>
<?php
var_dump($_SERVER);
die();
?>
</pre>
De var_dump() funtie zal de inhoud van een variabele op het scherm schrijven. Als deze klaar is wordt de die() functie aangeroepen en zal verdere uitvoer van het script stoppen.
Als we weer naar http://localhost gaan is alles nu wat beter leesbaar.
request_uri
Wij zijn voor onze router geïntresseerd in $_SERVER['REQUEST_URI'] in deze variabele is te zien welke url wordt opgevraagd.
Typ maar eens achter http://localhost/home
["REQUEST_URI"]=>
string(17) "/home"
Of bij http://localhost/home?id=20
["REQUEST_URI"]=>
string(23) "/home?id=20"
Wij gaan nu deze variabele gebruiken om onze router te maken.
Als eerst moeten we de query requests zoals id=20 van de url verwijderen. Daarvoor kunnen we gebruik maken van de functie parse_url(). De functie geeft een array terug
Bij http://localhost/home?id=20 wordt $uri => "home"
Bij http://localhost/producten/nieuw wordt $uri => "producten/nieuw"
Bij http://localhost/contact.php zal het index bestand niet worden aanroepen maar het bestand contact.php
Opdracht E2 doorsturen
We gaan nu de voorgaande theorie toepassen. In index.php halen we de uri op en zetten dit in $uri .
Daarna kunnen we kijken. Als de $uri gelijk is aan "about", dan willen we about.php inladen.
if($uri == "about"){
require "about.php";
}
Om deze vergelijking te maken gebruiken we if( ... ). Let op de dubbele == in de vergelijking. Een dubbele == betekend is het ene gelijk aan het andere. Een enkele = zal de waarde van $uri wijzigen in "about". Deze wijziging is altijd waar.
Maar we hebben drie pagina's dus moeten we dit steeds herhalen. Omdat we eerst onze home pagina willen laten zien mag dit ook met een lege $uri.
if ($uri == "") {
require "home.php";
}
if ($uri == "contact") {
require "contact.php";
}
if ($uri == "about") {
require "about.php";
}
Als de $uri overeenkomt met "contact" dan mag er eigenlijk wel gestopt worden met uitvoeren. Dit kan door die() of exit()
if ($uri == "") {
require "home.php";
die();
}
if ($uri == "home") { require "home.php"; die();
}
if ($uri == "contact") {
require "contact.php";
die();
}
if ($uri == "about") {
require "about.php";
die();
}
Omdat je heel veel keer een if( ...) krijgt kan je ook gebruik maken van een PHP switch statement. Dit doet hetzelfde maar is iets korter
switch ($uri){
case "":
case "home":
require "home.php";
break;
case "contact":
require "contact.php";
break;
case "about":
require "about.php";
break;
default:
require "404.php"; //pagina niet gevonden
break;
}
Je zou een default waarde kunnen opgeven als de $uri aan geen van de opties voldoet.
Maak een keuze van één van de twee opties en plaats in je index.php
Navigatiemenu aanpassen
Door onze router doen onze urls van het menu het niet meer. Dit moeten we aanpassen.
Let op de link wordt nu bijvoorbeeld home of / i.p.v. home.php
Een configuratie bestand maakt het makkelijk om variabele van een website op één plaats aan te maken en daarna overal te kunnen gebruiken. Mocht je een variabele moeten wijzigen dan hoeft dat ook maar op één plaats.
Onze Code Wizards club heeft een email-adres (fake) info@code-wizards.nl
We kunnen dit mooi in ons configuratie bestand zetten.
Onze config.php doet een return dat betekend geef de inhou terug. In ons geval is de inhoud een Array met variabele. Deze Array gaan we in de loop van de cursus nog verder vullen.
Toevoegen aan index.php
Om ons configuratie bestand overal te kunnen gebruiken moeten we deze inladen in ons index.php bestand.
<?php
//inladen van de configuratie parameters
$config = require "config.php";
Daaronder komt onze router
Je kan nu $config['app']['email'] gebruiken voor het email adres van onze website en $config['app']['name'] voor de naam van onze website.
Opdracht E4 pagina bestaat niet
Wanneer we een url in bezoeken die niet bestaat krijgen we momenteel niets te zien (lege pagina)
Het is natuurlijk mooier als we iets krijgen als "pagina niet gevonden"
Dit kunnen we eenvoudig doen door een nieuwe pagina te maken met de naam 404.php. 404 staat voor een status code pagina niet gevonden. We kunnen ook aan de browser melden dat deze pagina niet is gevonden door
Voeg dit toe aan index.php en maak een bestand 404.php met een tekst: "pagina niet gevonden".
Opdracht E5 router.php toevoegen
Wanneer onze webapplicatie groter wordt en er veel routes komen wordt index.php onleesbaar. Het daarom handig om onze routes onder te brengen in een appart bestand router.php
Stap 1
Maak een nieuw bestand router.php
Stap 2
Kopieër alles van index.php wat met de router te maken heeft naar router.php.
Stap 3
Verwijder de router code uit index.php en vervang dit door een een require "router.php";
F Scheiding logica en opmaak *
MVC model
Als een applicatie groter wordt is het belangrijk dat de structuur overzichtelijk blijft. Vaak wordt met meerdere mensen aan een applicatie gewerkt. Duidelijke afspraken worden dan zeer belangrijk. Waar is welke code te vinden.
Een veel gebruikte opzet is het MVC - model (Model - View - Controller)
Model
Staat voor de informatie waarmee de applicatie werkt. Naast de informatie kan een Model ook enige logica bevatten. De werkelijke opslag zal meestal in een database gebeuren. Deze valt buiten de applicatie. Met een model zal worden gestreeft naar zo min mogelijk strikte koppeling tussen de database en de applicatie. De hoofdtaak van de Model is het ophalen en wegschrijven van data.
View
Een view wordt enkel gebruikt om informatie te tonen. Dus geen bewerkingen, berekeningen of iets dergelijks. In onze applicatie komt dat overeen met voornamelijk HTML code.
Controller
De controller reageert op gebeurtenissen (events). Meestal worden deze getriggerd door de gebruiker. In de controller kan logica worden opgenomen.
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
Opdracht F1 Code re-organiseren
Mappen structuur
De applicatie wordt groter en groter op een gegeven moment hebben we meer dan 100 .php bestanden. Het terugvinden van een bestand wordt dan steeds moeilijker. Het is handig om de bestanden te organiseren in mappen.
Hiervoor maken we de volgende mappen structuur aan (images had je al)
root
app
controllers
models
views
parts
src
views
webroot
images
In de "webroot" komen bestanden te staan die direct voor de hele wereld toegankelijk zijn.
In de "src" directory komen bestanden te staan die in elke website gebruikt zouden kunnen worden.
In de "app" directory komen de bestanden te staan die specifiek voor de betreffende webapplicatie zijn.
Doe dit ook voor jou project OF download van GitHub (bespaart veel tijd)
Als je kiest voor de snelle weg Download van GitHub moet je alle bestanden uit opdracht_F1 (uit gedownload zip bestand) kopieren naar je root folder van USBwebserver. Alle bestanden die je al had kan je verwijderen. Wijzig het path in settings van USBwebserver naar {path}/root/webroot en dat is alles. Lees onderstaande kort door zodat je wel weet wat er is gebeurt.
Naamgeving views
We gaan nu views en controllers aanmaken. Om voor ons zelf te weten dat we met een view aan het werk zijn, is het prettig om in de bestandsnaam view op te nemen. Bijvoorbeeld home.view.php.
Omdat we dit bij de views doen is het niet meer nodig voor de controllers.
Views maken
In de map views maken we een nieuw mapje parts. Hier in komen de footer.php, header.php en navigatie-menu.php. Na het plaatsen in deze map wijzig je de namen naar footer.view.php, header.view.php en navigatie-menu.view.php.
Deze onderdelen zullen we op bijna elke pagina gebruiken. Het is daarom handig om deze op een centrale makkelijk te vindbare plek neer te zetten. (parts)
Verplaats home.php, about.php en contact.php naar views. En hernoem de bestanden met een view.php. Maak ook 3 lege bestanden in controllers (about.php, contact.php en home.php)
Als het goed is ziet je directory structuur er dan uit zoals hieronder.
Uiteraard werkt onze pagina niet meer door alle verplaatsingen.
Wijzig in router.php de require naar het juiste pad
if ($uri == "") {
require "controllers/home.php";
die();
}
if ($uri == "home") {
require "controllers/home.php";
die();
}
if ($uri == "contact") {
require "controllers/contact.php";
die();
}
if ($uri == "about") {
require "controllers/about.php";
die();
}
http_response_code(404);
require "views/404.view.php";
die();
In onze controllers zetten we de $title en doen een require van de view. Voorbeeld voor controller/home.php
De betekenis van ../ betekend een directory terug. Dus in dit geval vanuit webroot twee directories terug. En daarna volgen we het path app => views => home.view.php
Later in de cursus zullen we dit enorm vereenvoudigen.
home.view.php
<?php
require __DIR__ . "/../../app/views/parts/header.view.php";require __DIR__ . "/../../app/views/parts/navigatie-menu.view.php";?><div class="sm:mx-10">
<h1 class="text-3xl my-4">Home</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab accusamus aspernatur cupiditate delectus
deleniti
dignissimos dolor, dolore fuga fugiat magni maiores minima mollitia nihil obcaecati quia quidem recusandae
repellendus totam? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam exercitationem iusto
molestias
nemo nostrum perspiciatis quas quia quibusdam veritatis? At debitis ducimus eos molestiae mollitia numquam
pariatur
praesentium quas vitae!</p>
</div>
<?php
require __DIR__ . "/../../app/views/parts/footer.view.php";
Let op we verwijderen de $title uit home.view.php en passen de paden aan bij de require.
Doe hetzelfde voor about.php en contact.php en de bijhorende views.
Belangrijk
!! Om alles te laten werken is het belangrijk dat we bij de settings van USBWebserver het path aanpassen naar {path}/root/webroot !!
G Functies *
PHP data typen
In voorgaande theorie en opdrachten heb je al een beetje kennis gemaakt met variabele. Variabele zijn van een bepaald type. PHP is niet heel strikt met types van variabele. Onderstaande types komen vaak voor
String
String staat voor een tekst. $naam = "Pietje Puck";
Bij een string staan er altijd quotes " om heen.
Boolean
Een boolean kan twee verschillende waardes bevatten: true / false
$error = true;
Integer
Een integer staat voor een geheel getal. Bij de definitie zijn quotes niet nodig
$getal = 20;
Array
Een array is een rij met gegevens. Daarvan zouden de gegevens ook weer een rij van gegevens kunnen zijn.
$dagen = ['ma'=>'maandag','di'=>'dinsdag','wo'=>'woensdag','do'=>'donderdag','vr'=>'vrijdag']; // key => value
Hierboven is een array aangemaakt met dagen van de werkweek. De key is hierbij de afkorting en de value is de bijhorende dag. Een specifieke dag kan op het scherm worden geschreven dmv
echo $dagen['di'];
Tussen de blokhaken staat de key (di), het resultaat is de value (dinsdag)
PHP functie
In voorgaande opdrachten hebben we een aantal php functies gezien zoals url_parse(), trim() en var_dump(). PHP heeft zeer veel ingebouwde functies. Deze kan je vinden in de documentatie bij php.net
Zelf kan je ook functies maken. Dit is vooral handig als je iets vaak gaat gebruiken.
In de opdrachten van deze paragraaf gaan we een aantal functies maken. Deze functies kunnen we straks overal in ons project gebruiken.
De standaard functie definitie ziet er alsvolgt uit
function functie_naam(){
//doe iets
}
De aanroep van de functie gaat zo
functie_naam();
We kunnen ook parameters aan een functie meegeven
Hieronder een voorbeeld van een functie die twee getallen kan optellen
De if - loop hebben we al even gezien bij onze router.php
if ($uri == "contact") {
require "controllers/contact.php";
die();
}
Er kunnen bij de if ook meerdere voorwaardes worden opgenomen
if ($uri == "" or $uri == "home") {
require "controllers/home.php";
die();
}
Als de $uri gelijk is aan "" of aan "home" dan uitvoeren.
$getal1=10;
$getal2=2;
$getal3=5;
if($getal1 > $getal2 and $getal1 > $getal3 ){
echo "$getal1 is het grootste";
};
Bij bovenstaande code wordt gekeken of getal1 groter is dan getal2 en getal1 groter is dan getal3.
Ook kan je een else of elseif gebruiken
<?php
$getal = 1;
if($getal ==2){
echo "Het getal is 2";
}elseif($getal == 3){
echo "Het getal is 3";
}else{
echo "Het is een ander getal";
}
Opdracht G1 isUri(...)
In ons menu is niet te zien op welke pagina we op dit moment zijn. We gaan dit wijzigen. Aan het eind van deze opdracht zal er een streep staan onder de huidige pagina.
De eerste functie die we gaan toevoegen heet isUri(...)
Deze functie kijkt of de huidige uri hetzelfde is als een gegeven uri. De functie aanroep doen we in het navigatie-menu.view.php
Dit is een inline if-statement. Het werkt bijna hetzelfde als een normale vraag
isUri("contact")?
De eerste waarde is bij true, na de dubbele punt komt de false waarde
Dus bij true wordt er op het scherm geschreven underline
Bij false wordt er een lege string (niets) op het scherm geschreven. Let op de spatie achter 'underline' als deze er niet zou staan dan wordt het resultaat underlinetext-gray-300 en dan werkt de opmaak niet.
Nu moet de functie nog worden gemaakt
functions.php
Maak een bestand /src/functions.php en voeg aan index.php een require toe zodat functies worden ingeladen. Doe dit tussen de require van config.php en router.php.
In onze router.php hadden we al een stukje code die bij een REQUEST_URI de uri uitleest. Dus indien het verzoek http://localhost/contact is wordt er contact van gemaakt.
Regelmatig zullen we moeten kijken wat er mis gaat. Vaak moeten we dan weten wat er in een bepaalde variabele zit. Handig is het als we hiervoor onze eigen 'die and dump' functie hebben.
We maken een nieuwe function dd(...)
function dd(...$args)
{
echo "<pre>";
foreach ($args as $arg) {
var_dump($arg);
}
echo "</pre>";
die();
}
We doen een echo van <pre> om de output goed leesbaar te krijgen. Alle input argumenten doorlopen we en dumpen we op het scherm.
Daarna sluiten we de </pre>
En stoppen verder executie.
Voeg de functie toe aan functions.php
Je kan deze functie alsvolgt gebruiken
dd($variabele);
En ook uiteraard met meerdere variabele
dd($variabele, $variabele2);
Opdracht G3 config(...)
Als we nu een configuratie variabele willen gebruiken kan dit met
$config['app']['name']
Dit werkt niet echt lekker. Al die haakjes en quotes maken het onoverzichtelijk. Daarnaast zijn deze variabele ook niet te gebruiken in objecten (komt later in de cursus)
Zou het niet mooi zijn als we de naam van onze app met onderstaande code op het scherm kunnen schrijven
<?= config('app.name') ?>
Met onderstaande functie gaat dat lukken
function config(string $param): string
{
global $config;
$path_items = explode(".", $param);
$result = $config;
foreach ($path_items as $item) {
if (isset($result[$item])) {
$result = $result[$item];
} else {
return ''; //gezochte item bestaat niet
}
}
return $result;
}
Deze functie is nu nog vrij complex, kopieër de code in functions.php. Zorg dat je de functie kan toepassen. Begrijpen is leuk, maar niet noodzakelijk.
In ons menu kunnen we nu de tekst van onze club uit de variabele halen.
view(string $view) deze functie zorgt voor de require van de juiste view. Een nadeel, maar misschien ook een voordeel hiervan is dat wanneer de require in een functie staat dat de gedefinieerde variabele niet worden meegenomen.
Dus $title die we in onze controller hebben aangemaakt. Kan dan niet meer in de view gebruikt worden. Uiteraard zijn daar mooie oplossingen voor. We kunnen aan onze view functie meerdere parameters meegeven
view(string $view, array $variabele);
Voorbeeld:
Als we onze views/parts/header.view.php willen aanroepen met een title kan dat op deze manier
view("parts/header",[
'title'=>'Contact'
]);
Uiteraard kunnen we ook een variabele meesturen
view("parts/header", [
'title' => $title
]);
Als we geen parameters mee willen geven kan dat ook
view('parts/navigatie-menu');
view functie
De functie view kan je zo ingewikkeld maken als je zelf wilt. Kopieër onderstaande code naar functions.php
Hierboven is denk ik een redelijk bruikbare functie. Let op dat je de inhoud niet hoeft te begrijpen. Functie extract($vars) zet overigens de array $vars om in losse variabele.
De globale variabele __DIR__ is de webroot directory. Indien een view niet bestaat zal er een foutmelding volgen.
Aanpassen require "...view.php"
Pas alle requires van views aan in je controllers en views. En verwijder in je controllers meteen $titel. En geef deze direct mee in de view.
Dus
view("parts/header", ['title' => 'about']);
H Database aanmaken
Database mangement tools
Met Heidi-SQL kunnen we redelijk makkelijk een database aanmaken. Uiteraard kan dit ook met SQL-queries. Maar omdat je dit niet dagelijks doet maakt een visuele omgeving het leven een stuk makkelijker.
Maak een database genaamd 'code_wizards'. Dit kan door met de rechtermuis op de databaseserver te klikken (localhost).
En kies de collatie zoals hierboven.
Tabel users en posts
We gaan nu twee tabellen aanmaken Users en Posts. Een tabel bevat records, dit zijn er altijd meer dus is het netjes om de tabelnaam in meervoud te schrijven. Ik zelf doe dit graag in het Engels, maar uiteraard kan het ook gewoon in het Nederlands. Let op, dat verder in de cursus de Engelse tabelnamen worden gebruikt. Indien je voor Nederlands kiest moet jij dus wat dingen anders doen.
Maak onderstaande tabel Users:
De onderste 3 timestamps zijn niet persé noodzakelijk. Maar daarmee is wel snel terug te zien wanneer een gebruiker is aangemaakt, gewijzigd is of is verwijderd. Dus handig om toe te voegen.
Email is een uniek veld, dit zal later worden gebruikt om in te loggen (samen met het wachtwoord).
id is de primaire sleutel van de tabel en moet op auto_increament komen te staan.
Als je zelf nog meerdere velden wilt toevoegen mag dat, maar zet dan bij deze velden wel het vinkje 'null toestaan' aan. Anders kan je straks de automatische vulling niet gebruiken.
en maak de tabel Posts
Een post wordt door een user gedaan. Vandaar de verwijzende sleutel 'user_id'. Verder heeft een 'post' een titel en content. Omdat de content best groot kan zijn is er gekozen voor MEDIUMTEXT.
Vul de database met onderstaande fake data (copy paste in heidi-sql en voer uit)
USE code_wizards;
INSERT INTO `users` (`id`, `email`, `password`, `name`, `role`, `created_at`, `updated_at`, `deleted_at`) VALUES
(31, 'dkorstman@sam.net', 'secret', 'ing. Pippa die Witte BA', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(32, 'hvandermeer@vantriet.org', 'secret', 'Anouk Demir', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(33, 'rick47@vangent.nl', 'secret', 'ir. Lucas Smits MA', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(34, 'julian.kok@vandenheuvel.com', 'secret', 'Eva Bakker', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(35, 'cterry@yahoo.nl', 'secret', 'Iris de Strigter', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(36, 'vanheusden.florian@live.nl', 'secret', 'Roan Groen', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(37, 'thom.bosman@mohammad.com', 'secret', 'Annemijn Kurt', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(38, 'noelle63@gmail.com', 'secret', 'Ian de Koning Bsc', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(39, 'vantwel.owen@yahoo.nl', 'secret', 'Job Le', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(40, 'nvansuinvorde@wright.nl', 'secret', 'Maja Ismail', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(41, 'bart.buijs@cetin.org', 'secret', 'Liza van Veen LLB', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(42, 'joelle99@yahoo.nl', 'secret', 'ds. Fiene Kalloe MPhil', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(43, 'bibi.vanleeuwen@yahoo.nl', 'secret', 'Arie van der Smeede Msc', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(44, 'ceylin99@yahoo.nl', 'secret', 'Suus Geerman', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(45, 'chris.prins@vandepol.net', 'secret', 'mr. Eva van Vliet Msc', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(46, 'esther.bosch@vanderklijn.org', 'secret', 'Joost Jansen AD', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(47, 'rafael.wouters@ramcharan.org', 'secret', 'Ninthe Narain D', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(48, 'maas.zoey@bouthoorn.org', 'secret', 'prof. Cas Bezemer AD', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(49, 'ivy.vanbreukeleveen@muijs.nl', 'secret', 'dr.h.c. Pien Jansen B', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(50, 'dvanhaeften@galenzone.nl', 'secret', 'prof. Simon Simsek', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(51, 'zeynep75@live.nl', 'secret', 'Philip Ooms PhD', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(52, 'louise50@live.nl', 'secret', 'Frederique Jansen', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(53, 'kai.cicek@hotmail.nl', 'secret', 'bc. Mart Rijcken', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(54, 'selina.janssen@vanderkint.nl', 'secret', 'Brent de Haan', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(55, 'ryildiz@lommert.com', 'secret', 'Michael Perkins', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(56, 'yfke92@gmail.com', 'secret', 'bacc. Aiden Jansen AD', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(57, 'dehaan.jill@live.nl', 'secret', 'prof. Mohammed van der Veen', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(58, 'naud67@hotmail.nl', 'secret', 'Fenne den Buytelaar', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(59, 'desmit.mees@live.nl', 'secret', 'ds. Evi Biharie', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(60, 'inarain@vandenhoek.com', 'secret', 'Aya Brumleve', NULL, '2023-09-07 07:23:02', '2023-09-07 07:23:02', NULL),
(61, 'suus.vanhaspengouw@hotmail.nl', '$2y$10$Iu/2Iftpnmq0yZmnMuZCJuejLk92l97EU8KgUSZSZqSOG3ymtuE4O', 'Britt Kallen', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(62, 'vandeberg.sophia@sambo.com', '$2y$10$mhg7mDVeRC8kK/N25Kzrk.g6Q2lvLkq4LVOouF6O.192BSqiS.bMm', 'Marijn Gerritsen Bsc', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(63, 'kort.stef@scholten.nl', '$2y$10$ITnBtU28G2EBN4YQ0YRy6.aOtQBbIrGezKAIE0fMRtLw9M1UIor0C', 'ds. Maurits Lensen', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(64, 'vanveen.jake@galijn.net', '$2y$10$wnIx6Uis0YKtI6WGLzcxye07aqEjj6.G5QRWIWcJEiOEp7Ozh0N3G', 'Luke Geldens AD', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(65, 'puck16@diesbergen.nl', '$2y$10$sVFMxQhWGyF9coXE9ZpPeeUp0YJNmTjqTFdlJeaBV4UuoF9Sl2Qfq', 'Ties Huisman', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(66, 'yuksel.tess@devos.net', '$2y$10$vTpzaVN.2VpY75Rr66duZ.UDkuzPGTa72AjgILh9bzUiBgvm4eQT2', 'ing. Janna Spiegelmaker Spanjaard', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(67, 'farah.justin@hotmail.nl', '$2y$10$uxzAaK2CYSCbxqX6SHtAGOT.ahDrcOLLi5zHFz9RKE/zSnP0YQf9K', 'Merle Wijland', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(68, 'hunal@westerbeek.org', '$2y$10$9uWMvvfxSUOn7XZJZiD7L.TDIl5KyKHk8W7bsnKJPQaQYuW/dY8su', 'Loes Kuijpers', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(69, 'isa23@mallien.org', '$2y$10$UknBBffPw19pOeTQUlPgt.W/2NnBKcskpgJfuGf44SUHV37wefUsq', 'Ivan de la Fleche', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(70, 'lvandommelen@denijs.nl', '$2y$10$vYeUeBQ5sSe2Ze4sc0irvuPwdzzuqcz0CHbTweQM5txbqwrvBz9fi', 'kand. Floortje Adriaansen', NULL, '2023-09-07 07:27:24', '2023-09-07 07:27:24', NULL),
(71, 'lucas24@yahoo.nl', '$2y$10$3jBwhMHqXdGahu.Yu7hJEe59.wI.5iZacYMlbcG6EtNeSucsKCcwy', 'Daan van der Heijden', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(72, 'volcke.azra@live.nl', '$2y$10$u72eHw/XDW2Uwr1IOFdfaeeXj2GHv20aQPr.vxOWkPzDSwpliVcO.', 'Mohammed de Haan', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(73, 'noud.vandeveen@vandegreef.com', '$2y$10$K2qe/eK4xjV6I824W6wnUuqxEPZusnwVM.L72mJzxJM5z31bOqI.i', 'Kayleigh Versluijs', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(74, 'vandenvelde.isis@yahoo.nl', '$2y$10$zs.sMYpjldvAuE.vDnCV9Or0PPFYE6rY93eeV1pSs4gG80AB7I1Ee', 'kand. Catharina Postma', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(75, 'yfke.erdogan@yahoo.nl', '$2y$10$GErCBoxZ08eEDQvsjymAg.NTPKLy8mTWKMKgiGx4LDqcH7n3HD/hi', 'Britt van der Velden', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(76, 'bruggeman.finn@hotmail.nl', '$2y$10$kHDlkV9YsBx6dGKwVgHvNeSz1qaqrzl31MDFuT6yV2PU7ZhZ.ckMK', 'ir. Niels Woudenberg', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(77, 'rwinklaar@vandeveen.org', '$2y$10$yvyew8JNb17cX7T/2avI/eUXb6WJ14KHqItdy2FOhXeM49UCAWDze', 'Bryan Wagenvoort MA', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(78, 'ayoub.hulst@vandeven.nl', '$2y$10$kOLtnxBn8vmDiLfI.7dTJuT4RlTKmwiLYyddg4S9oS/AZ7x406fMC', 'Amira Dubbeldemuts van der Sluys', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(79, 'twan.bozkurt@hondeveld.nl', '$2y$10$vb1O6wOOI/GHCZqtr1Oa7e5RdfqE.BPqVNh7zO5SYPfZvydEuv87G', 'Zeynep Serra', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(80, 'esmee.vink@rackham.nl', '$2y$10$pB.HNu.8H4Xs4DahpTiyVuL9XNMqPxCwe4AkySLKFK61zPwpcm1XG', 'bacc. Lizz Aktaş AD', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(81, 'myrthe.koningknol@ozdemir.com', '$2y$10$4cOstn2N1KQw24ytFa5lSu6O5b06yv5IG4Y9FOAtE9thSRJQdLbY2', 'Tessa Ehlert MA', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(82, 'jade.vanbovene@dewit.net', '$2y$10$L/mCvKvcR2qZHRxPbJXi4.mykbeeHup4qLKwIw4oh6RsXrbfdTgLW', 'Yara van de Velden LLM', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(83, 'ivandongen@yahoo.nl', '$2y$10$SmJABeIbkEvb1smewbGV8Oo0dtPMd4qSjWDbg1OYYrjUnS3Cvr2IS', 'Micha Wolters LLM', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(84, 'sjoerd.mater@peeters.net', '$2y$10$ykr2yM7oI4P/e1t2dt/d4eRW.MxPBKul3O3BW3.Fm8lFYA1A.80Ma', 'Zoë Freshour', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(85, 'daan95@yahoo.nl', '$2y$10$dKhQqqA6c/ct3zfI/pOQVO3CHfNQvTNL8ZLChWMM6xnMjhklf7YoG', 'drs Felix Brouwer MPhil', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(86, 'oscar.keltenie@gmail.com', '$2y$10$d6BGNd8MVKTF9aOWanarDe.Kxx9TabR2qwhqLTfsk47E.OBt.AIfK', 'drs Sjoerd Overdijk Bsc', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(87, 'yaktas@hotmail.nl', '$2y$10$s9MtBwEyEzOKMXmlAv42L.beG6HyV97ZEAFRSmtZV11I57Do7ABPC', 'Samuel Winters', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(88, 'dylan.degroot@yahoo.nl', '$2y$10$1mHTBMzJolSVtDH/n.uArOjIYRJj0grX0iukJYBXPRf8/MBQQiIm.', 'Sterre Huisman', NULL, '2023-09-07 07:27:25', '2023-09-07 07:27:25', NULL),
(89, 'adam.autar@gmail.com', '$2y$10$.mj7Kd1vVICwgEDDZcNeF.yk24dl4ekOpYi/WaiKPD4Qdr0ZNheNa', 'mr. Justin Cornelisse B', NULL, '2023-09-07 07:27:26', '2023-09-07 07:27:26', NULL),
(90, 'samuel25@gmail.com', '$2y$10$2hEcwwVDvN57nbgNYt.7UeNRTvWOJw1z03g4IZyZvZceTeq0gmKsy', 'ir. Rick Kosten', NULL, '2023-09-07 07:27:26', '2023-09-07 07:27:26', NULL);
INSERT INTO `posts` (`id`, `user_id`, `title`, `content`, `created_at`, `updated_at`) VALUES
(1, 61, 'Ea quae sed ut tempora ex.', 'Error voluptates culpa ut sunt. Porro vel eaque non. Maiores qui velit ut magni laborum laborum autem. Quos necessitatibus provident sapiente consectetur enim sit voluptatem aut. Autem et et ducimus est quae ut. Ad quia et minus recusandae dolor ab cum. Earum ex omnis eaque accusantium.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(2, 62, 'Sit explicabo et minus nisi.', 'Nobis fugiat maiores iure provident aut et. Praesentium quisquam corporis atque dignissimos. Illum earum et perspiciatis eos consequatur. Corporis explicabo id totam accusamus. Temporibus autem voluptatibus vel. Dicta a repellat et. Vero voluptatem fugiat atque corporis rerum. Et eaque quia similique quia sed eum molestias.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(3, 62, 'Ut incidunt est nemo at.', 'Nesciunt voluptate autem sed perspiciatis recusandae. Est ipsam optio porro eaque natus aliquid tempora et. Iste ipsum non mollitia repudiandae voluptatem veniam necessitatibus. Sit labore reiciendis consequatur impedit ut. Id est quam aut soluta excepturi. Et ad omnis praesentium porro adipisci eos harum. Deserunt debitis neque voluptatem eum.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(4, 62, 'Illum sunt assumenda labore officiis dolores.', 'Eum fuga sed tempore harum dolorem quae ex. Occaecati facere dolore ab sit qui ut non. Tempora eum quae a harum. Eos velit enim quasi quo aliquid. Labore consequatur quae consectetur. Et similique maxime fugit fugiat et et nulla. Aliquam maiores quia ducimus consequatur ullam error. Iusto assumenda natus in sint. Fugit blanditiis quam rerum non.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(5, 63, 'Perspiciatis nulla odio quisquam nemo sed.', 'Soluta et commodi ullam atque. Laborum quo veritatis voluptatem totam quam autem. Harum nihil rerum dignissimos quia. Maiores reiciendis et magnam magnam qui. Voluptas voluptatem maxime tempore consequatur aut perspiciatis rerum est.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(6, 63, 'Ullam quae ea et est. Voluptas qui et qui quidem.', 'Enim iusto est itaque qui nemo praesentium nobis dolorem. Ea nesciunt eum minus ut. Nulla eos est qui ducimus mollitia qui iusto. Illo omnis eum maiores quidem. Amet aut deserunt quia. Velit tempore dignissimos ea et. Cumque assumenda excepturi ipsa.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(7, 63, 'Ut mollitia vel itaque nulla cum doloribus.', 'Aliquid dolore accusamus nulla assumenda aut a sint. Ducimus rerum aspernatur illum ut quibusdam ut. Vel repellendus quaerat qui quidem blanditiis deleniti vel. Est doloremque esse omnis incidunt. Facere impedit similique qui quisquam. Vero minus neque id magni corrupti.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(8, 64, 'Et deserunt autem et.', 'Non fuga et qui et. Non aut eius et vero aut omnis facilis. Blanditiis doloremque molestiae esse qui sapiente. Error enim delectus hic dolorum odio quasi rerum. Rerum necessitatibus omnis fugit eos nihil. Sed et quia rerum illum non quasi quis. Quasi est dolor praesentium quod ea. Perferendis consequatur incidunt dignissimos rerum. Fugit nesciunt et temporibus et quis voluptatem.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(9, 64, 'Deleniti aperiam aut quia nam ea sunt.', 'Esse odit dolores numquam in. Officia asperiores sequi vel. Expedita architecto perspiciatis velit aut nihil odio. Est quas placeat nam a repellendus nemo temporibus. In tenetur deserunt sint vitae. Rerum laudantium aut accusantium numquam consequuntur corrupti cupiditate. Nostrum repudiandae commodi qui nihil. Vitae vel id earum est blanditiis.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(10, 65, 'Sit minima sit hic.', 'Enim expedita voluptatum ducimus temporibus. Perspiciatis non laboriosam voluptate aut qui qui est et. Consequatur omnis cum maiores quo velit et et. Ipsa autem vel qui enim. Recusandae quis ullam voluptatem molestias. Debitis minus reiciendis voluptas dolore. Delectus ratione eos quo debitis in voluptatem. Quos temporibus ratione odit sapiente.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(11, 66, 'Et est omnis enim rerum aut.', 'Facere rerum velit nostrum enim. Nihil sed sunt quae nisi temporibus et voluptas. Quia fuga neque tempore in maxime voluptas ut pariatur. Vero vitae omnis aut. Officia necessitatibus facere sapiente ducimus consequuntur voluptate rerum in. Debitis doloribus voluptas autem molestias adipisci. In qui et exercitationem aliquid amet temporibus. Modi est velit et aliquid sit.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(12, 66, 'Quae incidunt qui dolores harum voluptas tempora.', 'Accusantium debitis ea doloribus doloremque debitis dolor. Omnis maiores minus reprehenderit in. Ducimus consequatur culpa mollitia amet at excepturi molestiae. Quas est praesentium qui temporibus eum id voluptas id. Nam itaque nobis non est quaerat labore. Amet nisi reiciendis sapiente eum veritatis nihil rerum voluptas. Eligendi illo et neque velit ducimus deserunt molestias.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(13, 66, 'Et amet suscipit quas sit.', 'Autem quis id eos et rem qui. Ipsam accusamus voluptatem animi consectetur. Quia qui non et sed quo odit quia. Nesciunt nihil ut delectus velit et ut. Aut corrupti minus consequatur. Corrupti illo error nihil dolor qui. Non quo sint ullam dicta in error autem. Cupiditate voluptatem optio et voluptas saepe laborum vero magnam. Deserunt qui illo recusandae sunt id est.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(14, 67, 'Excepturi cupiditate eum velit nam.', 'Eius unde voluptatem itaque maxime sed sed. Vel non animi illum molestiae. Quidem est nobis dolorem laborum repellendus excepturi perspiciatis. Voluptatem aliquam non voluptates laborum aliquid corrupti et. Rerum est laudantium nisi est et illum nulla. Iure ut temporibus quasi in modi. Dolores illo quia deserunt aut porro.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(15, 68, 'Hic et voluptatem sunt facere sunt.', 'Molestiae odit amet enim quae sit et sed. Delectus ut cumque rem impedit dolores numquam. Id nostrum dolorem fugit et beatae alias omnis. Nostrum vitae non et voluptatibus aut est. Quisquam sequi enim doloribus magnam aut quisquam delectus. Accusamus voluptas qui voluptatum iusto sunt. Est ea quos dolorem.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(16, 68, 'Rerum ipsam possimus est iste.', 'Tempore nam cupiditate eum. Quasi dolor ut vitae et sapiente veniam deserunt. Vel eveniet quia nemo quod impedit. Dolor minus quis non aut. Velit repellendus ut nihil quia qui itaque rerum in. Eius eos quis rerum molestiae omnis. Aut quibusdam aspernatur dignissimos modi quas corrupti occaecati. Illo fuga possimus labore rerum aut pariatur.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(17, 68, 'A ut magnam aut.', 'Est quo illo doloremque ipsam rerum incidunt. Debitis expedita dignissimos et. Aliquam amet tempore itaque iusto est soluta. Distinctio voluptatem quo nesciunt maiores ducimus voluptatum animi. Tempore ut nostrum natus iure. Est incidunt ab amet et non modi. Dolor deleniti quibusdam ratione maiores. Reiciendis odit pariatur facere veniam maxime.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(18, 69, 'Necessitatibus nihil ipsum reiciendis ipsa harum.', 'Quia reprehenderit repellat voluptates sed numquam voluptate deserunt. Et aut qui quia in consequuntur repudiandae quo. Quae qui placeat quis. Nemo repudiandae odio autem eos. Dolorem culpa nisi quae assumenda ipsa. Sequi dolores quis illum optio possimus amet ipsum. Corporis quod aut maxime amet modi.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(19, 69, 'Quia aut veritatis dolore culpa dolores a.', 'Omnis nemo rerum similique eum voluptates. Numquam facilis reiciendis quod voluptatem. Et et quis omnis in animi natus ad corporis. Nulla eveniet nihil quasi debitis. Nobis dicta aut repellendus perspiciatis molestias. Reprehenderit sint sit distinctio quod eos. Et sapiente quis qui et facilis non ex. Aut alias perspiciatis et maiores vel.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(20, 70, 'Velit ipsam aliquam asperiores rerum.', 'Laudantium atque est omnis ad quia. Est dicta quisquam vel tenetur quia laborum. Amet et eligendi quis ut. Omnis sed reiciendis qui. At enim consequatur sunt molestiae consectetur. Id veniam magni deserunt recusandae rerum. Rerum vel earum temporibus. Nisi similique sint non aut sunt. Error ab porro pariatur occaecati.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(21, 70, 'Mollitia sunt adipisci rem reprehenderit et ea.', 'Soluta est nobis aut aut voluptatem libero. Aspernatur consequuntur id a nihil nostrum assumenda. Est et qui architecto in quos qui molestiae. Illo eum dolores non cupiditate fugiat sit. Accusantium nihil pariatur culpa vel minus. In consequuntur molestiae ut et pariatur itaque. Et facilis id nisi qui. Qui non quo unde dolorem sed.', '2023-09-07 07:27:24', '2023-09-07 07:27:24'),
(22, 71, 'Ipsa sed facilis sed.', 'Omnis molestiae assumenda nulla provident veniam quis nobis dolore. Non deleniti praesentium ipsum dolor officiis nostrum vitae. Eum quia beatae consequatur excepturi et. Quidem ipsam qui amet est necessitatibus. Voluptas dolore id ut facere. Voluptatum unde reprehenderit temporibus voluptatum culpa quo magni. Qui ut quo animi.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(23, 72, 'Qui qui ut adipisci voluptatem.', 'Est harum reprehenderit autem. Consequatur nemo libero sint quo repellendus quis. Et voluptatem laboriosam facilis. Voluptatem minima quas nobis quia. Sed ipsum voluptatem et veniam saepe quaerat recusandae. Qui necessitatibus repellat quasi. Impedit sequi sunt deleniti sit reprehenderit.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(24, 72, 'Libero rerum voluptatibus labore unde dicta.', 'Accusantium qui ullam sit eaque. Ut reiciendis corrupti et sit praesentium ad quasi accusamus. Odit omnis eum explicabo dolorem placeat. Consequatur esse distinctio ratione. Quae autem voluptate occaecati nihil qui. Hic quo id perspiciatis. Et omnis laboriosam architecto nulla.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(25, 73, 'Itaque ipsam at animi voluptatem.', 'Vel nesciunt neque esse ea. Reiciendis necessitatibus qui fuga sed mollitia voluptatem. Ad modi et dignissimos qui iusto voluptatum inventore. Labore quae aperiam perspiciatis repellendus. Aut inventore non soluta provident eum. Quia ad voluptatibus repellendus. Ut id dolores aut voluptatem saepe.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(26, 73, 'Et natus nisi perferendis fugiat.', 'Aut in in suscipit architecto ab ut. Itaque adipisci tempore fugiat. Eum culpa magni nihil suscipit dignissimos veniam. Corrupti sunt et est voluptas tempore minima. Explicabo dignissimos dolores quam. Omnis est atque inventore consequuntur a. Aut aut in et. Culpa distinctio ut id ut rerum perspiciatis blanditiis.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(27, 73, 'Cumque ipsam error nam vitae qui.', 'Maiores voluptatem sint et velit ratione officiis molestiae. Odio molestiae veniam aut praesentium porro qui ipsa. Ut cumque voluptatibus non asperiores. Quam aut corporis nam. Dolorem architecto in culpa adipisci qui rerum velit. Officiis et repellendus eaque animi.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(28, 74, 'Velit voluptatum perspiciatis voluptas molestias.', 'Facilis aspernatur explicabo numquam incidunt et aspernatur sapiente. Aut culpa alias ipsum. Inventore sit ut alias magnam voluptas exercitationem quia in. Laboriosam voluptatem quo et officiis accusantium. Quis officia ea eligendi inventore doloribus tempora eum ipsa. Debitis quia necessitatibus iure natus. Distinctio velit aut dolore et.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(29, 74, 'Ea enim est odit sed repudiandae.', 'Doloremque neque et nobis. Quia unde quas quam esse non asperiores. Provident libero qui aut ut nam. Quasi culpa nostrum repellendus eligendi aut. Sed et nostrum hic qui dolores aliquam. Maiores perspiciatis eos et aut. Error dolorem id quo. Veritatis quasi sequi temporibus deleniti.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(30, 75, 'Sed pariatur minima quae pariatur.', 'Blanditiis voluptate sequi dolor architecto nesciunt rerum porro. Eos eius qui voluptate voluptatibus. Non veritatis delectus quis. Pariatur excepturi necessitatibus laudantium ut. Et dicta vero iure consequatur dolore delectus earum.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(31, 76, 'Fugiat dolorum nesciunt quae ratione a molestiae.', 'Vero ut sed quidem sint. Iste consectetur omnis incidunt sit ut nostrum. Et odio quae aut autem. Minus autem molestiae expedita modi suscipit. Sit voluptatem saepe illum blanditiis autem. Aut quae eum similique neque. Illum dolore consequatur harum beatae veniam blanditiis quis.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(32, 76, 'Quia et aperiam laborum ut fugiat et.', 'Est vero blanditiis ea nulla labore. Occaecati molestiae magnam natus aut ratione provident. Molestiae placeat aliquid sunt tempore. Molestias est impedit non tempora est enim. Mollitia animi omnis consequatur dicta. Quidem facilis et nostrum mollitia. Tempore odio illo doloribus ut. Vero id non nemo voluptates perspiciatis dolores neque incidunt.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(33, 76, 'Sequi saepe labore et aut error autem.', 'Harum dolorum voluptatem laboriosam facere voluptates esse. Odit aliquid autem et eaque. Enim impedit provident amet rerum laborum et adipisci. Ipsa eum similique dolor quia harum maiores. Aut qui esse voluptatem iusto. In molestias velit facere et vel iure. Eum vel ad sequi provident autem sunt. Nostrum nam id magnam molestias a velit ab.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(34, 77, 'Est fugiat tenetur quas ab ducimus placeat nihil.', 'Quia minima aspernatur sunt eum. Ut nihil est neque incidunt sit neque. Perferendis magni dolorem sint officiis suscipit voluptas delectus. Et vitae et nam sint sapiente est quia. Odio voluptatum assumenda in veniam fugiat. Quia tenetur dolorum porro non aut. Exercitationem consequuntur consequatur nesciunt placeat unde impedit praesentium. Facere pariatur deserunt aliquid rem voluptates eum.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(35, 77, 'Ipsa corporis et quam consectetur ut quod.', 'Accusantium exercitationem sit nam. Ipsum voluptate possimus est quia optio enim laborum. Eum fugiat placeat alias voluptate tempora qui consectetur. Culpa est incidunt repudiandae rerum soluta. Quam aut consectetur sed aliquam. Et eos necessitatibus velit repudiandae consequuntur illo dolorem. Sint tenetur aut sed sed quasi omnis in. Tempore blanditiis sed voluptatem.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(36, 78, 'Temporibus voluptas et quis magni autem aut.', 'Id accusamus est libero qui fugiat perferendis. Blanditiis et qui doloremque eaque molestiae explicabo quos. Odit consequatur in dolorem ut consequatur officia. Libero dolores a in debitis enim maxime perferendis. Repellat nobis aut nostrum non. Suscipit consequuntur nesciunt dolores sit. Doloribus ut error sunt consequatur architecto voluptatum.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(37, 78, 'Id sit vitae in ab iure debitis.', 'Est fugit est quas rerum laudantium eius quia. Aut voluptates quis sapiente aliquam. Laboriosam vero enim consequatur tempora velit. Commodi explicabo dolores quae et. Necessitatibus vitae eum distinctio. Doloribus voluptate eum explicabo vel nobis et rem. Libero voluptas ipsum molestiae asperiores quaerat. Sunt omnis quasi dolores in illum ut.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(38, 79, 'Culpa id et perferendis magnam expedita.', 'Non amet aliquid omnis vitae ut. Earum quae fugit esse dolorem eum. Tenetur consequatur consequatur delectus numquam. Neque delectus tempora voluptatem qui. Reiciendis nulla non esse ipsum sed ex nulla. Omnis nulla accusamus in molestias. Perferendis voluptas natus at cupiditate.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(39, 79, 'Aliquid itaque dignissimos voluptate odio.', 'Et nostrum porro quos voluptatem natus. Fugit blanditiis sunt a inventore omnis dignissimos ab. Non eaque earum a. Est sapiente eligendi voluptas ullam ea asperiores qui ab. Corporis laboriosam corrupti necessitatibus beatae culpa. Laudantium molestiae facere molestiae vitae. Natus libero dolor animi delectus numquam numquam accusamus. Et iusto a provident sit.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(40, 80, 'Nostrum ut enim occaecati a beatae.', 'Ipsa quae hic eius officia ad error qui totam. Voluptatem enim rerum et veritatis repudiandae sit corporis voluptas. Accusamus sed quidem et vitae. Labore voluptate qui occaecati dolor est unde sit. Sequi dolorem vel et sint in rerum. Pariatur voluptatibus alias soluta occaecati at nulla recusandae.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(41, 80, 'In non et beatae a tenetur nam sunt.', 'Molestiae enim autem ea accusamus officiis et numquam. Eum sapiente laboriosam totam aliquam ducimus. Repellat earum iusto praesentium distinctio. Quas quia assumenda sunt voluptas molestias non accusantium. Velit et cupiditate quibusdam. Et id laboriosam enim quo ipsam totam eum.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(42, 80, 'Accusantium est enim ut eligendi delectus eum.', 'Corrupti aut totam occaecati voluptas commodi ad. Blanditiis eos eveniet in. Molestias explicabo praesentium id nobis. Facilis deserunt sed voluptatem deleniti recusandae. Optio mollitia sit et et. Suscipit aspernatur voluptas voluptas. Sapiente temporibus totam quam doloremque qui distinctio. Id qui eaque et quod illo. Sunt aut repudiandae esse doloremque. Ullam sunt dolore fugiat aperiam et et.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(43, 81, 'Corporis molestiae sapiente est distinctio quo.', 'Voluptatum quisquam et repudiandae molestias est. Beatae aut voluptatibus eius unde autem ea dolore. Eius quo occaecati non laborum quia maiores qui. Dicta alias quo incidunt quaerat. Est harum perspiciatis consectetur eos. Natus doloribus rerum dolores consectetur nostrum et. Qui amet sint quia et. Et quam iure quaerat pariatur quaerat et.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(44, 81, 'Aut aperiam a ducimus sed.', 'In animi itaque et natus tempore. Tempora facere libero officia magnam praesentium est. Error aut nostrum nulla praesentium excepturi qui in modi. Placeat ratione qui consectetur optio maxime. Asperiores repellendus necessitatibus voluptatum illum rem quo. Illo similique repellendus sint.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(45, 81, 'Rem velit sequi officia ea quis.', 'Sit quod quibusdam qui dolorum ipsa. Debitis sed eius recusandae. Sequi sit maiores quia. Nulla quae id sint eveniet ipsum fugiat id enim. Corrupti error hic sint nisi dolorem accusantium ut. Quis vel sunt cumque id rerum. Autem commodi dolores aspernatur et. Et atque accusantium voluptates.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(46, 82, 'Nobis tenetur dicta qui ex ex aspernatur.', 'Rerum et ullam et et at non. Consequatur consequuntur voluptas modi adipisci blanditiis reprehenderit. Voluptatum provident fuga cumque hic magni. Ducimus laboriosam voluptas optio aut quasi. Aut numquam aut quas sed et mollitia. Ad laudantium ea vero. Sit magnam illum eius consectetur doloribus. Et nemo non temporibus ratione ipsum accusamus.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(47, 82, 'Neque natus perspiciatis ipsam rem aperiam unde.', 'Eligendi rem odit corrupti beatae. Et libero voluptatem accusamus neque non. Minus omnis et recusandae laborum voluptas nisi. Aliquid eius expedita sed qui enim et. Quam repellat labore veritatis beatae nobis perspiciatis. Dolorum aut commodi odit repellendus cupiditate ut debitis. Eos quo sit et accusamus dolore. Et sequi consequatur ut mollitia sed nemo eos.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(48, 83, 'Provident sit est et dicta.', 'Voluptate ipsam commodi nulla ut vero ut temporibus. Debitis unde saepe in debitis repudiandae enim. Omnis accusamus qui nam mollitia. Molestiae debitis aut id sint qui non et architecto. Esse ipsa vitae et et dignissimos rerum nam.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(49, 84, 'Similique totam voluptate amet nihil.', 'Quia incidunt nesciunt dignissimos consectetur unde totam laudantium. Dolorum est natus facere. Nesciunt enim eius temporibus laborum sit. Iusto in natus accusamus odit repellendus quisquam. Molestias officia dicta magnam mollitia temporibus. Voluptatibus rerum aut quia quo ipsam minima. Aut in autem minima ea illo quo.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(50, 85, 'Harum qui rem beatae omnis sunt.', 'Et ut animi exercitationem quia. Ut eum sit velit voluptate magni qui. Doloremque eius reprehenderit dolores non beatae. Quis iure repellendus ab aspernatur natus et. Repellat facilis impedit maiores eos delectus. Nobis est magni quia iste. Autem laboriosam eos aliquam occaecati et. Autem aut quia ea omnis et.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(51, 85, 'Veniam dolore sed minima quia.', 'Natus rerum voluptatem exercitationem voluptatem ut asperiores illo. Accusamus voluptatibus et voluptates. Molestiae optio vitae quo sint. Rerum totam blanditiis excepturi cum sit voluptates quo quia. Est in beatae sunt porro quibusdam sit repellendus. Nostrum voluptates facere nulla suscipit sed ipsam cupiditate eum. Tenetur quas veniam in quidem.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(52, 85, 'Voluptas natus ut repudiandae sed.', 'A iure ea dolorem occaecati. Quam minima rerum odio eum corporis dolore quaerat. Voluptate expedita qui nihil sit id et laudantium. Ut labore quos odio sequi suscipit. Rem quisquam non eos tempora quidem atque suscipit minima. Est iure reprehenderit ducimus sequi quaerat. Accusantium ad ut illo quia.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(53, 86, 'Voluptatem aut veniam voluptatem.', 'Architecto dolorum enim corrupti ea ea. Voluptas officia eum dolor eum aut quibusdam rerum. Qui incidunt beatae quia veniam. Architecto eos doloribus sed ea. Voluptas qui architecto magni deserunt quia quas quas provident. In qui veritatis qui repellendus blanditiis ut.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(54, 87, 'Commodi suscipit et omnis pariatur ea rerum nemo.', 'Assumenda sunt atque sed. Qui inventore beatae repellendus veritatis quia quo amet veritatis. Cum qui enim incidunt. Maxime aut laborum est. Dolores quam et est cupiditate quo. Ducimus fuga nihil repellendus ullam vel placeat. Beatae reprehenderit id aliquid perspiciatis itaque. Et est optio quia maxime dolor. Doloribus ut dolores dolor nihil ab. Laudantium voluptatem quas ipsam laboriosam rerum.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(55, 87, 'Neque ut autem ut facere.', 'Possimus eum quo temporibus et consequuntur enim aut. Ut nobis nihil placeat architecto consectetur qui et. Sit officia dolores est tempora ea. Voluptates recusandae sequi incidunt dolore ex est saepe. Quas et natus ipsa laboriosam et ipsum. Repellendus totam quis unde enim. Eos ea beatae autem ut reiciendis velit.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(56, 87, 'Sit quod autem aut et earum quaerat culpa.', 'Et ut sed excepturi voluptatibus aspernatur unde. Non culpa qui consectetur consequatur. Libero soluta quasi aut iusto consectetur. Eum id nemo animi quas eum omnis occaecati. Odio nulla et animi. Molestiae pariatur omnis nihil fuga nesciunt ut. Vel nemo odio fuga ad aut cum. Est enim vero adipisci quis qui minus dolor amet.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(57, 88, 'Quia ut dolorem officiis nihil sint.', 'Aut accusamus occaecati officiis temporibus vel vel iure unde. Placeat eum non quae occaecati dolor repellendus eaque. Quia enim autem aliquam dolore sapiente. Culpa quo error voluptatibus quia ex et. Occaecati tenetur aliquam explicabo eligendi quis. Aliquam unde praesentium quas alias inventore est. Molestiae cumque quia eum.', '2023-09-07 07:27:25', '2023-09-07 07:27:25'),
(58, 88, 'Et ad odio illo eum.', 'Eveniet consequatur ipsum optio. Omnis animi et quo reiciendis porro doloribus dolorum. Quia id velit sunt quibusdam perspiciatis. A nesciunt non minima quia tempora eum. Quod sit amet fugiat atque consequatur. Totam exercitationem fugit iure a vitae nisi. Ipsa qui voluptatum molestiae doloribus. Et dolorem est enim eum assumenda ut. Fugit possimus maiores in. Officia et aut et dolor minus.', '2023-09-07 07:27:26', '2023-09-07 07:27:26'),
(59, 89, 'Odio vel optio nesciunt repellat.', 'Voluptatem aspernatur totam et culpa saepe. Earum molestias voluptatum praesentium magnam. Pariatur esse laudantium eum nam adipisci corporis. Ab quia blanditiis unde magnam magni corporis et. Ex ut est et fugit magnam dolor. Voluptate architecto dolore necessitatibus tempora debitis cupiditate. Cum quod similique deserunt assumenda quis esse ut. Dolorem autem nesciunt aliquam explicabo.', '2023-09-07 07:27:26', '2023-09-07 07:27:26'),
(60, 89, 'Omnis aut hic velit voluptatem nisi aut.', 'Dolorem praesentium perspiciatis voluptatem tempore rerum odio. Aut voluptatem commodi sed animi ut voluptas veritatis. Laborum non error pariatur dolore autem temporibus sunt. Odit omnis ut eos est ea. Ut velit voluptatem perferendis sit. Ipsum hic reiciendis aut similique omnis tempore in dolor. Non consequatur quam fuga est natus consectetur.', '2023-09-07 07:27:26', '2023-09-07 07:27:26'),
(61, 90, 'Est sunt recusandae culpa officiis doloremque.', 'Dignissimos perferendis magni et quas tempora. Deserunt omnis ut quia veritatis officiis eum. Mollitia cupiditate eveniet nulla ut eaque est facere veritatis. Id est et et modi natus earum vitae. Quod sed dignissimos autem. Sit voluptate eum omnis. Cupiditate molestias dolor eos nam nesciunt aut.', '2023-09-07 07:27:26', '2023-09-07 07:27:26');
Database exporteren
Wanneer je een database hebt gemaakt kan je deze exporteren. Er wordt dan een .sql bestand gemaakt met queries. Door het uitvoeren van deze queries kan deze database weer worden aangemaakt.
Open Heidi-sql en klik met de rechtermuis op de database
In het volgende scherm kan je aangeven wat je wilt exporteren. Als je de complete database wilt exporteren, vul het dan is zoals hieronder.
Database importeren
Als je een database dumb (export bestand) hebt, kan je deze toevoegen aan je database server dmv importeren. Klik hiervoor bij Heidi-sql boven in het menu op "bestand"
Je kan dan het .sql bestand openen. Daarna wordt de database toegevoegd.
Als het importeren klaar is moet je het scherm vernieuwen om de wijzigingen te kunnen zien. Klik daarvoor op een database en daarna op F5.
In veel programmeertalen kan object georiënteerd geprogrammeerd worden. Dit kan ook met PHP een voordeel van object geörienteerd programmeren is dat de code overzichtelijker, beter herbruikbaar en beter onderhoudbaar is.
In deze cursus zullen we wat basis principes van object georiënteerd programmeren worden gebruikt en besproken.
PDO class
PHP heeft een aantal verschillende interfaces om een database te benaderen. Eén daarvan is PDO (PHP Data Objects). Om dit te gebruiken heb je de PDO extensie nodig. Deze kan in het php.ini bestand worden aangezet. In de meest recente versie van USBwebserver is deze extensie al geactiveerd.
Voordelen van de PDO driver is dat de synthax voor alle databases hetzelfde is.
Database object
Om de database te benaderen. Gaan we gebruik maken van een database object.
Dit object plaatsen we in de "src" directory.
<?phpclass Database
{
//is de PDO connectie private $connection;
//wordt aangeroepen als je een nieuw database object aanmaaktpublic 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,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
} catch (Exception $e) { //als het niet lukt dan...$this->showException($e);
} catch (Error $e) {
$this->showException($e);
}
}
public function query(string $sql, array $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)
{
//Alleen in productie if(config('app.env')!='production'){
dd($exception->getMessage());
}else{
echo "Er is een fout opgetreden, ga <a href='/'>terug</a> naar de website";
die();
}
}
}
Als we dit object gaan gebruiken dan zal je langzamerhand ook de code gaan begrijpen.
Om dit te laten werken moeten in het configuratie bestand de verbinding met de database worden aangemaakt.
We gaan nu even testen of onze database connectie werkt en of we gegevens uit de database kunnen halen.
We starten even met een snelle slordige manier. Als het werkt maken we alles netjes.
We openen het bestand app/views/home.view.php en wijzigen de code in het volgende.
<?phpview("parts/header", ['title' => 'home']);
view("parts/navigatie-menu");
?>
<div class="sm:mx-10">
<h1 class="text-3xl my-4">Home</h1>
<?php $db = new Database();
$result = $db->query("SELECT * FROM posts ORDER BY id LIMIT 1")->fetch();
?>
<div class="border border-1 rounded p-4 bg-gray-50">
<h2 class="font-bold"><?= $result['title'] ?></h2>
<?= $result['content'] ?>
</div>
</div>
<?phpview("parts/footer");
We starten PHP
<?php
We maken een database class aan met variabele naam $db (kies hiervoor een logische naam)
$db = new Database();
We gebruiken onze database class om een query op de database uit te voeren. We selecteren in dit geval de meest recente "post" en stoppen dit in $result.
$result = $db->query("SELECT * FROM posts ORDER BY id LIMIT 1")->fetch();
$result is nu een array met keys en values. De key's zijn de kolomnamen uit de tabel "posts" de values zijn de gegevens die bij de laatste "post" staan.
We sluiten PHP af
?>
Daarna maken we een blok aan waar we de 'titel' en de 'content' van de laatste "post" inzetten.
Het op het scherm schrijven van een value van de array kan dus met $result['title']
Om het er een beetje uit te laten zien wordt tailwindcss gebruikt voor de opmaak.
- een rand die een beetje rond is
- padding van 4px
- lichtgrijze achtergrond
De gegevens uit de database worden getoond door
<?= $result['title'] ?>
Dit is een snel notatie voor
<?php echo $result['title']; ?>
Met als resultaat
Dit was even snel testen of het werkt. Nu gaan we de code op de juiste plaats zetten.
Logica en database interactie komt in de controller. En in de view komt alleen wat we willen laten zien.
Dus open app/controllers/home.php en plaats daar de logica in. En geef aan de view de title en content mee
<?php$db = new Database();
$result = $db->query("SELECT * FROM posts ORDER BY id LIMIT 1")->fetch();
view("home", [
'title' => $result['title'],
'content' => $result['content'],
]);
We geven nu aan de view home (php pagina home.view.php) twee variabele mee 'title' en 'content'. Deze variabele kan je nu in home.view.php gebruiken. Uiteraard begint de variabele wel met een $
home.view.php verwijder de logica en wijzig de $result in de meegestuurde variabele $title en $content.
In de view willen we nu alle posts gaan tonen. Daarvoor hebben we een loop nodig die door alle posts heen kan gaan. Je zou hiervoor een foreach loop kunnen gebruiken
Nu moeten we alleen nog een route maken om de posts te kunnen zien. En een linkje in het menu.
Daarvoor openen we de router.php en voegen de route naar "posts" toe.
app/router.php
if ($uri == "posts") {
require "controllers/posts.php";
die();
}
htmlspecialchars() is een PHP-functie die wordt gebruikt om gegevens veilig weer te geven in HTML-pagina's, vooral wanneer deze gegevens afkomstig zijn uit gebruikersinvoer of een database. Hier is waarom het handig is om htmlspecialchars() te gebruiken bij het tonen van gegevens uit de database:
Beveiliging tegen cross-site scripting (XSS): Als gegevens uit de database rechtstreeks worden ingevoegd in een HTML-pagina zonder sanitizing (schoonmaken), kan een aanvaller kwaadaardige code insluiten die wordt uitgevoerd wanneer andere gebruikers de pagina bekijken. htmlspecialchars() codeert speciale tekens zoals <, >, " en & naar hun HTML-entiteiten (bijvoorbeeld <, >, ", &), waardoor ze onschadelijk worden gemaakt voor browserinterpretatie. Dit voorkomt XSS-aanvallen.
Behoud van gegevensintegriteit: Door gegevens te coderen met htmlspecialchars(), behoudt u de oorspronkelijke gegevensintegriteit. Als u bijvoorbeeld tekst met speciale tekens in de database hebt opgeslagen, zorgt het gebruik van htmlspecialchars() ervoor dat deze tekens correct worden weergegeven in de browser, zonder dat ze als HTML-tags worden geïnterpreteerd.
Hier is een voorbeeld van hoe u htmlspecialchars() kunt gebruiken om gegevens uit de database veilig weer te geven in een HTML-pagina:
php
<?php
// Haal gegevens op uit de database (bijvoorbeeld een gebruikersnaam)
$gebruikersnaam = getGebruikersnaamUitDatabase();
// Gebruik htmlspecialchars() om de gegevens veilig weer te geven in HTML
echo "<p>Welkom, " . htmlspecialchars($gebruikersnaam) . "!</p>";
?>
Door htmlspecialchars() te gebruiken, zorgt u ervoor dat de gegevens correct worden weergegeven zonder dat de veiligheid van uw applicatie in gevaar komt. Het is een essentiële praktijk bij het ontwikkelen van webtoepassingen om beveiligingsproblemen te voorkomen.
J Formulieren
Router.php
Omdat we in deze paragraaf zeer veel nieuw routes gaan aanmaken is het handig om hier een overzichtelijke manier voor maken.
Dit gaan we doen door object Route.php toe te voegen aan ons bronnen
Copy-paste de code van Route.php naar src/Route.php het is niet nodig om deze code te kennen.
In index.php voegen we Route.php toe achter Database class
Nu wijzigen we de inhoud van onze app/router.php in
<?php//ROUTER$route = new Route();// Hier doen we een controle of een bepaalde URL bestaat en we verwijzen door naar een controller of een view$route->get('', "controllers/home.php");$route->get('index', "controllers/home.php");$route->get('contact', "controllers/contact.php");$route->get('about', "controllers/about.php");$route->get('posts', "controllers/posts.php");
//niets gevonden
http_response_code(404);view("404", ['error' => $_SERVER['REQUEST_URI'] . " niet gevonden"]);die();
Je hebt nu meteen een paar voorbeelden hoe onze nieuwe router werkt.
Je kan nu de methode get() of post () gebruiken. Beide hebben twee functie parameters nodig. Als eerste de uri en als tweede de controller/view waar naar verwezen moet worden.
Formulieren basis
Om gegevens aan de database toe te voegen en te wijzigen hebben we HTML formulieren nodig. En ook om in te loggen zou een formulier erg handig kunnen zijn.
HTML kent een aantal verschillende formulier velden
Input veld
korte tekst (één regel)
password
datum
e-mailadres
etc...
Checkbox
vierkant blokje dat je aan/uit kan vinken
Radio button
Rond knopje die je alleen aan kan vinken. Als er meerdere van zijn kan er altijd maar één aan staan.
Selectbox
dropdown box waar je een item uit kan selecteren
Textarea
Een tekst van meestal meerdere regels
Deze velden staan tussen de html tags <form> ... </form>
Een formulier start met de <form> tag. Hierbij worden de attributen method en action meestal meegegeven. Voor de method heb je keuze uit post en get. Voor een zoekveld gebruiken we meestal get en voor het versturen van data voor in de database post.
De action is de url waar het formulier naartoe wordt gestuurd. Deze url mag je zelf bedenken en in je router.php toevoegen. In de router.php verwijs je weer naar een controller. Die uiteindelijk het formulier kan afhandelen.
Alle velden in een formulier hebben een name. Dit wordt als variabele met het formulier meegestuurd. In bovenstaande formulier zijn dit $_POST['title'] en $_POST['save']
Zorg ervoor dat je formulier ook een verstuur button heeft. Dit kan een input zijn maar je kan daarvoor ook de <button>Opslaan</button> gebruiken.
Voorbeelden van de verschillende formulieren staan in sheatCheat.php deze zal ook op de toets beschikbaar zijn.
Alle informatie over formulieren kan je vinden op W3schools.
Gebruik deze informatie voor de volgende opdrachten. Eventueel kan je natuurlijk ook aan chat-GPT of aan Google Bard vragen stellen over het gebruik van formulieren.
We gaan nu onze posts pagina uitbreiden met een zoekveld. Boven alle posts maken we een zoekveld waarbij we kunnen gaan zoeken op posts die voldoen aan de voorwaarde die we in het zoekveld intypen.
Van onderstaande opdrachten is ook een video uitwerking beschikbaar
In onderstaande opdracht gaan we een nieuwe 'post' aan onze database toevoegen.
De flow gaat er als volgt uitzien:
Ergens komt een button of link naar /post-create
Omdat post-create geen logica nodig heeft heb je geen controller nodig, maar kan je direct naar een view verwijzen. (mag natuurlijk wel via een controller)
De view-create toont een formulier met een action="/post-store" hier wordt het formulier naar toegestuurd.
post-store controller voert de gegevens in de database en verwijst de gebruiker door naar overzicht posts
Elke 'post' heeft een title, content en user_id, omdat we nog geen login mogelijkheid hebben zetten we in deze opdracht het user_id op een reeds aanwezig id uit de tabel users bv id=63
dit kan in jou database anders zijn, kijk hiervoor in de users tabel
Om dit te laten werken hebben we twee nieuwe route's nodig. Voeg onderstaande routes toe aan app/router.php
Als het aanmaken van een nieuwe post gelukt is wil je de gebruik daarover graag feedback geven. Je zou hiervoor een nieuwe view kunnen aanmaken met de tekst. Het invoeren is gelukt, of iets dergelijks.
Ook is het mogelijk om toast te laten zien als het toevoegen gelukt is. Bij een toast komt er een blokje in beeld met een bepaalde boodschap. Na enkele seconden verdwijnt het blokje weer.
Er zijn tal van voorbeelden en scripts op internet te vinden om dit te bouwen. Wij gaan dit nu ook toevoegen aan ons project. Daarvoor moeten een paar bestanden worden toegevoegd. Let op dit hoef je allemaal niet te begrijpen. Alleen als het het wilt aanpassen dan is begrijpen noodzakelijk.
Het gebruik van de toast werkt als volgt
flash("Post is opgeslagen", true, 3000);
Deze functie kan je overal aanroepen (dit zal meestal in de controller zijn) en zal een boodschap op het scherm tonen. De parameters die je aan de functie kan meegeven zijn:
- boodschap
- succes true/false
- tijd tot verdwijnen
Bestanden aanpassen (eenmalig)
Om het te laten werken moeten onderstaande drie bestanden worden aangepast:
app/views/parts/header.view.php copy-paste de code van github en vervang de huidige code.
app/views/parts/footer.view.php copy-paste de code van github en vervang de huidige code.
src/functions.php copy-paster de code van github en vervang de huidige code.
index.php (voeg helemaal boven aan de code: session_start(); toe
Uiteraard kan je zelf de layout van de toast aanpassen in views/parts/footer.php
De toast maakt gebruik van alpine.js, dit is een javascript framework waarmee we later leuke dingen mee kunnen doen.
Waarschijnlijk heb je bij het testen wat posts toegevoegd. Die je misschien helemaal niet wilt tonen op je pagina. Deze zou je graag willen kunnen verwijderen. Meestal doe je dit op een admin pagina. Deze hebben we nog niet, maag gaan de verwijder functionaliteit toevoegen aan de posts pagina.
Onder elke post voegen we een verwijder button toe.
De button is een formulier met een verborgen veld en een submit button.
In het verborgen veld staat het id van de betreffende post. Zodat na het versturen de juiste post verwijderd kan worden.
Er is ook een video waarin deze opdracht wordt uitgewerkt
Wat we hebben gemaakt werkt. Maar graag zou je na het klikken op een verwijder button eerst een bevestiging van de gebruiker willen hebben.
Dit is iets waar we later naar gaan kijken.
CSRF protection
Omdat we nu een url hebben waar posts kunnen worden toegevoegd en verwijderd zou hier van buiten onze website misbruik van kunnen worden gemaakt. Het is vrij eenvoudig om een scriptje te schrijven dat duizenden posts via onze /post-create gaan toevoegen. Uiteraard is dit onwenselijk en willen we dat het alleen mogelijk is om posts toe te voegen via onze website. Dit kan door gebruik te maken van csrf protectie.
Bij CSRF protectie wordt er bij elk formulier op de website een code toegevoegd. Deze code is op de server bekend. Als deze twee codes overeenkomen dan kan het formulier worden verwerkt. Zo niet dan zal deze worden geweigerd met bijvoorbeeld een foutmelding. Alleen bij formulieren met een method="post" zal de protectie woren uitgevoerd
Toen we de toast/flash bar hadden toegevoegd hebben we ook meteen wat andere functies toegevoegd csrf() en validateToken().
Aan elk formulier moet nu een csrf token worden toegevoegd om het te laten werken. Probeer nu maar eens een post te verwijderen. Als het goed is krijg je de boodschap:
401 Geen toegang: CSRF-token mismatch error.
Om het weer te laten werken voegen we aan ons verwijder formulier de volgende regel toe
<?= csrf() ?>
De code voor de volledige verwijder button wordt dan:
Daarmee werkt onze verwijder button weer. En is het niet meer mogelijk om met externe scripts posts te verwijderen.
Als je F12 klikt en dan klikt op tabblad netwerk en dan een post verwijderd. Dan kun je bij de aanvraag zien wat er wordt meegestuurd. En dat is een flink lange _token.
Voeg de <?= csrf() ?> nu ook toe aan views/post-create.view.php, zodat deze ook weer werkt.
Als je meer wilt weten over csrf protectie kan je dat hier lezen.
Gegevens aanpassen
Het wordt nu tijd dat we een post ook kunnen aanpassen. Hiervoor hebben we een formulier nodig om een post aan te kunnen passen.
Het wijzigingsformulier wil je alvast invullen met de huidige gegevens dus het is belangrijk dat deze vooraf worden opgehaald uit de database.
Het process loopt alsvolgt:
Toon de betreffende post met button wijzigen (met id van de post)
Form verstuurd naar post-edit
Controller post-edit haalt de gegevens van de betreffende post op uit de database en stuurt deze naar de view
view-update toont de gegevens uit de database in een formulier met update button
Bij klik op update wordt formulier verstuurd naar controller: post-update
post-update doet een update op de database en stuurt terug naar /posts
We hebben nu alle verschillende database transacties achter de rug. Meestal geldt voor de meeste tabellen in de database dat je een view, create, update, delete nodig hebt. Dit wordt ook wel CRUD genoemd.
De volgende routes zijn daarvoor nodig
index (overzicht van alle posts) GET
view (overzicht van één post) GET
create (formulier om een post aan te maken) GET
edit (formulier om te wijzigen) GET
store (opslaan van de gegevens) POST
update (wijzigen van de gegevens) POST
delete (verwijderen van de gegevens) POST
Optioneel
Veel applicaties gebruiken meerdere methode dan GET en POST zoals PUT, PATCH, DELETE
Onze Router.php is daarop voorbereid alleen werkt het niet door in je form method='delete' mee te geven.
Wil je deze methodes wel gebruiken dan kan dit door in je form de functie method_put() of method_delete() te gebruiken. Let op dat de method op post komt te staan
Indien gebruik wordt gemaakt van een REST-request gaat het dan zo uit zien
URL
Method
Endpoint
Beschrijving
/posts
get
/controllers/posts/index.php
Overzicht van alle posts
/posts/create
get
/controllers/posts/create.php
Aanmaken nieuwe post
/posts
post
/controllers/posts/store.php
Opslaan van de nieuwe post
/posts/{id}
get
/controllers/posts/show.php
Bekijken van een post
/posts/{id}/edit
get
/controllers/posts/edit.php
Bewerken van een post
/posts/{id}
put
/controllers/posts/update.php
Opslaan van een bewerkte post
/posts/{id}
delete
/controllers/posts/destroy.php
Verwijderen van een post
Vooral als je met meerdere mensen aan een project werkt kan het handig zijn om je aan dit soort conventies te houden.
K Validatie
Er zijn twee vormen van validatie:
client side validatie
server side validatie
Client side validatie
Let op deze validatie heeft twee grote nadelen:
Kwetsbaarheid voor manipulatie: Client-side validatie vindt plaats op de computer van de gebruiker, wat betekent dat kwaadwillende gebruikers de validatieregels gemakkelijk kunnen omzeilen of uitschakelen door de client-side code aan te passen of uit te schakelen. Dit maakt het onbetrouwbaar voor het beveiligen van gevoelige gegevens of kritieke processen.
Browserafhankelijkheid: Client-side validatie kan verschillen tussen browsers.
Maar deze validatie is zeer eenvoudig toe te passen dus wel wenselijk om als extra beveiliging te gebruiken.
Door de required entity toe te voegen aan een form field maak je veld verplicht de browser zal een melding geven wanneer de gebruiker het veld leeg laat. (let op dit gebeurt niet met hele oude browsers)
Door het type in een input veld mee te geven wordt ook een validatie gedaan. In bovenstaand voorbeeld kunnen alleen geldige email adressen worden ingevoerd.
Daarnaast zijn nog een aantal Javascript validaties mogelijk. Maar die gaan te ver voor deze cursus.
Server side validatie
Bij server side validatie ga je valideren op het moment dat het formulier naar de server is verstuurd.
We zullen een voorbeeld uitwerken voor het opslaan van een nieuwe 'post'. Dit opslaan gebeurt in de controller post-store.php. Deze gaan we hieronder aanpassen voor validatie
Daarvoor starten we met het aanmaken van een lege $errors array. In deze array gaan we alle fouten opslaan die we tegen komen.
// in $errors worden de foutmeldingen opgeslagen$errors = [];if ($_POST != null) {
Daarna starten we met validatie. Hieronder kijken we of de tekst lengte van de titel niet gelijk aan 0 is. Indien dat wel zo is wordt een boodschap in de $errors array gezet.
// controleren of de titel is ingevuldif (strlen($_POST['title']) == 0) {
$errors['title'] = "Titel mag niet leeg zijn";
}
Indien er geen errors zijn ($errors is leeg) dan mag de post ingevoerd worden in de database.
if (empty($errors)) {
// invoeren van de gegevens in de database$db->query("INSERT INTO posts (title, content, user_id) VALUES (?, ?, ?)", [
$_POST['title'],$_POST['content'],63, // user_id hard coded wordt later vervangen door
]); //hier kan je alleen komen als de query goed is uitgevoerd flash("Post is opgeslagen", true, 3000); // doorsturen naar posts paginaheader("Location: /posts");
}
}
Als laaste helemaal onder aan het bestand verwijzen we door naar de view post-create.view.php. We geven hier de $errors mee zodat ze deze variabele in onze view kunnen gebruiken.
//er is geen post dus we laten het formulier zienview("post-create", [
'errors' => $errors
]);
We openen onze post-create.view.php en voegen onder het invoer veld van de titel een stukje code om een foutmelding te laten zien (we hebben de required bij de titel even weggelaten om de code te kunnen testen)
Indien $errors['title'] bestaat dan zal de foutmelding getoond worden zoals hieronder
Nu hebben we nog een probleem. Wanneer we een tekst hebben ingevoerd bij content en de titel hebben leeggelaten. Zal de foutmelding verschijnen, maar is de tekst van content ook leeg. Dit is niet wenselijk, dan moet de gebruiker de hele tekst opnieuw intypen. Een manier om dit te voorkomen zou het volgende kunnen zijn.
We vullen de textarea met hetgeen we zo juist verstuurd hebben. De ?? '' zal een legen waarden invullen indien er niets is verstuurd ($_POST['content'] niet bestaat)
Voor het input veld werkt dan door de entity value te vullen:
Bij de validatie zullen we vaak het zelfde willen controleren. Bijvoorbeeld of een verplicht veld gevuld is, of het werkelijk een email adres is, etc. Daarnaast willen we graag de omliggende spaties verwijderen. Natuurlijk kan je dan elke keer de volgende regels schrijven.
// spaties voor en na de invoer weghalen$_POST['title'] = trim($_POST['title']);// controleren of de titel is ingevuldif (strlen($_POST['title']) == 0) {
$errors['title'] = "Titel mag niet leeg zijn";
}
Een andere oplossing is om het Validator object te gebruiken. Dat werkt als volgt.
Eenmaal moet je het object inladen. Dit kan bovenaan in de controller waar je het validatie object wilt gebruiken.
Validator.php kan je hier downloaden. Maak dit bestand aan en zet het in de directory src
require "../src/Validator.php";
Indien je dezelfde validatie wilt doen zoals in het voorbeeld hierboven kan je nu doen
if (!Validator::required($_POST['title'])) {
$errors['title'] = "Titel mag niet leeg zijn";
}
In de Validator wordt de static method required aangeroepen. Hier wordt de variabele ingestopt die gevalideerd moet gaan worden. Het Validator object haalt de omliggende spaties weg en geeft true indien alles goed gaat. Wij willen alleen als het misgaat de $errors vullen vandaar de ! voor Validator
Het Validator object heeft onderstaande validatie mogelijkheden
Validator::required($var) $var is niet leeg
Validator::integer($var) $var is een geheel getal
Validator::length($var,$min,$max) $var heeft een string lengte tussen $min en $max (inclusief)
Validator::email($var) $var is een geldig email adres
Validator::url($var) $var is een geldige url
Validator::date($var) $var is een geldige datum
Validator::min($var,$min) $var is hoger of gelijk aan $min
Validator::max($var,$max) $var is lager of gelijk aan $max
Validator::between($var,$min,$max) $var zit tussen $min en $max (inclusief)
Validator::in($var, $array) $var komt voor in de array $array
Validator::notIn($var,$array) $var komt niet voor in de array $array
Validator::regex($var,$regex) $var voldoet aan de gegeven regex expressie
Voorbeelden genoeg, je kan zelf er vanalles aan toevoegen
Let op! Het gebruiken van het Validator object is er om het leven makkelijker te maken. Als je de validatie liever steeds zelf schrijft is dat uiteraard ook goed.
L Inloggen
Stappen plan
Het inloggen gaat er schematisch alsvolgt uit zien.
Succesvol ingelogd
Als alles goed is verlopen verwijzen we de gebruiker door middel van een doorverwijzing naar de pagina waar we de gebruiker na het inloggen heen willen sturen.
PHP sessions
Session
Het 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.
PHP regelt op de achtergrond dat er een session-cookie wordt geplaatst in de browser van de bezoeker. Hier staat een code in die overeenkomt met een code op de server. Alle gegevens die wij in $_SESSION opslaan blijven op de server staan en niet in de browser.
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). Door de gegevens in de $_SESSION te zetten is het niet meer nodig om elke keer dat een bezoeker van pagina wisselt de gebruikergegevens uit de database op te halen.
Gebruikelijk is om gebruikergegevens in $_SESSION['user'] te zetten. Meestal is dat een array met alle gegevens van de user oa. name en email.
Login view
We hebben een inlogpagina nodig. Daarvoor maken we een bestand views/login.view.php aan. Uiteraard start dit bestand weer met onderstaande code.
De eerste brengt ons bij het login scherm. De tweede is wanneer de gebruiker zijn email en wachtwoord heeft ingevuld en daarmee probeert in te loggen. Herkenbaar aan de 'post'
Login controller
Nu gaan we de login controller aanmaken, deze doet onderstaande stappen:
- 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 mislukken dan wordt de gebruiker teruggestuurd naar het inlog formulier.
Als alle stappen goed zijn doorlopen wil je onthouden dat de gebruiker is ingelogd. Zodat hij niet bij het surfen door je website bij elke pagina opnieuw moet inloggen.
Voorbeeld van login controller (controllers/login.php)
<?phprequire "../src/Validator.php";
if (!Validator::required($_POST['email'])) {
$errors['login'] = "Email mag niet leeg zijn";
}
if (!Validator::required($_POST['password'])) {
$errors['login'] = "Wachtwoord mag niet leeg zijn";
}
if (empty($errors)) {
$db = new Database();
//gebruiker ophalen uit de database$user = $db->query("SELECT * FROM users WHERE email = ? LIMIT 1", [
$_POST['email']
])->fetch();
// als er een gebruiker is gevondenif ($user) {
//wachtwoord controlerenif (password_verify($_POST['password'], $user['password'])) {
//gebruiker in session zetten, maar het wachtwoord laten we wegunset($user['password']);
$_SESSION['user'] = $user;
flash("Welkom terug " . $user['name'], true);
// doorsturen naar de home pagina (of pas aan)
header("Location: /");
} else {
$errors['login'] = "Inloggegevens zijn niet correct";
}
} else {
$errors['login'] = "Inloggegevens zijn niet correct";
}
}
view("login", [
'errors' => $errors,
]);
Bij de functie password_verify(...) wordt het verstuurde wachtwoord vergeleken met de wachtwoord HASH uit de database.
Zie ook het commentaar in de code. Dit maakt het zelfs leesbaar als je niet veel programmeer ervaring hebt.
Foute login tonen
In login.view.php moeten we nog wel de login errors tonen. Dat kunnen we boven ons inlogformulier plaatsen, zie hieronder een voorbeeld
Bij het inloggen kan je hele specifieke foutmeldingen geven. Zoals het email adres komt niet voor. Het wachtwoord is onjuist. Beter is om een algemene foutmelding te geven. Dit maakt het moeilijker voor hackers.
Wachtwoorden
In de voorgevulde database is het wachtwoord van alle gebruikers 'secret'. Van de gebruikers waarbij het wachtwoord leesbaar is zullen nooit kunnen inloggen.De password_verify(...) functie vergelijkt het ingevoerde wachtwoord met de wachtwoord HASH uit de database. Dus geen HASH in de database betekend niet kunnen inloggen.
Je ziet dat de password HASH steeds verschillend is, toch hebben al die gebruikers hetzelfde wachtwoord. Aan de hand van de HASH kan het originele wachtwoord niet worden achterhaald. Dat voorkomt dat wachtwoorden op straat komen te liggen na een hack.
Uitloggen
Wanneer je bovenstaande stappen hebt genomen. Kan je in middels inloggen op 'code wizards'.
Je krijgt op de pagina alleen nog niet te zien dat je ingelogd bent. Om dit kenbaar te maken is het netjes om rechts boven aan te geven dat de gebruiker is ingelogd. In plaats van de tekst inloggen.
In view/parts/navigatie-menu.view.php kunnen we dit regelen. Onderstaande stukje toont de tekst login die klikbaar is.
Eigenlijk willen we deze tekst alleen tonen als een gebruiker niet is ingelogd. Bij wel ingelogd willen we bijvoorbeeld de naam van de gebruiker tonen. Of eventueel de tekst uitloggen. We kunnen hier een IF...ELSE... structuur voor gebruiken.
Omdat we in de applicatie vaak willen kijken of een gebruiker is ingelogd, is het handig om hier een functie voor te hebben. Wat andere functies die handig zijn rondom de login voegen we ook meteen toe. Copy paste onderstaande code in functions.php.
// Heeft de ingelogde gebruiker een bepaalde rolfunction hasRole($role): bool
{
return ($_SESSION['user'] ?? false) and ($_SESSION['user']['role'] ?? '') == $role;
}
//betreft het een ingelogde gebruiker? (afkorting authenticatie)function auth(): bool
{
return ($_SESSION['user']['id'] ?? false);
}
// geeft de gegevens van de ingelogde gebruiker terug// te gebruiken: user()->emailfunction user(): ?object
{
return $_SESSION['user']
? (object)$_SESSION['user']
: null;
}
Voor ons is de functie auth() handig om te gebruiken deze geeft een boolean terug dus true (wel ingelogd) of false (niet ingelogd)
Uiteraard moeten we in router.php de /logout route nog aanmaken. Deze komt binnen als get.
Posts van een user
In een vorige paragraaf hebben we een pagina gemaakt waar posts konden worden toegevoegd. Nu we een inlog mogelijkheid hebben kunnen we de ingevoerd post koppelen aan de ingevoerde gebruiker.
In controllers/post-store.php stond vanaf regel 32 de volgende code die een post invoert in de database:
$db->query("INSERT INTO posts (title, content, user_id) VALUES (?, ?, ?)", [
$_POST['title'],
$_POST['content'],
63, // user_id hard coded wordt later vervangen door
]);
De 63 was het user_id, op deze plaats willen we nu het id van de ingelogde gebruiker plaatsen. Dit kan eenvoudig door deze uit de session te halen.
$db->query("INSERT INTO posts (title, content, user_id) VALUES (?, ?, ?)", [
$_POST['title'],
$_POST['content'],
$_SESSION['user']['id'], // user_id uit de session
]);
Ook kunnen we de helper functie user() gebruiken om het probleem op te lossen (zelfde resultaat)
Het is nu alleen nog voor ingelogde gebruikers mogelijk om naar /post-create te gaan.
Toch staat de link nu ook nog bij niet ingelogde gebruikers op de pagina waar de posts te zien zijn. Dus daar moeten we ook een kleine aanpassing doen.
Om de link naar /post-create zetten we een if(auth()): dus alleen als je ingelogd bent zal de link op het scherm worden getoond.
User tonen bij een post
Misschien wil je ook nog tonen wie een post heeft geschreven. Uiteraard kan dat ook, daarvoor moeten we de query die alle posts ophaalt iets aanpassen.
$result = $db->query("SELECT posts.*, users.name FROM posts, users WHERE posts.user_id = users.id AND title LIKE ? or content LIKE ?", [
"%{$_GET['search']}%",
"%{$_GET['search']}%"
])->fetchAll();
En indien er niet wordt gezocht
$result = $db->query("SELECT posts.*, users.name FROM posts, users WHERE posts.user_id = users.id")->fetchAll();
We maken dus een join tussen de posts tabel en de user tabel. Hiermee worden ook alle user gegevens opgehaald. In de select geven we aan dat we alleen de name van de user willen hebben.
Onder de content in de view kan je de naam van de gebruiker bijvoorbeeld tonen met de volgende code
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 onder de require van Database.php.
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([
'name' => 'Piet 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);
//naam wijzigen
$user->name= 'John Doe';
//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;
}
}
Let op dat er voor bovenstaande code wel velden in de database moeten bestaan met de namen 'voornaam', 'tussenvoegsel' en 'achternaam' deze velden zijn in onze default database niet aanwezig. De code zal dan ook fouten opleveren bij aanroep.
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();
}
//in plaats van 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 = [];
}
}
Model User
We gaan nu een Model User aanmaken. Hiervoor is niet heel veel code nodig.
Maak een nieuwe bestand: app/models/User.php
Met onderstaande code
<?phpclass User extends Model
{
protected $table = 'users';
}
Meer code is hier voorlopig niet nodig Belangrijk is dat de tabelnaam wordt gegeven.
Let op dat onze User model nog wel ingeladen moet worden. Doet dit in je index.php onder de require van Database.php
In de view kan je dan $user gebruiken als variabele
N Drop down menu (optioneel)
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
Deze stap is hoogst waarschijnlijk al gedaan. Zo niet voeg de javascript file toe in onze header van elke pagina dus in views/parts/header.view.php
Bij gebruik van tailwind is het nu mogelijkom in je menu onderstaande code te gebruiken.
In onderstaande code wordt ervan uitgegaan dat het inloggen reeds werkt.
Als je zoekt naar iets zijn we tegenwoordig gewend om de resultaten direct te krijgen terwijl we aan het typen zijn. Dit kan niet met een eenvoudig formulier met submit button.
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 en name bestaan.
Stap 1
Zorg ervoor dat je parts/header.view.php in de <head> de volgende scripts toevoegd. Een iets andere versie zal waarschijnlijk ook werken.
In de map controllers maken we een nieuw map met de naam 'api'
Stap 2
In onze controller directory maken we een bestand controllers/api/users-search.php met de volgende inhoud
<?php
//gebruik van database object
$db = new Database();
//users opzoeken die aan onze zoek query voldoen en deze in $users zetten
$users = $db->query(
"SELECT id, name, email
FROM users
WHERE name LIKE ?
LIMIT 10", [
"%" . $_GET['name'] . "%" //zoeken naar alles wat er op lijkt] //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
if (auth()) { //alleen als je ingelogd bent kan je dit doen$route->get('api/users-search', "controllers/api/users-search.php");$route->get('users', "views/users-search.view.php");
}
Stap 4
We maken een bestand views/users-search.view.php
<?php
view("parts/header", ['title' => 'Zoek gebruikers']);
view("parts/navigatie-menu");?><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">Naam</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.name"></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('/api/users-search?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
view("parts/footer");
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.
Content-Security-Policy (optioneel)
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 en de volgende header tag toe. Doe nadat de functions.php is ingeladen.
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
Afbeelding uploaden
Formulier
Inleiding
In deze paragraaf zal stap voor stap besproken worden hoe je een afbeelding kan uploaden naar de server en dit kan opslaan in de database. Hiervoor gebruiken we de case: de functionaliteit om een profielfoto toe te voegen aan een user account.
Formulier
Maak een view waarin je het upload formulier kan plaatsen. Bv profiel.view.php
Belangrijk is dat je aan je form enctype="multipart/form-data" toevoegd. Dit maakt het mogelijk om bestanden te versturen.
Daarna kan je eenvoudig de <input type="file" .. gebruiken als veld waar je de afbeelding kan selecteren.
Router
Als action hebben we /profielfoto dus daar moeten we een route voor aanmaken
In dit bestand gaan we de foto toevoegen. We gaan daarbij de foto in een map plaatsen en in de database slaan we het pad op waar de foto opgeslagen is. Omdat afbeeldingen veel ruimte innemen is dit de meest praktische manier van opslaan.
De bestanden die worden geupload zijn terug te vinden in de variabele $_FILE. De naam van de profielfoto kan je met $_FILE['foto']['name'] vinden.
Belangrijk is dat je een controle doet of het een afbeelding betreft. Anders zou het ook een gevaarlijk virus kunnen zijn.
Indien het een afbeelding is mag deze opgeslagen worden in de afbeeldingen map. Daarna wil je het pad naar de afbeelding toevoegen in de database. Zodat je weet bij welke gebruiker de afbeelding hoort. Vaak is het handig om de naam van de afbeelding aan te passen naar een hash, zodat het bijna onmogelijk is om dubbele namen te krijgen.
Met wat zoeken op internet is deze code snel te vinden. Eventueel kan je hier ook heel goed een AI bot voor gebruiken.
Hieronder een voorbeeldje. Let op de code is niet getest.
$uploadDirectory = 'images/'; // map waarin de afbeeldingen worden opgeslagen
if (isset($_FILES["foto"])) { //wordt er een afbeelding verstuurd, dan ...
$errors = []; //hier gaan we fouten in opslaan
//de file extensie ophalen
$imageFileType = strtolower(pathinfo($uploadFile,PATHINFO_EXTENSION));
// Controleer of het bestand een afbeelding is
if(getimagesize($_FILES["afbeelding"]["tmp_name"])) {
$errors['foto'] = "Bestand is geen afbeelding.";
}
// Controleer de bestandsgrootte
if ($_FILES["afbeelding"]["size"] > 5000000) {
$errors['foto'] = "Sorry, het bestand is te groot.";
}
// Toegestane bestandstypen
$allowedExtensions = array("jpg", "jpeg", "png", "gif");
if (!in_array($imageFileType, $allowedExtensions)) {
$errors['foto'] = "Sorry, alleen JPG, JPEG, PNG en GIF bestanden zijn toegestaan.";
}
if(!empty($error)){ //fouten dus stoppen en terugsturen naar formlier
view('profielfoto',['errors'=>$errors]);
} else {
//uploadFile is de naam zoals wij de afbeelding uiteindelijk gaan opslaan
$uploadFile = $uploadDirectory . hash(date().random_int(1,1000000)).".".$imageFileType;
move_uploaded_file($_FILES["foto"]["tmp_name"], $uploadFile);
//de afbeelding is nu opgeslagen, nu gaan we deze ook in de database opslaan bij de gebruiker
$db = new Database();
$db->query("UPDATE users set profielfoto=? where id=?",[
$uploadFile,
user()->id, // de op dat moment ingelogde gebruiker
];
//PAS AAN: stuur de gebruiker naar de gewenste pagina
view('....');
}
die();
}
view('profielfoto',['errors'=>['foto'=>'Geen foto meegestuurd']);
X Samenvatting
Waar komt wat
Elk verzoek aan onze website volgt een vaste route. Schematisch is dat in onderstaand overzicht te zien.
Request
Klikken op een link / button
Versturen van een formulier
Intypen van een url in de browser
Index.php
Inladen van bestanden die nodig zijn voor de applicatie
configuratie bestand
functies
Objecten
Router (router.php)
verzoek bv /home doorsturen naar juiste controller of view
Controller
Logica, bv interactie met de database (gegevens ophalen, invoeren, wijzigen, verwijderen)
View
Opbouwen van de html pagina
Content
Formulieren
Navigatie menu
etc
CRUD voorbeelden create-read-update-delete
In de volgende blokken wordt een voorbeeld gegeven hoe gegevens te tonen, invoeren, wijzigen en verwijderen.
In het voorbeeld wordt gebruik gemaakt van een database tabel 'items'.
Let op voor update en delete worden de methode 'PUT'en 'DELETE' gebruikt zorg evoor dat je hiervoor de meest recente versie van functions.php gebruikt.
De url http://localhost/item/4 zal dan voor id=4 invullen. Omdat de method 'get' wordt gebruikt is in de controller $_GET['id'] beschikbaar.
In controller items-show.php
gegevens ophalen uit de database
gegevens meegeven aan view
<?php
//initialiseren van database class
$db = new Database();
//view met item teruggegeven
view('items-show', [
'item' => $db->query("SELECT * FROM items WHERE id=?", [$_GET['id']])->fetch()
]);
In de view items-show.view.php
Gebruik htmlspecialchars(...) om ook speciale teksens als quotes etc te laten werken
<?php
view("parts/header", ['title' => 'item ' . $item['id']]);
view("parts/navigatie-menu");
?>
<h1 class="text-3xl my-4">Item <?= htmlspecialchars($item['naam']) ?></h1>
<p class="my-4">Elke veld van 'item' kan hier nu worden gebruikt<br>
id: <?= $item['id'] ?><br>
naam: <?= htmlspecialchars($item['naam']) ?><br>
beschrijving: <?= htmlspecialchars($item['beschrijving']) ?><br>
prijs: <?= $item['prijs']; ?><br>
</p>
<?php
view("parts/footer");
Testen van je code kan met http://localhost/items/1
<?php
//initialiseren van database class
$db = new Database();
//view met item teruggegeven
view('items-index', [
'items' => $db->query("SELECT * FROM items")->fetchAll()
]);
In de view items-index.view.php
Gebruik htmlspecialchars(...) om ook speciale teksens als quotes etc te laten werken
<?php
view("parts/header", ['title' => 'items']);
view("parts/navigatie-menu");
?>
<h1 class="text-3xl my-4">Items</h1>
<p class="my-2">Hieronder een lijst met alle items</p><br>
<ul class="ml-4">
<!-- loop door alle items heen -->
<?php foreach ($items as $item) : ?>
<li><?= $item['id'] ?> -
<?= htmlspecialchars($item['naam']) ?> -
<?= htmlspecialchars($item['beschrijving']) ?> -
<?= $item['prijs'] ?> -
Link naar item:
<a href="/items/<?= $item['id'] ?>" class="text-indigo-600">
<?= $item['naam'] ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php
view("parts/footer");
<?php
/*
Indien je selectboxen hebt moet je hier waarschijnlijk gegevens
uit de database ophalen om mee te geven aan de view.
bv:
$db = new Database();
view('items-create',[
'categories' => $db->query("SELECT * FROM categories")->fetchAll()
]);
*/
view('items-create');
In view items-create.view.php
Hier komt een formulier met die kan worden verstuurd naar de route '/items' d.m.v. method=post
Versie zonder validatie en opnieuw invullen van velden
<?php
//validatie van de gegevens
require "../src/Validator.php";
$errors = []; //lege array voor de foutmeldingen
if (!Validator::required($_POST['naam'])) {
$errors['naam'] = "Naam is verplicht";
}
if (!Validator::length($_POST['naam'], 0, 50)) {
$errors['naam'] = "Naam mag niet langer zijn dan 50 tekens";
}
if (!Validator::between($_POST['prijs'], 0.01, 1000000)) {
$errors['naam'] = "De prijs moet tussen 0,01 en 1.000.000 liggen";
}
//voor alle validatie regels zie cheat-sheet
//indien niet oké terugsturen naar de create pagina met foutmeldingen
if (!empty($errors)) {
view('items-create', [
'errors' => $errors
]);
exit();
}
//indien validatie oké dan gegevens opslaan in de database
$db = new Database();
$db->query("INSERT INTO items (naam,beschrijving,prijs) VALUES (:naam,:beschrijving,:prijs)", [
'naam' => $_POST['naam'],
'beschrijving' => $_POST['beschrijving'],
'prijs' => $_POST['prijs']
]); //id veld staat op auto increment dus hoeft niet meegegeven te worden
flash("Item " . htmlspecialchars($_POST['naam']) . " is toegevoegd");
//terugsturen naar de index pagina
header("location: /items");
//terugsturen naar de detail pagina van het item
//header("location: /items/" . $db->lastInsertId());
<?php
//validatie van de gegevens
require "../src/Validator.php";
$errors = []; //lege array voor de foutmeldingen
if (!Validator::required($_POST['naam'])) {
$errors['naam'] = "Naam is verplicht";
}
if (!Validator::length($_POST['naam'], 0, 50)) {
$errors['naam'] = "Naam mag niet langer zijn dan 50 tekens";
}
if (!Validator::between($_POST['prijs'], 0.01, 1000000)) {
$errors['naam'] = "De prijs moet tussen 0,01 en 1.000.000 liggen";
}
//voor alle validatie regels zie cheat-sheet
//indien niet oké terugsturen naar de create pagina met foutmeldingen
if (!empty($errors)) {
view('items-edit', [
'errors' => $errors,
'item' => $_POST,
]);
exit();
}
//wijzigen doorvoeren
$db = new Database();
$db->query("UPDATE items SET naam = :naam, beschrijving = :beschrijving, prijs = :prijs WHERE id = :id", [
'naam' => $_POST['naam'],
'beschrijving' => $_POST['beschrijving'],
'prijs' => $_POST['prijs'],
'id' => $_POST['id']
]);
flash("Item " . htmlspecialchars($_POST['naam']) . " is gewijzigd");
//terugsturen naar de detail pagina van het item
header("location: /items/" . $_POST['id']);
Test wijzigen ga naar http://localhost/items/1/edit
<?php
$db = new Database();
$db->query("DELETE FROM items WHERE id = :id", [
'id' => $_POST['id']
]);
flash("Item is verwijderd");
//doorsturen naar de index pagina
header("location: /items");
Uiteraard is het prettig als er voordat er een verwijder actie wordt gedaan een bevestiging wordt gevraagd.
Je zou dit bv kunnen realiseren door gebruik te maken van een view delete-button.view.php
We plaatsen deze view in parts, want deze zou overal in de applicatie gebruikt moeten kunnen worden. De view heeft één variabele nodig dat is de action. Eventueel zou je nog meer variabele kunnen toevoegen. Bijvoorbeeld de tekst die wordt getoond bij de bevestiging.
Code voor views/parts/delete-button.view.php (eenmalig)
Tijdens de werkelijke toets zal het basis project al in de root directory van USBwebserver staan. En zal de database ook reeds aanwezig zijn. Voor de proeftoets moet je dit zelf gaan doen.
Download het toets_project van github. Sleep de hele directory toets_project naar je USBwebserver zodat deze is uitgepakt. Pas daarna bij settings het path aan zodat toets_project wordt geladen.
Bij deze opdracht gaan we een formulier maken om een afspraak te maken. De gemaakte afspraak moet worden ingevoerd in de tabel 'afspraken' van de agenda database. Neem onderstaande stappen om dit voor elkaar te krijgen.
Maak drie bestanden aan:
controllers/afspraak-create.php (1p) (Alle klanten selecteren en afspraak-create.view gebruikt)
Bij deze opdracht gaan we een pagina maken waarop je op datum kan gaan zoeken naar alle afspraken op de betreffende datum. De resultaten worden getoond inclusief een verwijder button. Met deze button kan je de afspraak verwijderen.
Maak een controller controllers/afspraken.php (1p) (juiste afspraken ophalen uit de database)
Maak een controller controllers/afspraak-destroy.php (1p) (verwijderen afspraak)
Maak een view views/afspraken.view.php (1p)
zoekveld
zoek resultaten
Maak een link in het menu naar /afspraken (2p)
Maak de benodigde routes aan (2p)
Zoek formulier
method='get' (1p)
Eén veld 'datum' (type='date') (1p)
Button met de tekst 'zoek' toe (1p)
De resultaten na het zoeken:
Datums zijn gesorteerd op afspraaktijd (2p)
De velden naam, van en tot moeten minimaal worden getoond. (15p)
Achter een resultaat staat een button om de afspraak te kunnen verwijderen (3p)
Na het klikken op de verwijder afspraak button, wordt de afspraak verwijderd en worden de afspraken weer getoond. (8p)
Deze opdracht is minder voorgekauwd. Het gaat om dat alle functionaliteiten werken en veilig zijn.
Maak iets om een klant te kunnen selecteren en wanneer een klant geselecteerd is de klantgegevens aangepast kunnen worden. (alle velden behalve het id van de klant) Uiteraard is er de juiste validatie en worden fouten getoond. En bij geen fouten worden de gegevens aangepast in de database.
Het kunnen selecteren van een klant kan bijvoorbeeld met een selectbox of met een lijst van alle klanten. Een lijst is daarbij de makkelijkste optie. Je hoeft hier geen zoek functionaliteiten bij te maken.
Het arrangement V6 p1-2 PHP-MySQL webapplicatie vanaf null is gemaakt met
Wikiwijs van
Kennisnet. Wikiwijs is hét onderwijsplatform waar je leermiddelen zoekt,
maakt en deelt.
Dit lesmateriaal is gepubliceerd onder de Creative Commons Naamsvermelding 4.0 Internationale licentie. Dit houdt in dat je onder de voorwaarde van naamsvermelding vrij bent om:
het werk te delen - te kopiëren, te verspreiden en door te geven via elk medium of bestandsformaat
het werk te bewerken - te remixen, te veranderen en afgeleide werken te maken
voor alle doeleinden, inclusief commerciële doeleinden.
Van dit lesmateriaal is de volgende aanvullende informatie beschikbaar:
Toelichting
In deze cursus leer je stap voor stap een PHP-MySQL webapplicatie te bouwen. Daarnaast zal er aandacht zijn voor diverse onderwerpen die bij web-development van belang zijn.
Eindgebruiker
leerling/student
Moeilijkheidsgraad
gemiddeld
Trefwoorden
crud, mvc, mysql, php, usbwebserver
V6 p1-2 PHP-MySQL webapplicatie vanaf null
nl
JEL informatica
2024-12-02 13:44:03
In deze cursus leer je stap voor stap een PHP-MySQL webapplicatie te bouwen. Daarnaast zal er aandacht zijn voor diverse onderwerpen die bij web-development van belang zijn.
Leeromgevingen die gebruik maken van LTI kunnen Wikiwijs arrangementen en toetsen afspelen en resultaten
terugkoppelen. Hiervoor moet de leeromgeving wel bij Wikiwijs aangemeld zijn. Wil je gebruik maken van de LTI
koppeling? Meld je aan via info@wikiwijs.nl met het verzoek om een LTI
koppeling aan te gaan.
Maak je al gebruik van LTI? Gebruik dan de onderstaande Launch URL’s.
Arrangement
IMSCC package
Wil je de Launch URL’s niet los kopiëren, maar in één keer downloaden? Download dan de IMSCC package.
Wikiwijs lesmateriaal kan worden gebruikt in een externe leeromgeving. Er kunnen koppelingen worden gemaakt en
het lesmateriaal kan op verschillende manieren worden geëxporteerd. Meer informatie hierover kun je vinden op
onze Developers Wiki.