V6 p1-2 PHP-MySQL webapplicatie vanaf null (2024)

Leerdoelen

  • Installeren van webserver
  • Gebruik databasemangement programma (Heidi-SQL)
  • Aanmaken van een database
  • MVC-model (Model-View-Controller)
  • SQL-injecties
  • PHP basis synthax
  • Tailwindcss
  • Objecten
  • POST-GET
  • Gestructureerd werken
  • HTML-formulieren
  • CRUD (create-read-update-delete)

De uitwerkingen van de cursus zijn op een github pagina te vinden

Bronnen

Uitwerkingen opdrachten bekijken en downloaden

Download USBwebserver hier

Heidi-SQL (kies voor portable 64-bits) hier

USB-webserver

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

V6 p1-2 PHP-MySQL webapplicatie vanaf null (1)

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
V6 p1-2 PHP-MySQL webapplicatie vanaf null (2)

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???

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.

Download hier

Extensies installeren

Het is aanbevollen om onderstaande extensies daarna te installeren

  • Intelephense

  • Tailwind CSS IntelliSense

  • Format HTML in PHP

  • 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

V6 p1-2 PHP-MySQL webapplicatie vanaf null (3)

Kies de root directory van USBwebserver

VScode is er nu klaar voor

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".

V6 p1-2 PHP-MySQL webapplicatie vanaf null (4)

Onderstaand scherm zal verschijnen.

  1. laat default of wijzig in "localhost"
  2. gebruiker: root wachtwoord: usbw (bij gebruik usbwebserver)
  3. Klik op opslaan
  4. Klik op openen

V6 p1-2 PHP-MySQL webapplicatie vanaf null (5)

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.

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

V6 p1-2 PHP-MySQL webapplicatie vanaf null (6)

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.

<!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></body></html>

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.

Menu

Het zou prettig zijn als we een menu hebben om tussen onze drie pagina's te kunnen navigeren.

We kunnen dit doen door gebruik te maken van HTML a tag.

Een voorbeeld van een link naar de home page (index.php) ziet er zo uit

<a href="/index.php">Home</a>

Het href attribuut van de a tag geeft aan naar welke pagina je wordt doorverwezen. Wat binnen de a tag staat ziet de gebruiker op het scherm.

We kunnen dit herhalen voor elke pagina. Voor het overzicht zetten we dit binnen een HTML nav tag.

<nav> <a href="/index.php">Home</a> <a href="/contact.php">Contact</a> <a href="/about.php">About</a></nav>

Opdracht D3 menu (eenvoudig)

Plaats het menu op alle drie de pagina's in de body boven de h1 tag

Herhaling van code voorkomen

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.

navigatie-menu.php

<nav> <a href="/index.php">Home</a> <a href="/contact.php">Contact</a> <a href="/about.php">About</a></nav>

Op onze drie pagina's vervangen we het HTML menu door een stukje PHP code

<?phprequire "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.

Herhaling van HTML header

Op onze drie pagina's is nog meer herhaling te zien. Zo zien we op elke pagina de start van HTML staan

<!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>Home</title></head><body>

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)

<?phprequire "header.php";require "navigatie-menu.php";?>

Foutmelding

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:

<?php$title="Home";require "header.php";require "navigatie-menu.php";?>

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.

Opdracht D6 footer.php toevoegen

Elke pagina eindigd met

</body></html>

Misschien willen we hier ook wel een pagina afsluiting aan toe voegen footer. (komt later)

Maak een bestand footer.php stop hier bovenstaande code in. En zorg ervoor dat elke pagina een require "footer.php"; krijgt.

<?phprequire "footer.php";

Omdat er na require "footer.php"; geen code meer komt, is het niet noodzakelijk om met een ?> af te sluiten. Dit mag natuurlijk wel.

Opmaak

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 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 <header> </header>

<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>

Nu kunnen we tailwindcss gaan gebruiken.

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.

Hieronder de inhoud van tailwind.config.js

module.exports = { content: ["./**/*.{html,js}"], theme: {  extend: {},  },  plugins: [],} 

Navigatie menu

Hieronder de code voor ons nieuwe menu. Uiteraard mag je het aanpassen naar eigen wens. Denk aan kleuren, logo etc.

<nav class="bg-gray-500"> <div class="flex justify-between items-center"> <div class="flex justify-start items-center text-xl space-x-4"> <a href="index.php" class="flex items-center"> <img src="images/wizard-logo.png" alt="wizard" class="h-10 p-2"> <span class="font-bold">Code Wizards</span> </a> <a href="index.php" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Home</a> <a href="contact.php" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Contact</a> <a href="about.php" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">About</a> </div> <div class="justify-end"> <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Login</a> </div> </div></nav>

In het menu wordt een logo gebruik deze. Zet je logo in een map images. Je kan het logo hier downloaden. (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.

Geef de drie pagina's de gewenste opmaak

Index.php als beginpunt

Het zou mooi als elk web verzoek op één bestand zou 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.

localhost/home => home.php
localhost/about => about.php

etc...

Opdracht E1 .htaccess

Stap 1

Geef het bestand index.php de naam home.php

Stap 2

En maak een leeg bestand index.php aan.

Stap 3

Maak een bestand .htaccess met onderstaande code en plaats deze in je webroot

De inhoud van dit bestand hoef je niet te kennen. Het is een stukje configuratie voor de webserver.

<IfModule mod_rewrite.c> <IfModule mod_negotiation.c> Options -MultiViews -Indexes </IfModule> RewriteEngine On # Handle Authorization Header RewriteCond %{HTTP:Authorization} . RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] # Redirect Trailing Slashes If Not A Folder... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} (.+)/$ RewriteRule ^ %1 [L,R=301] # Send Requests To Front Controller... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L]</IfModule>

$_SERVER variabele

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 gaan we nu gebruiken.Let op onderstaande code is best ingewikkeld. Als je het niet helemaal begrijpt geen zorgen. Copy-paste is genoeg.

index.php

<?phpvar_dump($_SERVER);die();

Als we nu naar http://localhost gaan dan krijg je een hoop informatie op het scherm. Alleen niet zo goed leesbaar. we kunnen hier de HTML -tags <pre> </pre> omheen zetten om het iets leesbaarder te maken.

<pre><?phpvar_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

Probeer maar

<pre><?phpvar_dump(parse_url($_SERVER['REQUEST_URI']));die();?></pre>

Als je naar http://localhost/home?id=20 gaat

array(2) { ["path"]=> string(17) "/home" ["query"]=> string(5) "id=20"}

Wij willen nu alleen 'path' hebben. Dan kan eenvoudig door

<pre><?phpvar_dump(parse_url($_SERVER['REQUEST_URI'])['path']);die();?></pre>

Met als resultaat

string(17) "/home"

Als we nu ook nog de / ervoor weghalen dan hebben we 'home' over.

Het weghalen van de / kan door de trim() functie.

$uri = trim(parse_url($_SERVER['REQUEST_URI'])['path'], "/");

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 .

<?php$uri = trim(parse_url($_SERVER['REQUEST_URI'])['path'], "/");

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

<nav class="bg-gray-500"> <div class="flex justify-between items-center"> <div class="flex justify-start items-center text-xl space-x-4"> <a href="/" class="flex items-center"> <img src="images/wizard-logo.png" alt="wizard" class="h-10 p-2"> <span class="font-bold">Code Wizards</span> </a> <a href="/" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Home</a> <a href="contact" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Contact</a> <a href="about" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">About</a> </div> <div class="justify-end"> <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Login</a> </div> </div></nav>

Opdracht E3 configuratie bestand

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.

config.php

Maak een bestand config.php in de webroot

<?phpreturn [ 'app' => [ 'env' => 'dev', 'name' => 'Code Wizards', 'email' => 'info@code-wizards.nl', ]];

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

http_response_code(404);

We kunnen onze index.php eindigen met

http_response_code(404);require "404.php";die();

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";

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

V6 p1-2 PHP-MySQL webapplicatie vanaf null (7)

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

V6 p1-2 PHP-MySQL webapplicatie vanaf null (8)

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.

V6 p1-2 PHP-MySQL webapplicatie vanaf null (9)

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

<?php$title = "Home";require __DIR__ . "/../../app/views/home.view.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

<?phprequire __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><?phprequire __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.

Om alles te laten werken is het belangrijk dat we bij de settings van USBWebserver het path aanpassen naar {path}/root/webroot

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

<?phpfunction optellen($getal1, $getal2){ return $getal1 + $getal2;}echo optellen(1,2);

PHP if-else

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

<a href="/contact" class="<?= isUri("contact") ? 'underline ' : '' ?>text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Contact</a>

In de class voegen we deze code toe

<?= isUri("contact") ? 'underline ' : '' ?>

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.

//handige functiesrequire __DIR__ . "/../src/functions.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.

trim(parse_url($_SERVER['REQUEST_URI'])['path'], "/")

Schrijf de functie

function isUri(string $uri){ if ( ... hier komt jou code ...) { return true; } return false;}

PHP foreach-loop

We kunnen een array met meerdere elementen doorlopen met een foreach loop

$array = ['ma'=>'maandag','di'=>'dinsdag','wo'=>'woensdag','do'=>'donderdag','vr'=>'vrijdag']; // key => valueforeach ($array as $key => $value){ echo $value;}

Plak de code in index.php en voeruit om te testen.

Zal op het scherm schrijven: maandagdinsdagwoensdagdonderdagvrijdag

In de array heb je altijd een combinatie van een key en een value.

Foreach tussen je HTML code

Hieronder een voorbeeld van een foreach die een selectbox maakt waarin je een werkdag kan selecteren

<?php$dagen = ['ma' => 'maandag', 'di' => 'dinsdag', 'wo' => 'woensdag', 'do' => 'donderdag', 'vr' => 'vrijdag']; // key => value?><label id="dag">Selecteer een dag</label><select id="dag" name="dag"> <?php foreach ($dagen as $afkorting => $dag): ?> <option value="<?= $afkorting ?>"><?= $dag ?></option> <?php endforeach; ?></select>

Dit is de output

<label id="dag">Selecteer een dag</label><select id="dag" name="dag"> <option value="ma">maandag</option> <option value="di">dinsdag</option> <option value="wo">woensdag</option> <option value="do">donderdag</option> <option value="vr">vrijdag</option> </select>

V6 p1-2 PHP-MySQL webapplicatie vanaf null (10)

Opdracht G2 dd(...)

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.

<a href="/" class="flex items-center"> <img src="images/wizard-logo.png" alt="wizard" class="h-10 p-2"> <span class="font-bold"><?= config("app.name") ?></span></a>

Pas views/parts/navigatie-menu.view.php aan

Opdracht G4 view(...)

In elke view moeten we steeds toevoegen

require __DIR__ . "/../../app/views/parts/header.view.php";

En in elke controller

require __DIR__ . "/../../app/views/contact.view.php";

We zouden hiervoor een functie kunnen maken

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

function view(string $file, array $vars = []){ extract($vars); if (file_exists(__DIR__ . "/../app/views/" . $file . ".view.php")) { require __DIR__ . "/../app/views/" . $file . ".view.php"; } else { if (file_exists(__DIR__ . "/../src/views/" . $file . ".view.php")) { require __DIR__ . "/../src/views/" . $file . ".view.php"; }else{ dd('required view: /app/views/' . $file . ".view.php". ' not found'); } }}

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']);

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).

V6 p1-2 PHP-MySQL webapplicatie vanaf null (11)

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:

V6 p1-2 PHP-MySQL webapplicatie vanaf null (12)

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

V6 p1-2 PHP-MySQL webapplicatie vanaf null (13)

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

V6 p1-2 PHP-MySQL webapplicatie vanaf null (14)

In het volgende scherm kan je aangeven wat je wilt exporteren. Als je de complete database wilt exporteren, vul het dan is zoals hieronder.

V6 p1-2 PHP-MySQL webapplicatie vanaf null (15)

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"

V6 p1-2 PHP-MySQL webapplicatie vanaf null (16)

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.

Onze huidige database importeren?

Hier staat een dump, die je kan downloaden

PHP Classes

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 aanmaakt public function __construct() { $dns = "mysql:host=" . config('database.host') . ";" . "port=" . config('database.port') . ";" . "dbname=" . config('database.dbname') . ";" . "charset=" . config('database.charset'); try { //probeer een verbinding te maken $this->connection = new PDO($dns, config('database.user'), config('database.password'), [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 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.

<?phpreturn [ 'app' => [ 'name' => 'Code Wizards', 'email' => 'info@code-wizards.nl', 'env' => 'dev', ], 'database' => [ 'user' => 'root', 'password' => 'usbw', 'port' => 3306, 'host' => 'localhost', 'dbname' => 'code_wizards', 'charset' => 'utf8mb4', ],];

En om het database object te gebruiken moet deze worden ingeladen in het index bestand

//Database classrequire __DIR__ . "/../src/Database.php";

Gebruik van de database class

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']

<div class="border border-1 rounded p-4 bg-gray-50"> <h2 class="font-bold"><?= $result['title'] ?></h2>  <?= $result['content'] ?></div>

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

V6 p1-2 PHP-MySQL webapplicatie vanaf null (17)

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'],]);

home.view.php verwijder de logica en wijzig de $result in de meegestuurde variabele.

<?phpview("parts/header", ['title' => 'home']);view("parts/navigatie-menu");?><div class="sm:mx-10"> <h1 class="text-3xl my-4">Home</h1> <div class="border border-1 rounded p-4 bg-gray-50"> <h2 class="font-bold"><?= $title; ?></h2> <?= $content ?> </div></div><?phpview("parts/footer");

Het komt er dan ongeveer als volgt uit te zien op de site

V6 p1-2 PHP-MySQL webapplicatie vanaf null (18)

Opdracht ... tonen van posts

We gaan in deze opdracht een nieuwe pagina aanmaken waarop posts te zien zijn. Maak daarvoor twee nieuwe bestanden aan:

- app/controller/posts.php
- app/views/posts.view.php

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();}

app/views/parts/navigatie-menu.view.php

<a href="posts" class="<?= isUri("posts") ? 'underline ' : '' ?>text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Posts</a>

Opmaak en javascript injecties

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:

  1. 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 &lt;, &gt;, &quot;, &amp;), waardoor ze onschadelijk worden gemaakt voor browserinterpretatie. Dit voorkomt XSS-aanvallen.

  2. 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.

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

//Route classrequire __DIR__ . "/../src/Route.php";

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 gevondenhttp_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 een 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>

Standaard formulier

<form method="post" action="/post/insert"> <input type="text" name="title" placeholder="Titel"> <input type="submit" value="Opslaan" name="save"></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.

Zoeken in de database

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.

V6 p1-2 PHP-MySQL webapplicatie vanaf null (19)

Opdracht ... post toevoegen

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

$route->get('post-create', "views/post-create.view.php");
$route->post('post-store', "controllers/post-store.php");

Om post toe te voegen moeten er nog een link worden toegevoegd op de posts pagina. views/posts.view.php

...<h1 class="text-3xl my-4">Home</h1><a href="/post-create" class="text-indigo-600 hover:text-indigo-400">Post toevoegen</a><br><form action="/posts" method="get">...

Flash bar

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.

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.

Gegevens verwijderen

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.

V6 p1-2 PHP-MySQL webapplicatie vanaf null (20)

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.

<input type="hidden" name="id" value="<?= $post['id'] ?>">

Bij value zal het id van de post worden ingevuld

Pas views/posts.view.php aan zodat de button erbij komt

...<?php foreach ($posts as $post): ?> <div class="border border-1 rounded p-4 bg-gray-50"> <h2 class="font-bold"><?= htmlspecialchars($post['title']) ?></h2> <?= htmlspecialchars($post['content']) ?> <form method="post" action="/post-destroy"> <input type="hidden" name="id" value="<?= $post['id'] ?>"> <input type="submit" value="Verwijder" name="delete" class="border bg-red-600 text-white rounded-md px-2 py-1 hover:bg-red-300 cursor-pointer"> </form> </div><?php endforeach; ?>...

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().

Installeren

Voeg het bestand src/csrf.php toe.

Voeg het bestand src/views/401.view.php toe.

Voeg een require toe aan index.php

<?phpsession_start();//inladen van de configuratie parameters$config = require __DIR__ . "/../app/config.php";//handige functiesrequire __DIR__ . "/../src/functions.php";//Database classrequire __DIR__ . "/../src/Database.php";//csrf protectionrequire __DIR__."/../src/csrf.php";//routesrequire __DIR__ . "/../app/router.php";

De installatie is nu klaar

Gebruik van CSRF tokens

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:

<form method="post" action="/post-destroy"> <?= csrf() ?> <input type="hidden" name="id" value="<?= $post['id'] ?>"> <input type="submit" value="Verwijder" name="delete" class="border border-1 rounded-md px-2 py-1 hover:bg-gray-100 cursor-pointer"></form>

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:

  1. Toon de betreffende post met button wijzigen (met id van de post)
  2. Form verstuurd naar post-edit
  3. Controller post-edit haalt de gegevens van de betreffende post op uit de database en stuurt deze naar de view
  4. view-update toont de gegevens uit de database in een formulier met update button
  5. Bij klik op update wordt formulier verstuurd naar controller: post-update
  6. post-update doet een update op de database en stuurt terug naar /posts

Routes

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

Voorbeeld:

<form action="/berichten/<?= $post['id'] ?>" method="post"> <?= csrf() ?> <?= method_put() ?> <label for="titel">Titel</label>...

Indien gebruik wordt gemaakt van een REST-request gaat het dan zo uit zien

URLMethodEndpoint
/postsget/controllers/posts/index.php
/posts/createget/controllers/posts/create.php
/postspost/controllers/posts/store.php
/posts/{id}get/controllers/posts/show.php
/posts/{id}/editget/controllers/posts/edit.php
/posts/{id}put/controllers/posts/update.php
/posts/{id}delete/controllers/posts/destroy.php

Vooral als je met meerdere mensen aan een project werkt kan het handig zijn om je aan dit soort conventies te houden.

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.

Hieronder een paar voorbeelden hoe te gebruiken

<input type="text" name="title" placeholder="Titel" required>

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)

<input type="email" name="email" placeholder="email" required>

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 ingevuld if (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 pagina header("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)

<input type="text" name="title" placeholder="Titel"><br><?php if (isset($errors['title'])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['title'] ?></p><?php endif; ?>

Indien $errors['title'] bestaat dan zal de foutmelding getoond worden zoals hieronder

V6 p1-2 PHP-MySQL webapplicatie vanaf null (21)

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.

<textarea name="content" placeholder="Content..."><?= $_POST['content'] ?? '' ?></textarea><br>

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:

<input type="text" name="title" placeholder="Titel" value="<?= $_POST['title'] ?? ''?>"><br>

Als we de validatie verder uitwerken ook voor het content veld zal de code alsvolgt worden (klik op de link)

Validatie met Validator object

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.

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.

Stappen plan

Het inloggen gaat er schematisch alsvolgt uit zien.

V6 p1-2 PHP-MySQL webapplicatie vanaf null (22)

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.

<?phpview("parts/header", ['title' => 'Inloggen']);view("parts/navigatie-menu");?>

En helemaal onderaan sluiten we af met

<?phpview("parts/footer");

Tussen deze code komt het inlogformulier. Deze kunnen we gebruiken van tailwindcomponents.

V6 p1-2 PHP-MySQL webapplicatie vanaf null (23)

Klik op de bron en copy-paste de relevante html-code in je login.view.php en wijzig wat je anders wilt hebben.

Pas de form tag aan, zodat de action verwijst naar /login en voeg de csrf() token toe

<form class="space-y-6" action="/login" method="POST"> <?= csrf() ?>

Login routes toevoegen

Uiteraard moeten we in onze router ook de login routes aanmaken

$route->get('login', "views/login.view.php");$route->post('login', "controllers/login.php");

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 gevonden if ($user) { //wachtwoord controleren if (password_verify($_POST['password'], $user['password'])) { //gebruiker in session zetten, maar het wachtwoord laten we weg unset($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

<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> <?php if (isset($errors['login'])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['login'] ?></p> <?php endif; ?> <form class="space-y-6" action="/login" method="POST">

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.

<div class="justify-end"> <a href="/login" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Login</a></div>

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)

<div class="justify-end"> <?php if (auth()): ?> <a href="/logout" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Logout</a> <?php else: ?> <a href="/login" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Login</a> <?php endif; ?></div>

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)

$db->query("INSERT INTO posts (title, content, user_id) VALUES (?, ?, ?)", [ $_POST['title'], $_POST['content'], user()->id, // user_id dmv helper functie]);

Alleen voor ingelogde gebruikers

Uiteraard mag je de pagina waar je posts aan kan maken alleen benaderen als je ingelogde gebruiker bent.

De route kunnen we beveiligen met de auth() functie.

router.php

if(auth()){ $route->get('post-create', "views/post-create.view.php");}

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.

views/posts.view.php

<?php if (auth()): ?> <a href="/post-create" class="text-indigo-600 hover:text-indigo-400">Post toevoegen</a><br><?php endif; ?>

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

<h3 class="italic text-sm mt-3">Door: <?= htmlspecialchars($post['name']) ?></h3>

Wat is een model?

Een model is een object die gerelateerd is aan een database tabel.
Voorbeeld model User (let op naam is in enkelvoud en met hoofdletter) heeft in de database de tabel 'users'

Omdat we met models object georienteerd programmeren is het mogelijk om overerving te gebruiken. Elke model moet kunnen worden aangemaakt, opgeslagen, gewijzigd etc.
Dit kunnen we één keer doen in een Model class. Deze Model class kunnen we als blauwdruk voor onze User model of Bericht model gebruiken.

Aanmaken van een Model

Het is netjes om al je Models in een directory 'models' te zetten. Dit is overzichtelijk, maar niet noodzakelijk.

Een voorbeeld van een user Model

<?phpclass 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.

require "Model.php";require "models/User.php";require "models/Post.php";

Model voor queries

Je kan met je Model queries bouwen. Hieronder een voorbeeldje

//query op users tabel$users = (new User()) ->where('voornaam', 'LIKE', '%p%') //voornaam LIKE '%p%' ->where('role', 'user') // role = 'user' ->whereNull('tussenvoegsel') // tussenvoegsel IS NULL ->get(); //uitvoeren van de querydd($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 schrijvenecho $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

<?phpclass 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

<?phpclass 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

//Model classesrequire __DIR__ . "/../src/Model.php";require __DIR__ . "/../app/models/User.php";

Models en relaties

Een User heeft posts. We kunnen ons User model uitbreiden om deze posts snel op te kunnen halen. Onderstaande methode doen al het werk

public function posts(){ return (new Post)->where('user_id', $this->id)->get();}

Uiteraard moet je wel een Model Post aanmaken en een require toevoegen in index.php


Alle geplaatste posts van gebruiker 2 ophalen is nu heel eenvoudig

$user = (new User)->find(2); //selecteren van de userdd($user->posts());

Het is uiteraard niet heel netjes om alle code in home.view.php te zetten.
Eigenlijk zou de regel

$user = (new User)->find(random_int(31, 90));

In de controller/home.php komen te staan en worden meegegeven aan de view

...$user = (new User)->find(random_int(31, 90));view("home", [ 'title' => $result['title'], 'content' => $result['content'], 'user' => $user,]);

In de view kan je dan $user gebruiken als variabele

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

 <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script></head>

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.

<div class="justify-end"> <?php if (auth()): ?> <div class="mr-2 py-1" x-data="{open: false}" @click="open = true" @mouseleave="open = false"> <div class="relative flex items-center space-x-1 cursor-pointer text-gray-700 hover:bg-pink-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"> <!-- Items waarop je kan klikken om uit te klappen --> <div class="flex items-center"> <span><?= user()->name ?></span> <svg xmlns="http://www.w3.org/2000/svg" class="pl-1 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/> </svg> </div> <!-- Uitklap blok dat verschijnt bij klikken --> <div class="origin-top-right absolute top-10 right-0 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-50" x-show="open" x-cloak role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1"> <!-- Active: "bg-gray-100", Not Active: "" --> <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full inline-flex items-center" role="menuitem" tabindex="-1" id="user-menu-item-0"> <svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/> </svg>Profiel</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full inline-flex items-center" role="menuitem" tabindex="-1" id="user-menu-item-1"> <svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>  </svg>Wijzig wachtwoord</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full inline-flex items-center" role="menuitem" tabindex="-1" id="user-menu-item-2"> <svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/> </svg>Berichten</a> <a href="/logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full inline-flex items-center" role="menuitem" tabindex="-1" id="user-menu-item-3"> <svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>  </svg>Sign out</a>  </div>  </div> </div> <?php else: ?> <a href="/login" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md font-medium">Login</a> <?php endif; ?></div>

Of zie de hele navigatie-menu.view.php

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.

<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script><script src="https://unpkg.com/axios@1.1.2/dist/axios.min.js"></script>

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 formaatecho 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

<?phpview("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><?phpview("parts/footer");

Je kan nu naar http://localhost/users navigeren en uitproberen.

Door gebruik van axios en alpinejs wordt ons zoekveld dynamisch. Lees de documentatie van alpinejs om de code beter te begrijpen. Of kijk een in een video hoe het werkt.

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.

require "../src/csp.php";

csp.php

header("Content-Security-Policy: base-uri 'self';" . "connect-src 'self';" . "default-src 'self';" . "form-action 'self';" . "img-src 'self' tailwindui.com ;" . "media-src 'self';" . "object-src 'none';" . "script-src cdn.jsdelivr.net cdn.tailwindcss.com unpkg.com 'nonce-" . getNonce() . "' 'unsafe-eval';" . "style-src cdn.jsdelivr.net cdn.tailwindcss.com 'self' 'nonce-" . getNonce() . "'");

Aan functions.php voeg je de functie getNonce() toe

//wordt gebruikt voor Content Security Policyfunction getNonce(): string{ if (!isset($_SESSION['nonce'])) { $bytes = random_bytes(20); $_SESSION['nonce'] = bin2hex($bytes); } return $_SESSION['nonce'];}

Aan alle javascripts en css bestanden die je toevoegd bij je project moet je toevoegen

<script nonce="<?php echo getNonce(); ?>"> // je script code</script>

Indien je op een speficieke pagina een stukje CSS wilt toevoegen kan dat alsvolgt

<style nonce="<?php echo getNonce(); ?>"> /* je script code */</style>

Inline styles/scripts worden default geblokkeerd. Kijk regelmatig even bij developer tools -> console hier kan je zien dat bepaalde code wordt geblokkeerd. Wanneer je dit toch wilt toestaan kan dit door het aanpassen van de CSP header in csp.php

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

In profiel.view.php komt het formulier te staan.

In het formulier :

<?phpview("parts/header", ['title' => 'about']);view("parts/navigatie-menu");?><form action="/profielfoto" method="post" enctype="multipart/form-data"> Selecteer een profielfoto: <input type="file" name="foto" id="foto"> <!-- om eventuele errors te tonen --> <?php if (isset($errors['foto])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['foto'] ?></p> <?php endif; ?> <input type="submit" value="Upload profielfoto" name="submit"></form><?phpview("parts/footer");

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

$route->post('profielfoto','controllers/profielfoto-store');

Controller

We maken een controller: profielfoto-store.php

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 opgeslagenif (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']);

Waar komt wat

Elk verzoek aan onze website volgt een vaste route. Schematisch is dat in onderstaand overzicht te zien.

V6 p1-2 PHP-MySQL webapplicatie vanaf null (24)

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'.

CREATE TABLE `items` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `naam` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci', `beschrijving` TEXT NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci', `prijs` DECIMAL(8,2) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE)ENGINE=InnoDB;

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 gehele code is te vinden onder opdracht_X

Tonen

Tonen van één item

Stappen plan

  • Maak een route in router.php
  • Maak een bestand controllers/items-show.php
  • Maak een bestand views/items-show.view.php

In de router.php

$route->get('items/{id}','controllers/item-show.php');

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 teruggegevenview('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

<?phpview("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><?phpview("parts/footer");

Testen van je code kan met http://localhost/items/1

Tonen van meerdere items

Stappen plan

  • Maak een route in router.php
  • Maak een bestand controllers/items-index.php
  • Maak een bestand views/items-index.view.php

In de router.php

$route->get('items', 'controllers/items-index.php');

In de controller items-index.php

<?php//initialiseren van database class$db = new Database();//view met item teruggegevenview('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

<?phpview("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><?phpview("parts/footer");

Testen van code: http://localhost/items

Invoeren

Stappenplan

  • Maak twee routes aan in router.php
  • Maak een controller items-create.php
  • Maak een view items-create.view.php
  • Maak een controller items-store.php

In router.php

$route->get('items/create', 'controllers/items-create.php');$route->post('items', 'controllers/items-store.php');

In controller items.create.php

<?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

<?phpview("parts/header", ['title' => 'item toevoegen']);view("parts/navigatie-menu");?> <h1 class="text-3xl my-4">Item toevoegen</h1> <form action="/items" method="post"> <?= csrf(); ?> <label for="naam">Naam</label><br> <input type="text" name="naam" id="naam" placeholder="naam"><br> <label for="beschrijving">Beschrijving</label><br> <textarea name="beschrijving" id="beschrijving" cols="30" rows="3" placeholder="beschrijving"></textarea><br> <label for="prijs">Prijs</label><br> <input type="number" step="0.01" name="prijs" id="prijs" placeholder="prijs"><br> <input type="submit" value="Toevoegen" class="border b-gray-600 rounded py-1 px-2 hover:bg-gray-100 cursor-pointer"><?phpview("parts/footer");

Versie met validatie en opnieuw invullen van velden

<?phpview("parts/header", ['title' => 'item toevoegen']);view("parts/navigatie-menu");?> <h1 class="text-3xl my-4">Item toevoegen</h1> <form action="/items" method="post"><?= csrf(); ?> <label for="naam">Naam</label><br> <input type="text" name="naam" id="naam" placeholder="naam" value="<?= $_POST['naam'] ?? '' ?>"><br><?php if (isset($errors['naam'])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['naam'] ?></p><?php endif; ?> <label for="beschrijving">Beschrijving</label><br> <textarea name="beschrijving" id="beschrijving" cols="30" rows="3" placeholder="beschrijving"><?= $_POST['beschrijving'] ?? '' ?></textarea> <br><?php if (isset($errors['beschrijving'])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['beschrijving'] ?></p><?php endif; ?> <label for="prijs">Prijs</label><br> <input type="number" step="0.01" name="prijs" id="prijs" placeholder="prijs" value="<?= $_POST['prijs'] ?? '' ?>"> <br><?php if (isset($errors['prijs'])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['prijs'] ?></p><?php endif; ?> <input type="submit" value="Toevoegen" class="border b-gray-600 rounded py-1 px-2 hover:bg-gray-100 cursor-pointer"><?phpview("parts/footer");

In controller items-store.php

  • Validatie (voor regels zie cheatsheet)
  • Terugsturen indien errors
  • Invoeren in database
  • Doorverwijzen naar andere pagina
<?php//validatie van de gegevensrequire "../src/Validator.php";$errors = []; //lege array voor de foutmeldingenif (!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 foutmeldingenif (!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 wordenflash("Item " . htmlspecialchars($_POST['naam']) . " is toegevoegd");//terugsturen naar de index paginaheader("location: /items");//terugsturen naar de detail pagina van het item//header("location: /items/" . $db->lastInsertId());

Wijzigen

Stappenplan

  • Maak twee routes in router.php
  • Maak een controller items-edit.php
  • Maak een view items-edit.view.php
  • Maak een controller items-update.php

In de router.php

$route->get('items/{id}/edit', 'controllers/items-edit.php');$route->put('items/{id}', 'controllers/items-update.php');

In controller items-edit.php

<?php$db = new Database();view('items-edit', [ 'item' => $db->query("SELECT * FROM items WHERE id=?", [$_GET['id']])->fetch()]);

In view items-edit.view.php

Zonder validatie

<?phpview("parts/header", ['title' => 'item toevoegen']);view("parts/navigatie-menu");?> <h1 class="text-3xl my-4">Item <?= htmlspecialchars($item['naam']) ?> wijzigen</h1><form action="/items/<?= $item['id'] ?>" method="post"><?= csrf(); ?><?= method_put() ?> <label for="naam">Naam</label><br> <input type="text" name="naam" id="naam" placeholder="naam" value="<?= htmlspecialchars($item['naam']) ?>"><br> <label for="beschrijving">Beschrijving</label><br> <textarea name="beschrijving" id="beschrijving" cols="30" rows="3" placeholder="beschrijving"><?= htmlspecialchars($item['beschrijving']) ?></textarea> <br> <label for="prijs">Prijs</label><br> <input type="number" step="0.01" name="prijs" id="prijs" placeholder="prijs" value="<?= htmlspecialchars($item['prijs']) ?>"> <br> <input type="submit" value="Wijzigen" class="border b-gray-600 rounded py-1 px-2 hover:bg-gray-100 cursor-pointer"><?phpview("parts/footer");

Met validatie

<?phpview("parts/header", ['title' => 'item toevoegen']);view("parts/navigatie-menu");?> <h1 class="text-3xl my-4">Item <?= htmlspecialchars($item['naam']) ?> wijzigen</h1><form action="/items/<?= $item['id'] ?>" method="post"><?= csrf(); ?><?= method_put() ?> <label for="naam">Naam</label><br> <input type="text" name="naam" id="naam" placeholder="naam" value="<?= htmlspecialchars($item['naam']) ?>"><br><?php if (isset($errors['naam'])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['naam'] ?></p><?php endif; ?> <label for="beschrijving">Beschrijving</label><br> <textarea name="beschrijving" id="beschrijving" cols="30" rows="3" placeholder="beschrijving"><?= htmlspecialchars($item['beschrijving']) ?></textarea> <br><?php if (isset($errors['beschrijving'])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['beschrijving'] ?></p><?php endif; ?> <label for="prijs">Prijs</label><br> <input type="number" step="0.01" name="prijs" id="prijs" placeholder="prijs" value="<?= htmlspecialchars($item['prijs']) ?>"> <br><?php if (isset($errors['prijs'])): ?> <p class="text-red-500 text-sm my-2"><?= $errors['prijs'] ?></p><?php endif; ?> <input type="submit" value="Wijzigen" class="border b-gray-600 rounded py-1 px-2 hover:bg-gray-100 cursor-pointer"><?phpview("parts/footer");

In controller items-update.php

<?php//validatie van de gegevensrequire "../src/Validator.php";$errors = []; //lege array voor de foutmeldingenif (!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 foutmeldingenif (!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 itemheader("location: /items/" . $_POST['id']);

Test wijzigen ga naar http://localhost/items/1/edit

Verwijderen

Stappenplan

  • Route aanmaken in router.php
  • controller aanmaken items-destroy.php

Route aanmaken

$route->delete('items/{id}', 'controllers/items-destroy.php');

Controller items-destroy.php

<?php$db = new Database();$db->query("DELETE FROM items WHERE id = :id", [ 'id' => $_POST['id']]);flash("Item is verwijderd");//doorsturen naar de index paginaheader("location: /items");

Verwijderen kan via een formulier

<form method="post" action="/items/<?= $item['id'] ?>"> <?= csrf(); ?> <?= method_delete(); ?> <input type="submit" value="Verwijderen" class="bg-red-600 text-white rounded py-1 px-2 hover:bg-red-400 cursor-pointer"></form>

Met een bevestiging

Uiteraard is het prettig als er voordat er een verwijder actie wordt gedaan een bevestiging wordt gevraagd.

V6 p1-2 PHP-MySQL webapplicatie vanaf null (25)

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)

<div x-data="{ modelConfirm: false }"> <button @click="modelConfirm =!modelConfirm" class="flex justify-center text-white text-md bg-red-500 hover:bg-red-600 border border-gray-200 focus:ring-4 focus:outline-none shadow-md focus:ring-gray-100 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center "> <span> Verwijder </span> </button> <div x-show="modelConfirm" class="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true"> <div class="flex items-end justify-center min-h-screen px-4 text-center md:items-center sm:block sm:p-0"> <div x-cloak @click="modelConfirm = true" x-show="modelConfirm" x-transition:enter="transition ease-out duration-300 transform" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-200 transform" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity bg-gray-700 bg-opacity-60" aria-hidden="true" ></div> <div x-cloak x-show="modelConfirm" x-transition:enter="transition ease-out duration-300 transform" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="transition ease-in duration-200 transform" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" class="inline-block w-full max-w-md p-6 my-10 overflow-hidden text-left transition-all transform bg-white rounded-lg shadow-xl xl:max-w-xl" > <div class="flex items-center justify-between space-x-4"> <h1 class="text-xl font-bold text-gray-800 ">Verwijderen</h1> </div> <p class="mt-2 text-md text-gray-800 "> Wanneer je verder gaat, wordt dit item permanent verwijderd. Weet je het zeker? </p> <x-text-input name="id" type="hidden" value="{{ $site->id }}"/> <div class="flex justify-end mt-6"> <button for="show" @click="modelConfirm = false" type="button" class="mr-2 px-2 py-2 text-sm tracking-wide text-white capitalize transition-colors duration-200 transform bg-gray-500 hover:bg-gray-600 rounded-md shadow-md"> Annuleren </button> <form action="<?= $action ?>" method="post"> <?= csrf(); ?> <?= method_delete(); ?> <button for="show" type="submit" @click="modelConfirm = false" class="mr-2 px-2 py-2 text-sm tracking-wide text-white capitalize transition-colors duration-200 transform bg-red-500 hover:bg-red-600 rounded-md shadow-md"> Verwijderen </button> </form> </div> </div> </div> </div></div>

Uiteraard kan je dit bewerken en opmaken met de style die jij zelft wilt.

Code voor de verwijder button ergens te plaatsen

<?phpview('parts/delete-button', ['action' => "/items/{$item['id']}"]);?>

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.

{path}/toets_project/webroot

Download de proeftoets database en importeer deze in je database.

In de webroot staat cheatSheet.php dit kan je als bron gebruiken voor PHP en HTML code
Daarnaast staat er een cheatSheet voor SQL

De uitwerkingen zijn op github te vinden

opgave 1 (3p)

Opdracht 1 3pt

Pas de config.php aan zodat deze naar de juiste database verwijst. Wijzig de applicatie naam in 'Agenda' en het email adres in info@agenda-mail.nl

Opgave 2 (32p)

Bij deze opdracht gaan we een formulier maken om afspraken in te kunnen voeren in de 'afspraken' tabel uit de agenda database. Hiervoor moeten onderstaande stappen worden ondernomen

  • Maak drie bestanden aan:
    • controllers/afspraak.php (1p)
    • controllers/afspraak-store.php (1p)
    • views/afspraak.view.php (1p)
  • Maak in het menu een link naar '/afspraak-create' met een bijhorende tekst (2p)
  • Maak de routes aan zodat het formulier kan worden getoond en verstuurd (2p)
  • Maak het webformulier waarmee de gegevens ingevoerd kunnen worden
    • Selectbox waar een klant geselecteerd kan worden (klantgegevens moeten afkomstig zijn uit de database) (6p)
    • Een datum veld met als type='date' (1p)
    • Een van tijd met als type='time' (1p)
    • Een tot tijd met als type='time' (1p)
    • Een opmerking veld (textarea) (2p)
    • Verstuur button (1p)
    • Het formulier wordt dmv method="post" verstuurd (1p)
    • Denk aan de csrf() protectie om het laten werken (1p)
  • Na het versturen van het formulier vind er validatie plaats op het gevuld zijn van de velden: klant_id, datum, van en tot (4p)
  • De gegevens worden ingevoerd in de database (3p)
  • De gebruiker krijg een flash message te zien als het goed is gegaan (1p)
  • Indien er fouten zijn moeten deze in het formulier worden getoond (en mag er niet worden ingevoerd in de database) (5p)

Er zijn 10 bonus punten voor het herinvullen van gegevens bij validatie fouten.

Opgave 3 (41)

Zoek pagina

Bij deze opdracht gaan we een pagina maken waarop je op datum kan gaan zoeken naar alle afspraken op die datum.

  • Maak een controller controllers/afspraken.php (1p)
  • Maak een controller controllers/afspraak-destroy.php (1p)
  • Maak een view views/afspraken.view.php (1p)
  • Maak een link in het menu naar /afspraken (2p)
  • Maak de benodigde routes aan (2p)
  • Maak in de view een formulier met method='get' (1p)
  • Geef het formulier één veld 'datum' (type='date') (1p)
  • Voeg een button met de tekst 'zoek' toe (1p)

Na het klikken op de zoek button worden onder het formulier de resultaten van de zoekopdracht getoond. Let op de volgende details:

  • Datums zijn geoordend op afspraaktijd (2p)
  • De velden naam, van en tot moeten minimaal getoond worden. (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 posts weer getoond. (8p)
  • Veiligheid (3p)

Opgave 4 (50p)

Klant beheer

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) Uiteraard is er de juiste validatie en worden fouten getoond. En bij geen fouten de gegevens aangepast in de database.

Het kunnen selecteren van een klant kan bijvoorbeeld met een selectbox of met een lijst van alle klanten (makkelijkste optie).

views 25p
controllers 20p
routes en menu 5p

Opgave 5 (24p)

Cijfer

Het cijfer kan berekend worden met

Cijfer = punten*9/150 + 1

V6 p1-2 PHP-MySQL webapplicatie vanaf null (2024)
Top Articles
Latest Posts
Article information

Author: Aron Pacocha

Last Updated:

Views: 6357

Rating: 4.8 / 5 (68 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Aron Pacocha

Birthday: 1999-08-12

Address: 3808 Moen Corner, Gorczanyport, FL 67364-2074

Phone: +393457723392

Job: Retail Consultant

Hobby: Jewelry making, Cooking, Gaming, Reading, Juggling, Cabaret, Origami

Introduction: My name is Aron Pacocha, I am a happy, tasty, innocent, proud, talented, courageous, magnificent person who loves writing and wants to share my knowledge and understanding with you.