V6 p1-2 PHP-MySQL webapplicatie vanaf null v2

V6 p1-2 PHP-MySQL webapplicatie vanaf null v2

A Inleiding

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

De hoofdstukken/paragrafen met een * zijn vooral informatief. Het is zeker niet noodzakelijk daarbij de opdrachten te maken. Wel wordt aangeraden dit te lezen.

Bronnen

Uitwerkingen opdrachten bekijken en downloaden

Download USBwebserver hier

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

 

B Webserver

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

usbwebserver

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
usbwebserver mappen

Settings

Eventueel kan je in de settings het portnr van de webserver wijzigen. Default is dat port 80. Als je meerdere webservers wilt gebruiken (of als port 80 al ergens anders voor wordt gebruikt) dan kan je de port 8000 t/m 8080 daarvoor gebruiken. Je kan je website dan bereiken dmv http://localhost:8000  (bij gebruik port 8000). Hetzelfde geldt voor de MySQL port, standaard 3306 maar port 3307 kan je ook gebruiken bij conflicten

Configuratie aanpassingen van php (indien nodig)

Open in de map "php" het bestand php.ini

  • Zoek naar "extension=pdo_mysql" en zorg dat daarvoor geen ; komt te staan (; betekend wordt niet uitgevoerd)
  • Zoek naar date.timezone zorg dat daar komt te staan
    date.timezone = Europe/Amsterdam

Test werking

Open een browser en ga naar http://localhost. Als het goed is krijg je de webpagina te zien die in je "root" mapje staat van usbwebserver.

XAMPP

MAC OS alternatief

Wanneer je gebruik maakt van MacBook kan je de volgende software gebruiken.

Als alternatief voor USBwebserver kan je gebruik maken van Mamp

Dit kan je hier downloaden https://www.mamp.info/en/mac/

Bij USBwebserver is de webroot 'root', bij het gebruik van Mamp is de webroot 'htdocs'
Standaard staat de webpoort op 8888, dit kan je bij instellingen aanpassen naar 80.

Apache server

Let op om het project te laten werken moeten er een paar instellingen worden aangepast in het bestand httpd.conf

  • In httpd.conf on /Applications/MAMP/conf/apache, find:

    <Directory />
        Options Indexes FollowSymLinks
        AllowOverride None
    </Directory>
    
  • Replace None with All.

  • Haal de # voor LoadModule rewrite_module modules/mod_rewrite.so weg

  • restart de mamp server

Nginx server

Let op om het project te laten werken moeten een paar instellingen worden aangepast in het bestand nginx.conf in /Applications/MAMP/conf/nginx

Aanpassen:


location / {
   index index.php;
   try_files $uri $uri/ /index.php?query_string;
}

 

Alternatief voor HeidiSQL

Als alternatief voor HeidiSQL zou je gebruik kunnen maken van DBeaver

Dit kan je hier downloaden https://dbeaver.io/

 

VScode is ook voor MacOS dus hier kan je gewoon gebruik van maken.

C Applicaties

VScode

Visual Studio code

Dit is een mogelijke tekst editor die je kunt gebruiken om code te schrijven. VScode biedt ondersteuning (extensies) voor zeer veel programmeertalen. Het is een gratis programma en biedt zeer veel mogelijkheden.

Download hier

Extensies installeren

Het is aanbevollen om onderstaande extensies daarna te installeren

  • Intelephense
  • Tailwind CSS IntelliSense
  • Format HTML in PHP
  • Live Share (zie onder)
  • PHP
     

Configuratie aanpassen

Bij VScode is de editor volledig te configureren. Het is handig om onderstaande parameters te wijzigen. Ga naar file->preferences->settings (ctrl + ,)

  • Zoek naar "auto save" wijzig deze in "onWindowChange"
  • Zoek naar "format" vink "Format On Save" aan
  • Zoek naar "HTML-CSS-class-completion" vink "Enables completion when ..." aan

Working director

Kies open folder

Kies de root directory van USBwebserver

VScode is er nu klaar voor

 

Live Share

Om samen te werken kan het handig zijn om de extensie Live Share te installeren.

Na het installeren kan een teamgenoot Live Share aanzetten. (Let op hiervoor moet je wel inloggen met een Microsoft account of Github account. Als Microsoft account kan je je school email gebruiken).

Live Share Server (dus diegene die deelt met andere)

Klik links onder op Live Share

Er wordt nu een link aangemaakt op je Clipboard die directe te plakken is in een Teams chat naar je maatje waarmee je samenwerkt.

Live Share Client (dus diegene die mee mag doen)

Klik op

Daarna op

Plak de URL uit de Teamschat in de balk en de samenwerking kan beginnen

PHPStorm

PHPstorm kan je als student gratis gebruiken. Je kunt het hier downloaden.
Standaard is het een 30 dagen gratis probeer versie. Maar als je je registreert met je school email dan kan je het gratis gebruiken (studenten licence).

PHPstorm is zeer uitgebreid en maken het leven van een programmeur echt een stuk makkelijker. Wel een paar nadelen.

  • PC gebonden
  • Niet bruikbaar tijdens de toets

Heidi-SQL

Heidi-SQL is een gratis database mangement tool voor Windows. Heidi-SQL maakt het voor ons mogelijk om op een visuele manier onze database te beheren..

Download Heidi-SQL portable 64 bit. Pak het zip bestand uit en zet het in OneDrive of in je Document map. (Pak alle bestanden uit en niet alleen heidi.exe, zonder de overige bestanden werkt het niet).

Interface voor database

Let op Heidi is alleen een tool om met de database te werken. Zonder database is Heidi niet bruikbaar. Dus als je met Heidi gaat werken moet er een database opgestart zijn (bijvoorbeeld die van je USBwebserver)

Eerste keer

Bij de eerste keer dat je Heid-SQL gebruikt moet je de verbinding instellen. Dit is maar eenmalig nodig. Daarna kan je de verbinding herbruiken. Open Heidi-SQL en klik op "nieuw".

heidi start

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

Heidi link

Heidi-SQL zal openen en je krijgt een overzicht van al je database te zien.

 

PHPMyAdmin

Bij USBwebserver is PHPMyAdmin ingebouwd. PHPMyAdmin is een web database mangement tool. Een website waarmee je de database kunt beheren. Via USBwebserver te open door http://localhost/phpmyadmin.

D Hello World

Inleiding

Bij deze cursus ga je leren een website (applicatie) 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.

Om te testen of onze omgeving goed is ingesteld gaat we beginnen met wat kleine scripts. Hiermee zal je HTML kennis worden opgefrist. En leer je wat basis synthax van PHP.

Bij alle opdrachten zullen we USBwebserver gebruiken. Onze bestanden moeten in de 'root' directory komen te staan. Het meest makkelijk daarvoor is om in VScode File => 'Open Folder' te kiezen

Selecteer daarna de 'root' folder van USBwebserver.

Maak een nieuwe folder met de naam 'D' aan in de root directory van USBwebserver. In deze folder komen alle oefenbestanden te staan van paragraaf D van de cursus.

Pas nu het pad van USBwebserver aan bij settings in {path}/root/D

* Laat de apache port op 80 staan als dat werkt

 

Opdracht D1 - HTML

HTML staat voor HyperText Markup Language. Het is de standaardtaal voor het maken van webpagina's en het structureren van inhoud. HTML beschrijft de inhoud van een webpagina met behulp van "tags". Elke HTML-pagina bestaat uit elementen die met tags worden gedefinieerd.

Een voorbeeld pagina kan er zo uitzien

<!DOCTYPE html>
<html>

<head>
    <title>Mijn eerste HTML-pagina</title>
</head>

<body>
    <h1>Hello World</h1>
</body>

</html>

Verklaring:

  • <!DOCTYPE html>: Bepaalt dat het document HTML5 is.
  • <html>: De root van de HTML-pagina.
  • <head>: Bevat metadata, zoals de titel van de pagina.
  • <title>: Bepaalt de naam die in de browser wordt weergegeven.
  • <body>: Bevat de inhoud die op de webpagina wordt weergegeven.
  • <h1>: Een koptekst (hier "Hello World").

 

Opdracht D1

Schrijf een HTML-bestand met de naam hello.html en voeg de bovenstaande code toe. Open het bestand in je browser om te zien hoe "Hello World" wordt weergegeven.

Zorg ervoor dat je USBwebserver is opgestart. Indien de instellingen op standaard staan, kan je de pagina open http://localhost/hello.html

Uitwerking

TIP

In een eerdere paragraaf hebben we de VScode extensie Emmet geïnstalleerd. Hiermee kunnen we snel HTML code genereren.

Type ! gevolgd door de <tab> toets. Dit zal ongeveer de volgende code aanmaken.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

</body>

</html>

Hiermee kan je snel een nieuwe HTML pagina aanmaken

En zet 'auto save' aan bij je VScode!!!

 

Opdracht D2 - Hello world in PHP

 

PHP staat voor Hypertext Preprocessor. PHP is een serverside programmeertaal, wat betekent dat de code op de server wordt uitgevoerd voordat deze naar de browser van de gebruiker wordt gestuurd.

PHP wordt vaak gebruikt om dynamische webpagina's te maken. Je kunt PHP gebruiken om code te schrijven die samenwerkt met HTML.

PHP-syntaxis:

De eenvoudigste PHP-code om "Hello World" weer te geven is:

<?php
  echo "Hello World";
?>

Verklaring:

  • <?php ... ?>: PHP-code wordt geschreven tussen deze tags.
  • echo: Gebruikt om iets naar de browser te sturen (in dit geval de tekst "Hello World").

Belangrijk is dat je een bestand waarin je PHP code gebruikt opslaat met de extensie .php en dat je het uitvoert via een webserver bv. USBwebserver.

Opdracht D2

Maak een nieuw bestand aan, bijvoorbeeld hello.php. Voeg de bovenstaande PHP-code toe, en open dit bestand. Zorg ervoor dat het bestand weer is opgeslagen in de root folder van USBwebserver. Je kan het bestand openen met http://localhost/hello.php

 

Uitwerking

Opdracht D3 - PHP en HTML combineren

Nu gaan we HTML en PHP combineren. PHP kan naadloos worden geïntegreerd in een HTML-document. Hieronder staat een voorbeeld van een HTML-pagina met ingebedde PHP-code:

<!DOCTYPE html>
<html>
<head>
    <title>PHP en HTML</title>
</head>
<body>
    <h1><?php echo "Hello World"; ?></h1>
</body>
</html>

In dit voorbeeld zorgt PHP ervoor dat "Hello World" dynamisch wordt weergegeven binnen de <h1>-tag.

Voor diegene die niet te veel willen typen is er ook een snelle synthax voor <?php echo en dan is <?= Zelfs kan je dan de ; aan het eind weglaten.

De code zou dan worden

<!DOCTYPE html>
<html>
<head>
    <title>PHP en HTML</title>
</head>
<body>
    <h1><?= "Hello World" ?></h1>
</body>
</html>

Opdracht D3

Maak een bestand met de naam combi.php, voeg de bovenstaande code toe en bekijk het in je browser via een serveromgeving. (http://localhost/combi.php)

 

Uitwerking

 

PHP-variabelen

In PHP kun je gegevens opslaan in variabelen om ze later te gebruiken. Laten we beginnen met het definiëren en weergeven van variabelen in een PHP-bestand.

<?php
$naam = "Student";
echo "Hello, " . $naam . "!";
?>

Verklaring:

  • $naam: Een variabele in PHP wordt aangegeven met het $-teken.
  • .: Dit is de concatenatie-operator, waarmee je strings samenvoegt.

Met de . kan je dus teksten aan elkaar plakken.

Opdracht D4 - Variabele

HTML-formulieren en PHP (basis)

Met HTML-formulieren kun je input van gebruikers verzamelen en deze door PHP laten verwerken. Hier is een eenvoudig voorbeeld van een formulier waarmee een gebruiker zijn naam kan invoeren:

Hieronder de code om dit voor elkaar te krijgen

<!DOCTYPE html>
<html>
<head>
    <title>Formulier met PHP</title>
</head>
<body>
  <form method="get" action="formulier_controller.php">
    Naam: <input type="text" name="naam" placeholder="Naam">
    <input type="submit" value="Verstuur">
  </form>
</body>
</html>

Verklaring:

  • <form method="get" action="formulier_controller.php">: Het formulier stuurt de ingevoerde gegevens naar formulier_controller.php voor verwerking. De method kan hier post of get zijn
  • <input type="text" name="naam" placeholder="Naam">: Hier kan de gebruiker zijn naam invoeren.
    type="text"Het input veld is van type tekst. (andere opties zijn bv. email en number)
    name="naam" de variabele heet 'naam'
    placeholder="Naam" de gebruiker ziet een tekst in het veld staan "Naam'
  • <input type="submit" value="Verstuur">: Hiermee wordt het formulier verzonden.
    value="Verstuur" de button heeft de tekst "Verstuur"

Als het formulier wordt verstuurd dan wordt script formulier_controller.php aangeroepen.
De code in dit script zou kunnen zijn:

<?php

if (isset($_GET['naam'])) {
    $naam = $_GET['naam'];
    echo "Hallo, " . htmlspecialchars($naam) . "!";
}

Verklaring:

  • if(isset($_GET['naam'])){ Dit controleert of er een variabele 'naam' is verstuurd
  • $_GET['naam']: Dit haalt de ingevoerde naam op uit het formulier (variabele).
  • htmlspecialchars(): Deze functie beschermt tegen het invoeren van schadelijke HTML-code door de gebruiker.

De bestanden zijn hier te vinden:
formulier.php

formulier_controller.php

Download deze bestanden en zet deze in /root/D van je USBwebserver kijk of het werk.

 

Waarom htmlspecialchars()

De functie htmlspecialchars() is essentieel in PHP om te voorkomen dat kwaadwillenden HTML- of JavaScript-code in je website injecteren, wat kan leiden tot beveiligingsproblemen zoals cross-site scripting (XSS).

Stel je voor dat je gebruikers de mogelijkheid geeft om tekst in te voeren op je site, zoals een naam of een bericht. Zonder htmlspecialchars() kan iemand HTML-tags of JavaScript-scripts invoeren in plaats van gewone tekst. Als je die invoer vervolgens direct op de pagina weergeeft, wordt de code uitgevoerd in de browser van andere gebruikers, wat schadelijk kan zijn.

Hoe werkt htmlspecialchars()?
De functie htmlspecialchars() zet speciale karakters, zoals `<`, `>`, en `&`, om naar veilige HTML-entiteiten, bijvoorbeeld:

  • `<` wordt `&lt;`
  • `>` wordt `&gt;`
  • `&` wordt `&amp;`

Hierdoor wordt gebruikersinvoer als gewone tekst weergegeven en niet als uitvoerbare code.

Voorbeeld

// Zonder htmlspecialchars()
echo $_GET['username'];

// Met htmlspecialchars()
echo htmlspecialchars($_GET['username']);

 

In het tweede voorbeeld voorkomt `htmlspecialchars()` dat eventuele tags in de gebruikersinvoer als HTML of JavaScript worden geïnterpreteerd. Het is dus een belangrijke functie voor de veiligheid en betrouwbaarheid van je website.

 

Opgave D5 - Formulier

Opgave D5

Maak twee bestanden:

  1. sport.php voor het HTML-formulier.
  2. sport_controller.php voor de verwerking van de formuliergegevens.

In het formulier komen drie velden. De naam van de gebruiker, de leeftijd van de gebruiker en de sport die hij doet.
Het formulier moet worden verstuurd naar sport_controller.php

In de controller worden de verstuurde gegevens in variabele gezet en daarna als zin op het scherm geschreven
{naam} is {leeftijd} jaar oud en doet aan {sport}.

Voor het leeftijdsveld kan gebruik worden gemaakt van

Leeftijd: <input type="number" name="leeftijd">

Voor de andere twee velden kan je het tekstveld gebruiken uit het voorbeeld.

 

Uitwerkingen:

sport.php

sport_controller.php

Opgave D6 - htmlspecialchars

Opgave D6

Pas de code in vorige opgave nu eens aan waarbij je htmlspecialchars() bij 'sport' weglaat.
Voor nu in je formulier de volgende gegevens in
Naam: Piet
Leeftijd: 16
Sport:   <script>alert('Dit is onveilig!!!!')</script>

Wat gebeurt er als je wel de htmlspecialchars() gebruikt?

De alert die we in Opgave D6 hebben gebruikt is vrij onschuldig. Maar uiteraard kunnen ook minder onschuldige scripts op deze manier worden uitgevoerd. Dus let op deze veiligheid.

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. Het is zeer populair en lijkt erg veel op de CSS die jullie al geleerd hebben.

 

Tailwindcss

Er zijn vele manieren om Tailwindcss aan je project toe te voegen. De meest eenvoudige manier is via CDN. Er wordt dan van een server een script ingeladen en daarmee kunnen we alles gebruiken van Tailwindcss. Dit is zeer eenvoudig toe te voegen.

<!doctype html>
<html lang="en">
<head>
    <title>Tailwind voorbeeld</title>
    <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body>

</body>
</html>

 

Verklaring:

  • <script src="https://cdn.tailwindcss.com"></script>: Dit moet worden toegevoegd om tailwindcss te kunnen gebruiken.

Alle documentatie van Tailwindcss is te vinden op hun website.

Daarnaast is er een pagina met diverse gratis componenten die je als basis voor een pagina kan gebruiken. Deze kan je hier vinden.

Tailwindcss bestaat uit classes en die kun je gebruiken in class="" attribuut

<!doctype html>
<html lang="en">
<head>
    <title>Tailwind voorbeeld</title>
    <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body>
<h1 class="text-4xl text-center text-indigo-700">Tailwindcss voorbeeld</h1>
<p class="mx-10 mt-10">Dit is een voorbeeld van Tailwind CSS</p>
</body>
</html>

Verklaring:

  • <h1 class="text-4xl text-center text-indigo-700"> tekst wordt 4x zo groot, tekst wordt gecentreerd en heeft kleur indigo.
  • <p class="mx-10 mt-10"> De paragraaf heeft een horizontale (x) margin van 10 en een margin-top van 10

Het komt er dan zo uit te zien

Bovenstaande bestand kan je hier downloaden

tailwindcss.php

Probeer wat te spelen met de margin, padding, text color, background color en font size

bv

<body class="bg-blue-100"> 

 

E PHP syntax

Commentaar

Om code na een periode niet te hebben gebruikt en voor anderen begrijpelijk te houden is het handig om commentaar toe te voegen. Hou het commentaar kort, maar wel duidelijk.

Een voorbeeld van commentaar

<?php

// controlle of de variabele $naam bestaat
if (isset($naam)) {
    echo "De variabele \$naam bestaat";
} else {
    echo "De variabele \$naam bestaat niet";
}

Commentaar kan op twee manieren in PHP met een //

Of als je meerdere regels commentaar wilt schrijven kan het tussen  /* en */

/*
 * In de volgende code wordt de variabele $naam gecontroleerd of deze bestaat.
 * Als de variabele bestaat wordt de tekst "De variabele $naam bestaat" getoond.
 * Als de variabele niet bestaat wordt de tekst "De variabele $naam bestaat niet" getoond.
 */
if (isset($naam)) {
    echo "De variabele \$naam bestaat";
} else {
    echo "De variabele \$naam bestaat niet";
}

Let op de * tussen de /* en */ zijn alleen voor de leesbaarheid

 

Je kan ook in HTML commentaar geven

<!-- Uitwerking 1 -->

Tussen <!-- en --> wordt niet getoond op het scherm. Maar let op dit is wel zichtbaar in de bron van de pagina. Met bv F12 (devTools)

PHP commentaar is niet zichtbaar in de browser.

PHP-variabelen

Variabele

In PHP starten variabele altijd met een $ en daarna tekst (geen getallen)
$naam       Juist
$2naam     Onjuist
$_naam     Juist

Eerst wordt een variabele gevuld met een waarde bv.

$naam = "Piet";

De code wordt afgesloten met een ;

De variabele kan daarna gebruikt worden in bijvoorbeeld teksten, berekeningen, etc.

echo "Je naam is $naam";

$getal3 = $getal1 + $getal2;

Let op echo "Je naam is $naam"; is iets anders dan echo 'Je naam is $naam';

 

Voorgedefinieerde variabele

In PHP zijn een aantal variabele al voor gedefinieerd. Deze beginnen met een $_ en zijn in hoofdletters.

$_GET   array met gegevens die door een formulier worden verstuurd (via method="get")
$_POST array met gegvens die door een formulier worden verstuurd (via method="post")
$_FILES array met bestand(en) die door een formulier worden verstuurd
$_SESSION array met variabele die in een sessie staan. (blijven bewaard tijdens website bezoek)
$_SERVER array met gegevens over de webserver en een paar client parameters
$_COOKIE array met gegevens die in een cookie zijn opgeslagen (blijft bewaard ook na website verlaten)
 

Opgave E1 - PHP variabele

Opgave E1

Probeer beide codes uit te voeren om het verschil uit te zoeken. Let wel op dat je de variabele eerst een waarde geeft.

Uitwerking:

naam.php

Array

Array's

Een array is een variabele waarmee je meerdere waarden kunt opslaan onder één naam. Dit is handig als je bijvoorbeeld een lijst met items hebt, zoals een verzameling namen of getallen.

<?php
$kleuren = ["rood", "groen", "blauw"];
echo "De eerste kleur is: " . $kleuren[0]."<br>";
echo "De tweede kleur is: " . $kleuren[1]."<br>";
echo "De derde kleur is: " . $kleuren[2]."<br>";
?>

Verklaring:

  • [...]: Dit is de manier om een array aan te maken.
  • $kleuren[0]: Hiermee haal je het eerste element van de array op (arrays beginnen bij index 0).

 

Associatieve arrays

Naast gewone arrays, waarbij de items een numerieke index hebben, zijn er ook associatieve arrays. Dit type array gebruikt sleutels (keys) in plaats van nummers om waarden op te slaan.

<?php
$leeftijd = ["Jan" => 25, "Els" => 30, "Klaas" => 22];
echo "De leeftijd van Jan is: " . $leeftijd['Jan'];
?>

Verklaring:

  • In deze array zijn de namen de sleutels en de leeftijden de waarden.
  • $leeftijd['Jan']: Dit haalt de waarde op die hoort bij de sleutel 'Jan'.

Let op de key "Jan" en "jan" is iets anders. Een key is hoofdletter gevoelig.

 

Arrays doorlopen met loops

Een veelgebruikte manier om arrays te gebruiken is door ze te doorlopen met een loop. Dit stelt je in staat om elk item in een array te verwerken. Hiervoor wordt meestal de foreach loop gebruikt

<?php
$dieren = ["kat", "hond", "vogel"];

foreach ($dieren as $dier) {
    echo "Een " . $dier . "<br>";
}
?>

Verklaring:

  • foreach: Dit is een speciale loop om door alle elementen van een array te gaan.
  • $dieren as $dier: Voor elk item in de array wordt de waarde opgeslagen in $dier en vervolgens weergegeven.

 

Alternatieve foreach loops

Omdat je vaak PHP en HTML door elkaar heen gebruikt, zijn er wat alternatieve notatie wijzen. Wat ook kan is het volgende. (Doet het zelfde als het voorbeeld hierboven)

<?php
$dieren = ["kat", "hond", "vogel"];
?>

<?php foreach ($dieren as $dier): ?>
    Een <?= $dier ?><br>
<?php endforeach ?>

 

Met HTML opmaak

<?php
$dieren = ["kat", "hond", "vogel"];
?>

<b>Dieren</b>
<ul>
    <?php foreach ($dieren as $dier): ?>
        <li>Een <?= $dier ?></li>
    <?php endforeach ?>
</ul>

In het bestand arrays.php zijn de diverse mogelijkheden te zien. Je kan het bestand in je USBwebserver zetten zodat je voorbeelden hebt die je kan gebruiken.

if - else

IF - ELSE in PHP

Een if-else statement laat je code uitvoeren op basis van een bepaalde voorwaarde. Dit is erg handig om te bepalen welke actie onder specifieke omstandigheden uitgevoerd moet worden.

<?php
$uur = 10;

if ($uur < 12) {
    echo "Goedemorgen!";
} else {
    echo "Goedemiddag!";
}
?>

Verklaring:

  • if ($uur < 12): Als de variabele $uur minder dan 12 is, wordt "Goedemorgen!" weergegeven.
  • else: Als de voorwaarde in de if niet waar is, wordt "Goedemiddag!" weergegeven.

 

Kijken of er iets is verstuurd. Dit kan op verschillende manieren. In het voorbeeld kan je de $_GET ook lezen als $_POST

<?php

if( isset($_GET['veld']) ){ //is er een formulier met een name="veld" verstuurd?
    ...
}

//of

if( $_GET != null ){ //is er iets met een formulier verstuurd
    ...
}
?>

Vaak wordt ook een enkele regel notatie gebruikt

<h1><?= ($leeftijd<18) ? "Geen toegang" : "Toegang" ?></h1>

Voorwaarde ? waar : niet waar

Dus als de $leeftijd kleiner dan 18 is dan 'Geen toegang' en anders 'Toegang'

 

PHP-HTML

Vaak als je PHP en HTML door elkaar heen gebruikt worden if-loops als volgt gedaan.

<?php if(true): ?>
    <p>Dit is waar</p>
<?php else: ?>
    <p>Dit is onwaar</p>
<?php endif; ?>

Resultaat:

Heel veel verschillende mogelijkheden om hetzelfde te bereiken

 

In het bestand if-else.php zijn de diverse mogelijkheden te zien. Je kan het bestand in je USBwebserver zetten zodat je voorbeelden hebt die je kan gebruiken.

Opgave E2.1 t/m 2.3

Opgave E2.1

Maak een webpagina met deze inhoud

Gebruik bij deze opdracht de basis e2_1.php

Met Tailwindcss kan je de tekst kleur aanpassen door class="text-red-600", waarbij je red kan vervangen door elke kleur uit de array $colors. De 600 geeft aan hoe donker de kleur is dit loopt van 100 t/m 900 in stappen van 100. En daarnaast bestaat 50 ook. Dus de lichtste kleur rood is text-red-50 en donkerste text-red-900.

Gebruik als HTML-tag een <p> of een <div>

 

Uitwerking e2_1_uitwerking.php

Opgave E2.2

Maak het volgende formulier (is al voor je gemaakt in e2_2.php)

Na het maken van een keuze, wordt jou keuze op het scherm geschreven. Maak gebruik van een if-loop.

De variabele die wordt verstuurd kan je gebruiken als $_GET['keuze']

Dus bv.

   if($_GET['keuze'] == 'ja')

 

Hier kan je de uitwerking vinden

e2_2_uitwerking.php

Opgave E2.3

In de volgende opgave gaan we alle vlaggen van de 27 EU lidstaten tonen.

Hiervoor gebruiken de de vlaggen api van https://flagsapi.com/

De vlag van Nederland kunnen we maken dmv

<img src="https://flagsapi.com/NL/flat/64.png">

Op de plaats van NL kan je de landcode zetten van het betreffende land.

Een AI kan een array met landcodes voor je maken, dit was mijn output van Chat-GPT

$eu_lidstaten = [
    "België" => "BE",
    "Bulgarije" => "BG",
    "Cyprus" => "CY",
    "Denemarken" => "DK",
    "Duitsland" => "DE",
    "Estland" => "EE",
    "Finland" => "FI",
    "Frankrijk" => "FR",
    "Griekenland" => "GR",
    "Hongarije" => "HU",
    "Ierland" => "IE",
    "Italië" => "IT",
    "Kroatië" => "HR",
    "Letland" => "LV",
    "Litouwen" => "LT",
    "Luxemburg" => "LU",
    "Malta" => "MT",
    "Nederland" => "NL",
    "Oostenrijk" => "AT",
    "Polen" => "PL",
    "Portugal" => "PT",
    "Roemenië" => "RO",
    "Slovenië" => "SI",
    "Slowakije" => "SK",
    "Spanje" => "ES",
    "Tsjechië" => "CZ",
    "Zweden" => "SE"
];

Maak een loop door de lidstaten en toon de naam van het land met daarachter de bijhorende vlag (of andersom).

Uitwerking

F Database aanmaken

Database mangement tools

Met Heidi-SQL kunnen we redelijk makkelijk een database aanmaken. Uiteraard kan dit ook met SQL-queries. Maar omdat je dit niet dagelijks doet maakt een visuele omgeving het leven een stuk makkelijker.

Opgave F1.1 Database maken

Maak een database genaamd 'code_wizards'. Dit kan door met de rechtermuis op de databaseserver te klikken (localhost).

En kies de collatie zoals hierboven.

Tabel users en posts

We gaan nu twee tabellen aanmaken Users en Posts. Een tabel bevat records, dit zijn er altijd meer dus is het netjes om de tabelnaam in meervoud te schrijven. Ik zelf doe dit graag in het Engels, maar uiteraard kan het ook gewoon in het Nederlands. Let op, dat verder in de cursus de Engelse tabelnamen worden gebruikt. Indien je voor Nederlands kiest moet jij dus wat dingen anders doen.

Opgave F1.2 Maak onderstaande tabel Users:

De onderste 3 timestamps zijn niet persé noodzakelijk. Maar daarmee is wel snel terug te zien wanneer een gebruiker is aangemaakt, gewijzigd is of is verwijderd. Dus handig om toe te voegen.

Email is een uniek veld, dit zal later worden gebruikt om in te loggen (samen met het wachtwoord).
id is de primaire sleutel van de tabel en moet op auto_increament komen te staan.

Als je zelf nog meerdere velden wilt toevoegen mag dat, maar zet dan bij deze velden wel het vinkje 'null toestaan' aan. Anders kan je straks de automatische vulling niet gebruiken.

 

Opgave F1.3 Maak de tabel Posts:

Een post wordt door een user gedaan. Vandaar de verwijzende sleutel 'user_id'. Verder heeft een 'post' een titel en content. Omdat de content best groot kan zijn is er gekozen voor MEDIUMTEXT.

 

Relatieschema code_wizards

Het relatieschema van de code_wizard database

Tabel data (users en posts)

Opgave F1.4 Database vullen

Vul de database met onderstaande fake data (copy paste in Heidi-sql en voer uit)

of download hier 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),
	(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);

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

 

Database exporteren

Wanneer je een database hebt gemaakt kan je deze exporteren. Er wordt dan een .sql bestand gemaakt met queries. Door het uitvoeren van deze queries kan deze database weer worden aangemaakt.

Open Heidi-sql en klik met de rechtermuis op de database

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

 

Opgave F1.5 Exporteren

Maak een export bestand van jou code_wizards database.

 

Database importeren

Als je een database dumb (export bestand) hebt, kan je deze toevoegen aan je database server dmv importeren. Klik hiervoor bij Heidi-sql boven in het menu op "bestand"

Je kan dan het .sql bestand openen. Daarna wordt de database toegevoegd.

Als het importeren klaar is moet je het scherm vernieuwen om de wijzigingen te kunnen zien. Klik daarvoor op een database en daarna op F5.

 

Onze huidige database importeren?

Hier staat een dump, die je kan downloaden

Opgave F1.6 Dump importeren

Download de dump van code_wizards en importeer deze. Dit zal jou database overschreven. Mocht je een klein foutje hebben gemaakt in voorgaande opdrachten is jou database nu in elk geval hetzelfde als in de rest van de cursus gebruikt zal worden.

Eén op meer relatie

De code_wizard database heeft nu twee tabellen: users en posts. Deze tabellen hebben een één op meer relatie.

Elke post is geschreven door één gebruiker. Daarom wordt het veld user_id toegevoegd aan de tabel posts. Elke gebruiker kan meerdere posts hebben. Elke post heeft zijn eigen user_id, dus er zouden meerdere posts kunnen zijn van één gebruiker.

De user_id in de tabel 'posts' verwijst dus naar een bepaalde user. Zo weet je val welke user de post is.

Meer op meer relatie

Bij een meer op meer relatie kunnen beide meerdere keren voorkomen.

Zie bovenstaande voorbeeld
Elke leerling kan meerdere vakken hebben. Elk vak kan door meerdere leerlingen worden gevolgd. Tussen leerlingen en vakken bestaat dan een meer op meer relatie. Het is dan noodzakelijk om een koppeltabel toe te voegen. Geef deze een logische naam zodat je weet waar de tabel bij hoort.

Je zou kunnen kiezen voor heeft_vak / volgt_vak of zoiets.
De koppeltabel heeft_vak krijgt een samengestelde primaire sleutel. Die bestaat uit twee verwijzende sleutels (foreign keys)

G Framework gebruiken

Het gebruik van een PHP-framework voor het ontwikkelen van webapplicaties biedt verschillende voordelen die zowel de efficiëntie van ontwikkelaars als de kwaliteit van de applicatie verbeteren. Hier zijn de belangrijkste redenen waarom het handig is om een framework te gebruiken:

1. Snellere ontwikkeling en minder herhalend werk

Frameworks bieden een solide basis van vooraf geschreven code en functies die je kunt hergebruiken. Dit voorkomt dat je herhalend werk doet, zoals het opzetten van databases, formulieren, authenticatiesystemen en routing. In plaats van alles vanaf nul te schrijven, kun je bouwen op de fundering die het framework biedt, waardoor je sneller kunt ontwikkelen.

Voorbeeld: Laravel, een populair PHP-framework, heeft ingebouwde functionaliteit voor veelvoorkomende taken zoals gebruikersauthenticatie, routing en database-interactie, wat je uren aan coderen bespaart.

2. Gestructureerde code en onderhoudbaarheid

Frameworks volgen doorgaans het Model-View-Controller (MVC)-patroon, wat helpt om je code gestructureerd te houden. Met MVC worden de gegevenslogica (Model), weergave (View) en toepassingslogica (Controller) van elkaar gescheiden, waardoor je applicatie veel overzichtelijker en beter te onderhouden is.

Waarom is dit belangrijk?

  • Het scheidt je code in logische delen, wat het gemakkelijker maakt om bugs op te sporen.
  • Het zorgt ervoor dat teams gemakkelijker kunnen samenwerken, omdat de structuur duidelijk is.
  • Bij wijzigingen in de gebruikersinterface hoef je niet de logica of datamanagementcode aan te passen.

3. Beveiliging

Veiligheid is een van de grootste zorgen bij webontwikkeling, en PHP-frameworks komen met ingebouwde beveiligingsfuncties om je applicatie te beschermen tegen veelvoorkomende kwetsbaarheden zoals:

  • SQL-injectie (via ORM of prepared statements)
  • Cross-Site Scripting (XSS)
  • Cross-Site Request Forgery (CSRF)

Frameworks zoals Laravel, Symfony en CodeIgniter hebben ingebouwde beveiligingsmechanismen die je helpen om veilige webapplicaties te schrijven zonder dat je elk detail handmatig hoeft te beheren.

4. Community en ondersteuning

Grote PHP-frameworks hebben een actieve community van ontwikkelaars en uitgebreide documentatie. Dit betekent dat je snel hulp kunt vinden wanneer je vastloopt. Omdat veel ontwikkelaars dezelfde tools en patronen gebruiken, zijn er veel kant-en-klare oplossingen beschikbaar voor problemen die je tegenkomt.

Wat betekent dit voor jou?

  • Je kunt gebruikmaken van kant-en-klare bibliotheken en pakketten.
  • Als je een probleem hebt, is de kans groot dat iemand anders het al heeft opgelost en dat de oplossing online beschikbaar is.

5. Schaling en prestaties

Frameworks zijn vaak ontworpen met schaalbaarheid in gedachten. Ze bieden mechanismen zoals caching, asynchrone verwerking en optimalisaties om de prestaties van je applicatie te verbeteren naarmate het aantal gebruikers en de hoeveelheid data groeit. Dit helpt bij het bouwen van robuuste, grootschalige webapplicaties.

Voorbeeld: Met Laravel kun je eenvoudig cachingmechanismen instellen om de snelheid van je applicatie te verbeteren zonder dat je deze zelf moet coderen.

6. Ondersteuning voor databases en ORM

De meeste frameworks bieden ingebouwde ondersteuning voor meerdere databases en bevatten Object Relational Mapping (ORM), waardoor je op een abstracte manier met databases kunt werken. Dit betekent dat je niet zelf handmatig SQL-query's hoeft te schrijven voor elke databasetransactie, maar in plaats daarvan object-georiënteerde PHP-code kunt gebruiken.

Voorbeeld: In Laravel kun je met Eloquent ORM eenvoudig werken met je database via modellen, zonder dat je directe SQL-query's hoeft te schrijven:

<?php
// Een gebruiker toevoegen
$user = new User();
$user->name = "Jan";
$user->email = "jan@example.com";
$user->save();

7. Testbaarheid

PHP-frameworks bevorderen het schrijven van testbare code. Frameworks zoals Laravel en Symfony bevatten ingebouwde tools voor het uitvoeren van unit tests en integratietests. Dit maakt het gemakkelijker om ervoor te zorgen dat je applicatie correct werkt, zelfs wanneer je nieuwe functies toevoegt of wijzigingen aanbrengt.

 

Op school zijn we iets beperkt in het gebruiken van een framework. Op de computers hebben we beperkte rechten wat het niet mogelijk maakt om hier efficiënt mee te kunnen werken. Daarom zullen we gaan werken met een zeer eenvoudig framework dat gebaseerd is op Laravel, maar uiteraard lang niet al de functionaliteiten bevat van de grote spelers.

Het framework dat wij gebruiken is 'framework'. De code hiervoor staat op Github. In de volgende opdrachten zullen we het framework gebruiken en de functionaliteiten leren kennen.

Framework

Opgave G1.1 Framework installeren

Framework installeren op onze USBwebserver

  1. Ga naar de github pagina waar het framework te downloaden is
  2. Download de zip (dit zijn alle bestanden)
  3. Pak de zip uit en sleep de map framework naar je root directory van je USBwebserver
  4. Pas het pad van je USBwebserver aan naar {path}/root/framework/webroot


 

.htaccess en index.php

In de folder webroot staat een bestand .htaccess
De inhoud van dit bestand hoef je niet te kennen. Dit bestand zorgt ervoor dat elk web verzoek (bezoek van de website) via index.php verloopt. Het voordeel hiervan is, is dat we in index.php een aantal scripts kunnen inladen en deze script zijn altijd beschikbaar. Dit bespaart ons veel tijd en code.

Directory structuur

Het framework bevat drie directories

App

In app komt de code voor jou applicatie. Alles in deze directory kan je aanpassen naar eigen wens.

Src

De src (source directory) moet je nooit aanpassen, tenzij je nieuw eigenschappen aan het framework wilt toevoegen.
Als je goede aanvullingen hebt, bespreek dit dan met de docent.

Webroot

In de webroot directory staat alles wat je met de wereld wilt delen. Bijvoorbeeld afbeeldingen de ingang van je website (index.php) etc.

Structuur MVC

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

MVC model

 

 

 

 

 

 

 

 

 

 

 

 

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

flow

Praktijk voorbeeld:

Bezoek van de home pagina.

Je gaat naar http://localhost/home

index.php wordt nu aangeroepen, hierin worden alle benodigde bestanden ingeladen. Als laatste wordt router.php aangeroepen.

Router.php zal je doorsturen naar de controller: home.php

De controller zal in de laatste regel de view home.php aanroepen.

De view home.view.php roept nog wat andere views aan en zorgt voor de HTML, JavaScript en CSS.

Configuratie

In de folder /app staat een bestand config.php. In dit bestand kunnen parameters opgenomen worden die je overal in de applicatie zou willen kunnen gebruiken. En als je de gegevens in config.php aanpast, zal dat meteen overal in de applicatie aangepast zijn.

Jij kunt aan config.php zelf ook parameters toevoegen. Het is belangrijk dat de parameters voor de verbinding met de database goed staan.

Wanneer je een parameter uit het config.php bestand wilt gebruiken kan dat alsvolgt

<?php

echo config('app.email');  // deze code zal uit item app het emailadres opzoeken en gebruiken

?>

Opgave G1.2 - Configuratie aanpassen

Pas config.php aan zodat de verbinding met de database goed staat

 

Uitwerking

Routing

Omdat elk verzoek dat binnenkomt naar index.php wordt gestuurd moeten we iets slims doen om de bezoeker wel op de juiste pagina te krijgen. Wij maken hiervoor gebruik van de /app/router.php.

 

Stel we bezoeken de pagina http://localhost/home
In onze router.php moeten we dan een route aanmaken. Elk normaal verzoek gaat via een get.

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

In router.php zal van boven naar beneden gekeken worden of het verzoek 'home' is gedefinieerd. Zo ja dan zal er verwezen worden naar dat script. In boven staande route is dat naar /app/controllers/home.php

Indien er geen route bestaat zal een pagina niet gevonden error (404) verschijnen.

Controllers

In de controllers komt alle logica en alle database transacties te staan. Stel je wilt op een pagina alle gebruikers tonen. In dat geval zal je een in je controller verbinding met de database maken, alle users ophalen uit de database. En daarna deze gegevens meegeven aan een view.

Voorbeeld /app/controllers/users.php

<?php
$db = new Database();
$users = $db->query("SELECT name FROM users")->fetchAll();

view('users',[
    'users' => $users,
]);

Deze code wordt in een later deel van de cursus uitgelegd

Views

In de views staat de opmaak en HTML code die de gebruik te zien krijgt.
In de meeste gevallen zal een view een hele pagina zijn. Maar view kan ook een deel van een pagina zijn. Zo staan er in het project al een aantal views gedefinieerd. Bv. /app/views/home.view.php is een hele pagina. In deze view wordt weer verwezen naar sub-views oa. /app/views/parts/header.view.php. In deze view wordt de HTML header van de pagina opgemaakt. Wanneer je dus iets in de header van een pagina wilt wijzigen kan je dat in die view /app/views/parts/header.view.php doen.

 

Werking

De aanroep van

view('bestandsnaam');

zal de inhoud van /app/views/bestandsnaam.view.php op die plaats weergeven.

 

Met parameters

Soms wil je ook parameters meesturen

view('bestandsnaam',[
    'titel' => 'Hallo wereld',
    'content' => 'Lorum ipsum ...',
]);

Parameters worden altijd in een array meegestuurd. In de view, zal de variabele $titel en $content nu bestaan.
Alle parameters zullen op de achtergrond door de functie htmlspecialchars() gaan zodat een stukje onveiligheid wordt voorkomen.
Mocht je nu een parameter mee willen sturen, waarbij je geen htmlspecialchars() op wilt toevoegen. Dan kan dat als 3e parameter.

view('bestandsnaam',[
    'titel' => 'Hallo wereld',
    'content' => 'Lorum ipsum ...',
],[
    'code' => '<script>alert("Hallo wereld")</script>'
]);

 

Let op

  • De naam van een view eindigt altijd met .view.php
  • Views staan altijd in de directory app/views/
  • Als je deze plaatst in een subdirectory dan moet de subdirectory ook bij de aanroep worden genoemd
    Bv: view('parts/header',[

Opgave G1.3 - Nieuwe pagina

Inleiding

Indien we aan de applicatie nieuwe pagina's willen toevoegen moeten er altijd een paar vaste stappen genomen worden.

  • Toevoegen van een controller
  • Toevoegen van een view
  • Toevoegen van een route die verwijst naar de controller
  • Link naar de pagina

De volgorde van deze stappen maakt niet uit.

De controllers wordt geplaatst in app/controllers

De views worden geplaatst in app/views

En de route kan worden toegevoegd aan app/router.php

Om de bezoeker te laten weten dat een pagina bestaat zou je bijvoorbeeld een link in het menu kunnen maken naar de betreffende pagina.

Opgave

Maak een contactpagina. Voeg maak onderstaande bestanden en voeg deze toe aan het framework in de juiste directory.

Controller: /app/controllers/contact.php
View: /app/views/contact.view.php
In /app/router.php => $route->get('contact','controllers/contact.php');
In het menu link naar de pagina:
<a href="/contact">Contact</a>

Het menu kan je vinden in het bestand /app/views/parts/navigatie-menu.view.php

 

De inhoud van de controller zal alleen een verwijzing zijn naar de view. Kijk in de CheatSheet of kijk af bij de controller van home.php

In de view geef je wat informatie over de website.

 

 

Uitwerking:

Controller: app/controllers/contact.php
View: app/views/contact.view.php
router.php
app/views/parts/navigatie-menu.view.php

I Database gebruiken

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.

 

Voorbeeld:

class Leerling
{
    public string $naam;
    public int $geboortedatum;
    public string $klas;
    
    public function __construct(string $naam, int $geboortedatum, string $klas)
    {
        $this->naam = $naam;
        $this->geboortedatum = $geboortedatum;
        $this->klas = $klas;
    }
    
    public function getInfo(): string
    {
        return "Naam: $this->naam, Geboortedatum: $this->geboortedatum, Klas: $this->klas";
    }
}

Een class is de blauwdruk van een object. Het beschrijft de eigenschappen (bv. naam, geboortedatum, klas) en de methode / functies (bv. getInfo)

Een object is het gebruik van de class voor iets. Zo is 'Piet' een object met als type Leerling. Bij een object zijn de eigenschappen van de class ingevuld.

 

De eigenschappen / variabele en functies in de class kunnen op drie manieren worden gedefinieerd.

  • public (overal toegankelijk)
  • protected (toegankelijk voor overerving)
  • private (alleen toegankelijk voor de class zelf)

 

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. Gebruiken we het Database object (Database.php)

Dit object staat in onze src directory. Bij elk verzoek aan de database zal deze code gebruikt worden. Alle bestanden in de src directory hoef je nooit aan te passen. Suggesties voor verbeteringen van deze bestanden zijn altijd welkom.

De code van Database.php hoef je niet te kennen.

Om Database.php te laten werken moeten in het configuratie bestand (config.php) de verbinding met de database worden aangemaakt. Als het goed is heb je dit bestand al aangepast bij een eerdere opdracht.

<?php
return [
  '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',
  ],
];

Bij elke website bezoek zal het Database object worden ingeladen (staat in index.php)

//Database class
require __DIR__ . "/../src/Database.php";

 

 

Opgave - Een post tonen op de homepagina

Opgave I1.1 - controller aanmaken

We gaan nu even testen of onze database connectie werkt en of we gegevens uit de database kunnen halen.

Hiervoor voegen we aan onze controllers/home.php de code toe om de meest recente post uit de database op te halen.

<?php

$db = new Database();
$post = $db->query("SELECT * FROM posts ORDER BY id DESC LIMIT 1")->fetch();

view("home", [
    'post' => $post,
]);

Verklaring:
We starten met PHP code

<?php

We maken een database object aan met variabele naam $db (kies hiervoor een logische naam)

$db = new Database();

We gebruiken onze database object om een query op de database uit te voeren. We selecteren in dit geval de meest recente "post" en stoppen dit in $result.

$post = $db->query("SELECT * FROM posts ORDER BY id DESC LIMIT 1")->fetch();

$post 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. De titel van de geselecteerde post is dan bv $post['title']

Daarna geven we de gegevens mee aan de view. In onze view zal nu $post als variabele bestaan.

 

PHP sluiten we alleen af als er nog andere code volgt. Anders laten we dit weg. Dus nu laten ?> kan weggelaten worden.

 

Uitwerking:

app/controllers/home.php

 

Opgave I1.2 - view aanmaken

View home.view.php

We openen het bestand app/views/home.view.php en wijzigen de code in het volgende.

<?php
view("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"><?= $post['title'] ?></h2>
            <?= $post['content'] ?>
        </div>
    </div>

<?php
view("parts/footer");

Verklaring:

Om het er een beetje uit te laten zien wordt tailwindcss gebruikt voor de opmaak.
- een rand die een beetje rond is border border-1 rounded
- padding van 4px p-4
- lichtgrijze achtergrond bg-gray-50

De gegevens uit de database worden getoond door

<?= $post['title'] ?>

Dit is een snel notatie voor

<?php echo $post['title']; ?>

Met als resultaat

http://localhost

Uiteraard mag je de opmaak ook zelf vormgeven. Probeer wat met de tailwindcss te spelen, zodat het eruit komt te zien als jij graag zou willen.

Uitwerking:

app/views/home.view.php

Opdracht ... tonen van posts

We gaan in deze opdracht een nieuwe pagina aanmaken waarop alle 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

 

Opgave I2.1 - Controller

 

- roep de database class aan en zet dit in variabele $db
- Maak een query die alle posts ophaalt en zet dit in $result, ipv fetch() gebruiken we nu fetchAll() omdat dit meerdere records betreft.
- we roepen de view posts aan en geven $result mee aan de view.

 

* Je mag de cheatSheet gebruiken (ook bij de toets). Deze staat ook in je /webroot

 

Uitwerking:

/app/controlleres/posts.php

 

Opgave I2.2 - View

 

In de view:

Gebruik een foreach loop om door het resultaat te lopen bv

<?php foreach ($posts as $post): ?>
    <!-- hier komt de code voor het weergeven van een post -->
<?php endforeach; ?>

Je kan home.view.php als basis gebruik voor dit bestand. Waarschijnlijk kom je er nog niet helemaal zelf uit. Zeker niet erg, bij het antwoord kan je altijd even afkijken.De opmaak is hierbij nu nog niet belangrijk. Maar als je het leuk vindt, mag je er wat aandacht aan besteden.

 

Uitwerking:

/app/views/posts.view.php

 

Opgave I2.3 - Route

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

$route->get('posts','controllers/posts.php');
Verklaring
$route->get  Er wordt verstuurd d.m.v. get
'posts' Er wordt gekeken of het verzoek http://localhost/posts is
'controllers/posts.php' Je wordt doorgestuurd naar dit bestand
 
Als het goed is kan je nu de posts pagina bezoeken dmv. http://localhost/posts
 
Uitwerking:
 

Opgave I2.4 - Menu

Pas de view app/views/parts/navigatie-menu.view.php aan met een link naar 'posts'
<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>
In bovenstaande code is vooral de href="/posts" belangrijk. Dit is de verwijzing naar de juiste pagina
 
Uitwerking:

J Formulieren CRUD (create-read-update-delete)

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-store">
    <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 CheatSheet.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 ChatGPT, CoPilot of Google Gemini vragen stellen over het gebruik van formulieren.

 

Verstuurde data

De data die d.m.v. een formulier wordt verstuurd. Kan op verschillende manieren gebruikt worden.

GET

Formulier met method get

<form method="get" action="/posts-create">
    <input type="text" name="variabele_naam">

De verstuurde variabele staat dan in de $_GET array

$_GET['variabele_naam']

Post

Formulier met method post

<form method="post" action="/posts-store">
    <input type="text" name="variabele_naam">

De verstuurde variabele staat dan in de $_POST array

$_POST['variabele_naam']

 

In beide gevallen is 'variabele_naam' de veld naam van het veld in het formulier. Dus vul hier een logische naam voor in. Bv. voornaam, leeftijd, etc.

Request object

Een andere manier om de meestuurde variabele te gebruiken is via het Request object. Dit object wordt bij elk http request aangemaakt. En heeft wat handige eigenschappen die later nog besproken zullen worden.

Eén van de voordelen is het dat de meegestuurde data uit een formulier in het Request object wordt geplaats en gebruikt kan worden.

Indien het formulier met een method POST of GET wordt verstuurd is het meegestuurd altijd op de volgende manier beschikbaar:

$request->variabele_naam

 

Uiteindelijk heb je dus drie manieren om de meegestuurde variabele te gebruiken:

$_GET['variabele']

$_POST['variabele']

$request->variabele

 

Voorbeeld

Dit formulier wordt verstuurd met post.
De verstuurde gebruikersnaam kan je op twee manieren gebruiken

$request->username;

$_POST['username'];

CSRF protection

Inleiding

Wanneer we een formulier hebben (kan ook alleen een button zijn) om een 'Post' te verwijderen is er ook een url beschikbaar om dat te doen bv. http://localhost/posts-destroy/4

Bij het volgen van de link zal 'Post' met id=4 worden verwijderd. 

Ook buiten onze website kunnen nu 'Posts' verwijderd worden. Het zou dus mogelijk zijn om vanaf een andere website een post in onze database te verwijderen. Dit is een beveiligingsrisico. Een oplossing daarvoor is een CSRF token.

 

CSRF token

Elke wijziging in de database zou alleen verstuurd moeten worden d.m.v.een method="post". In het formulier kunnen we dan een CSRF-token opnemen. Dit is een lange reeks tekens die door het framework wordt gemaakt en wordt opgeslagen in een SESSION. Op het moment dat het formulier naar onze website wordt verstuurd zal het framework controleren of dit een geldige token is. Zo ja, dan zal doorverwezen worden naar de controller. Zo niet dan zal het verzoek afgewezen worden. Deze token controle voeren we dan ook alleen uit bij verzoeken via een POST. Alles via GET laten we altijd door.

 

Beschikbaar hiervoor zijn de functies csrf() en validateToken().

 

Gebruik van CSRF tokens

Alleen bij POST

Aan elk formulier dat wordt verstuurd met POST moet een csrf token worden toegevoegd om het te laten werken. Anders krijg je de boodschap:

401 Geen toegang: CSRF-token mismatch error.

Een CSRF-token toevoegen is vrij eenvoudig

<?= csrf() ?>

De code voor de volledige verwijder button wordt dan:

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

Het is nu niet meer mogelijk om met externe scripts posts te verwijderen.

De <?php csrf() ?> zal worden vervangen door een verborgen input veld met als name="_token" en als value een flink lange reeks willekeurige letters en getallen.

<form action="/posts-store" method="post">
    <input type="hidden" name="_token" value="b754d4e0eae5154d8470acd177561d6f6e23289e349e7682bd6e4dc5cc6b176c586047e94fbef8b6adb8eda6d32d5b7c1d3c9ffc821140487117725b6235b58f8372d54789d993d4535870af23019feb1ab2a983648453b93fe527cb0bb7bc15e4a2fc57adec0fdcfd225cac3a71db52d65dd72d6237f93bf32d8f925a515a16fe48641a2786fa9eabcc4b4bf1d75a56e4b02b0a0481f0b177744a80b4785e72f946f7224ac62799769efbbe134274ebd005765546eaf4a8721f117032f58ac6ec367a9c1afdac8951d541552ee36ff104904d56b902334651928148005dc7fded2896ee061ffe018d1c19e973a3b31fda80c43b8b2a4c0383e35106c6ce8b83">            <input type="text" name="title" placeholder="Titel" required="" value=""><br>

Als je meer wilt weten over csrf protectie kan je dat  hier lezen.

 

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.

 

Omdat we bij zoeken niets invoeren/wijzigen in de database versturen we het formulier met een method="get". Het formulier krijgt één input veld van het type="text". Voor de button heb je twee mogelijkheden.

<input type="submit" value="Zoek" class="border b-gray-600 rounded py-1 px-2 hover:bg-gray-100 cursor-pointer">

of

<button class="border b-gray-600 rounded py-1 px-2 hover:bg-gray-100 cursor-pointer">Zoek</button>

De opmaak van de button (class) mag jij zelf bepalen.

 

Opgave J1.1

 

Voeg aan views/posts.view.php onder <h1> tag een formulier toe

Eigenschappen formulier:

  • verstuur methode is get
  • het moet verstuurd worden naar /posts
  • heeft één tekstveld <input type="text" name="q" ... (q van query)
  • een verstuur button met een tekst als zoek, search of iets dergelijks

 

Voor de button kan je gebruiken maken van de volgende tailwindcss class:
class="border border-1 rounded-md px-2 py-1 hover:bg-gray-100 cursor-pointer"

Gebruik voor het input veld tailwindcss class:
class="border border-1 rounded-md px-2 py-1"

 

Uitwerking:

/app/views/posts.view.php

 

Zoeken in database

We willen nu gaan zoeken in de posts tabel met de meegestuurd waarde uit het zoekveld. Een mogelijke query zou er zo uit kunnen zien:

"SELECT * FROM posts WHERE title LIKE '%zoek%' OR content LIKE '%zoek%'"

In dit geval zou het meegestuurde zoekveld de waar 'zoek' bevatten.

We willen dus voor 'zoek' de meegestuurde waarde invullen, dat kan op een aantal manieren.

"SELECT * FROM posts WHERE title LIKE '%".$request->q."%' OR content LIKE '%".$request->q."%'"

Hierboven vullen we letterlijk tussen de %% tekens hetgeen we hebben meegestuurd in het zoekveld.
Dit heeft echter een groot risico.

SQL-injecties

SQL-injecties zijn een veelvoorkomend beveiligingsrisico in webapplicaties waarbij een aanvaller kwaadaardige SQL-code invoert in een invoerveld. Deze code wordt vervolgens door de server uitgevoerd, waardoor de aanvaller ongewenste toegang kan krijgen tot de database.

Hoe werkt een SQL-injectie?

In bovenstaande query zou je het volgende kunnen doen. Stel je verstuurt het formulier met q= ';DROP DATABASE code_wizard;SELECT * FROM posts WHERE content LIKE '
Wanneer de query dan wordt uitgevoerd. Wordt er eerst een select-query gedaan en daarna wordt de database verwijderd. Voor DROP DATABASE kan natuurlijk ook een INSERT INTO users ... worden gedaan etc.

Dit is iets wat we zeker willen voorkomen.

Hoe voorkom je SQL-injecties?

Wij kunnen SQL-injects voorkomen door in de query parameters op te nemen die we later een waarde geven. Deze parameters worden net als bij de htmlspecialchars() methode veilig ingevoerd.

We hebben hiervoor twee manieren

Optie 1 door het gebruik van vraagtekens

$posts = $db->query("SELECT * FROM posts WHERE title LIKE ? OR content LIKE ?", [
    "%$request->q%",
    "%$request->q%",
])->fetchAll();

Omdat er twee vraagtekens in de query staan moeten we ook twee parameters meegeven

Optie 2 door het gebruik van parameters

$posts = $db->query("SELECT * FROM posts WHERE title LIKE :q OR content LIKE :q", [
    'q' => "%$request->q%"
])->fetchAll();

Omdat :q twee keer hetzelfde is hoeft deze maar één keer als parameter meegestuurd te worden.

Samengevat

$db->query(param1, param2)->fetchAll()
param1 is de query
param2 is een array met parameters

Query met één resultaat

$db->query("SELECT.. ",[
    $request->param1
])->fetch();

gebruiken we de fetch() methode

Query met meerdere resultaten

$db->query("SELECT.. ",[
    $request->param1
])->fetchAll();

gebruiken we de fetchAll() methode

 

Opgave J1.2 - Afhandeling zoekveld

 

Nu gaan we aan het werk in controllers/posts.php

Stap 1
Maak een variabele $db met type Database()

Stap 2
Controleer of er iets is verstuurd: if( $_GET['q']??false ){
Of met het Request object if( $request->q??false ){
??false geeft de variabele de waarde false als deze niet bestaat.

Stap 3
Doe een query en stop het resultaat in $posts (kijk af bij de CheatSheet)

Stap 4
Gebruik view posts en geef variabele $posts mee.

 

Uitwerking

/app/controllers/posts.php

 

Een post bekijken - VIEW

Om een post te kunnen bekijken heb je minimaal 3 dingen nodig

  1. Route in router.php bv.
    $route->get('posts-show/{id}','controllers/posts-show.php');
  2. Controller bv. posts-show.php
  3. View posts-show.view.php
  4. Optioneel een linkje naar de betreffende pagina
    <a href="posts-show/67">Show post 67</a>

    Of dynamisch (bv)

    <a href="/posts-show/<?= $post['id'] ?>" class="text-indigo-500">Open</a>

Route

In de route zie je {id} dit is een manier om het id (primaire sleutel) van een post te kunnen meesturen. Op die manier weet je welke post je uit de database moet ophalen.

In de controller kan je dan gebruik maken van

$request->id

Indien je op de link van item 4 klikt zal het $request->id 67 zijn

Controlller

In de controller zullen de volgende stappen worden genomen.

  1. Valideren of id is meegestuurd
  2. Database object aanmaken
  3. De betreffende post ophalen uit de database dmv een query
  4. Indien er niets gevonden wordt sturen we de gebruiker naar een 404 pagina niet gevonden.
  5. De post meegeven aan de view
<?php
// stap 1 valideren of de id bestaat
$request->validate([
    'id' => 'required'
]);

// stap 2 database Object aanmaken
$db = new Database();

// stap 3 query uitvoeren
$post = $db->query("SELECT * FROM posts WHERE id = :id", [
    'id' => $request->id
])->fetch();

// stap 4 controleren of de post bestaat
if(!$post) {
    abort(404);
}

// stap 5 post meegeven aan de view
view("posts-show", [
    'post' => $post
]);

Bij stap 1 wordt de validatie gedaan. Dit stukje zal later nog worden toegelicht in paragraaf K Validatie

In stap 4 zie je if( !$post){    De ! hierin betekent not. Dus als de $post null of leeg is

View

De view is niet heel spectaculair, als het goed is beschik je al over de kennis om die zelf in te vullen

 

Opgave J2.1

Maak de route aan in router.php (code staat bij stap 1)
Maak de controller app/controllers/posts-show.php en copy de juiste code hierin
Maak de view app/views/posts-show.view.php en zorg dat de juiste gegevens van de post zichtbaar zijn. Minimaal de title en content.

Tip. Copy-paste de code van een reeds bestaande view en pas aan wat nodig.

 

Uitwerking

app/router.php
app/controllers/posts-show.php
app/views/posts-show.view.php

 

Tonen van gebruiker bij post

Een post is gemaakt door een bepaalde gebruiker. In de database is dit aangegeven in het veld user_id.

Het zou natuurlijk mooi zijn om bij het tonen van een post ook de betreffende gebruiker op het scherm te tonen.

 

Opgave J2.2

Pas de query in de controller aan zodat ook de gegevens van de schrijver van de post worden opgehaald.

Pas de view aan zodat ook de schrijver van de post wordt getoond.

 

Uitwerking

app/controllers/posts-show.php

app/views/posts-show.view.php

 

De posts-show nuttig gebruiken

In onze overzichtspagina 'posts' staat nu bij elke tekst een lange tekst. Deze teksten gaan we inkorten en een 'lees meer' toevoegen.
Als je op 'lees meer' klikt ga je naar de show pagina.

De 'lees meer' is een link naar /posts-show/id waarbij het id het id van de post.

De tekst inkorten kan d.m.v. de PHP functie substr()

substr("Hello world!", 0, 5); // outputs "Hello"
substr("Hello world!", 0, 3); // outputs "Hel"

En link kan er bijvoorbeeld zo uit zien

<a href="/posts-show/<?= $post['id'] ?>" class="text-indigo-500">...lees meer</a>

 

Opgave J2.3

Kort de content van de post in tot 25 tekens d.m.v. substr() en zet daarachter een link om meer te lezen

 

Uitwerking

/app/views/posts.view.php

 

Een post toevoegen - CREATE

Opgave J3.1 - Routes

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 /posts-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 /views/posts-create.view.php toont een formulier met een action="/posts-store" hier wordt het formulier naar toegestuurd.
  • posts-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 user_id een verplicht veld is en we inloggen nog niet hebben behandeld voeren we tijdelijk voor user_id=63 in. Je mag natuurlijk ook een andere user_id gebruiken. Maar zorg er wel voor dat deze in de database bestaat.

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('posts-create', "views/posts-create.view.php");
$route->post('posts-store', "controllers/posts-store.php");
 
Uitwerking:
 

Opgave J3.2 - View posts-create

Maak een bestand /app/views/posts-create.view.php

Copy-paste de inhoud van een andere pagina view. Om als basis te gebruiken.

Voeg in de view een formulier toe om een post in te voeren

  • Form met met method="post" en action="/posts-store"
  • csrf() token
  • Input veld met name="title"
  • Textarea veld met name="content"
  • Verstuur button

Als class voor het input veld en de textarea kan gebruik worden gemaakt van

class="border-1 rounded-md py-1 px-2"

Uitwerking

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

 

Opgave J3.3 - Controller posts-store.php

 

Maak een nieuw bestand controller/posts-store.php

  • Valideer of de title en content zijn meegestuurd
    $request->validate([
        'title' => 'required',
        'content' => 'required'
    ]);
  • Maak een $db aan met de database object
  • Maak een insert-query (tip voer een nieuwe post in met Heidi-SQL en copy-paste de query onderaan het scherm. Iets als INSERT INTO ...)
    Bij een INSERT/UPDATE query is geen ->fetch() nodig
  • Daarna stuur je de gebruiker naar http://localhost/posts met de code
    redirect("/posts");

 

Uitwerking

/app/controllers/posts-store.php

 

Opgave J3.4 - Link naar create

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

...
<div class="sm:mx-10">
    <h1 class="text-3xl my-4">Posts</h1>
    <a href="/posts-create" class="text-indigo-600 hover:text-indigo-400">Nieuwe post</a><br>
    <form action="/posts" method="get">
...

Dit is de link naar de /posts-create

<a href="/posts-create" class="text-indigo-600 hover:text-indigo-400">Nieuwe post</a><br>

 

/app/views/posts.view.php

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.

In ons framework zit al een toast bericht ingebakken. Dus die kunnen we eenvoudig gebruiken

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 (optioneel)
- tijd tot verdwijnen (optioneel)

Toast aanpassen

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.

 

Opgave J4.1 - Toast

 

Wanneer een post succesvol is opgeslagen in de database wil je graag de flash (toast) laten zien.

Voeg onderstaande code op de juiste plaats toe in controllers/post-store.php

flash("Post is opgeslagen", true, 3000);

 

Uitwerking

/controllers/posts-store.php

Een Post verwijderen - DELETE

Waarschijnlijk heb je bij het testen wat posts toegevoegd. Die je misschien niet wilt tonen op je pagina. Deze zou je graag willen kunnen verwijderen. We gaan deze functionaliteit maken door een verwijder button toe te voegen bij elke 'post'. Normaal gesproken doe je dit op een admin pagina of zoiets.

Onder elke post voegen we een verwijder button toe.

Om te weten welke post verwijderd moet gaan worden. Is het belangrijk dat de id van de post wordt meegestuurd bij het klikken op de button. Verwijderen is een actie op de database dus dit moet gedaan worden met een method="post".
Let op dat we ook een csrf-token moeten meesturen, omdat we action="post" gebruiken.

Er zijn twee manieren om een id mee te sturen.

Optie 1
Je stuurt de id mee in een hidden input veld.

<form method="post" action="/posts-destroy">
    <?=csrf()?>
    <input type="hidden" name="id" value="<?= $post['id'] ?>">
    <input type="submit" value="Verwijder"  class="...">
</form>

De route in je router zal er dan als volgt uit zien.

$route->post('posts-destroy', "controllers/posts-destroy.php");

Optie 2
Je stuurt de id mee in de url van je action

<form method="post" action="/posts-destroy/<?= $post['id'] ?>">
    <?=csrf()?>
    <input type="submit" value="Verwijder"  class="...">
</form>

De route in je router zal er dan als volgt uitzien.

$route->post('posts-destroy/{id}', "controllers/posts-destroy.php");

In de route wordt dan gedefinieerd dat na de slash het id volgt.

Met beide opties kan je in de controller posts-destroy.php het meegestuurde id gebruiken

$request->id
// of met
$_POST['id']

 

 

Opgave J5.1 - Verwijderen

 

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 my-2">
        <h2 class="font-bold"><?= $post['title']; ?></h2>
        <?= $post['content'] ?>
        <form method="post" action="/posts-destroy/<?= $post['id'] ?>">
            <?=csrf()?>
            <input type="submit" value="Verwijder" name="delete"
                   class="bg-red-600 text-white rounded px-2 py-1 hover:bg-red-300 cursor-pointer">
        </form>
    </div>
<?php endforeach; ?>
...

En voeg de juiste route toe

/app/views/posts.view.php
/app/router.php

 

Opgave J5.2 - Verwijderen (controller)

 

Nu gaan we een controller aanmaken die de post verwijderd

  • Maak een controller: controllers/posts-destroy.php
  • Als het goed is heb je de route al aangemaakt
  • Doe een validatie of id is meegestuurd
  • Maak een $db object aan van de Database
  • Voer een verwijder query uit (verwijder een rij in HeidiSQL en kijk onder aan de pagina hoe een DELETE query werkt. Of kijk in de SQL cheatSheet)
  • Geef een flash bericht succesvol verwijderd / of eventueel niet succesvol
  • Verwijs door naar /posts

 

Uitwerking

/app/controllers/posts-destroy.php

 

Opgave J5.3 - Bevestiging vragen

Wat we hebben gemaakt werkt. Maar graag zou je na het klikken op een verwijder button eerst een bevestiging van de gebruiker willen hebben.

In het framewerk is daarvoor de logica al aanwezig.

Om dit voor elkaar te krijgen kan je de verwijder button vervangen door de delete-button view.

<?php foreach ($posts as $post): ?>
    <div class="border border-1 rounded p-4 bg-gray-50 my-2">
        <h2 class="font-bold"><?= $post['title']; ?></h2>
        <?= $post['content'] ?>
        <?php
        //Opgave J5.3 - Bevestigingsdialoog voor het verwijderen van een post
        view("parts/delete-button", [
            'titel' => 'Post verwijderen',
            'content' => 'Weet je zeker dat je deze post wilt verwijderen?',
            'action' => '/posts-destroy/' . $post['id'],
        ]); ?>
    </div>

De view delete-button heeft drie parameters nodig

  • titel
  • content
  • action

Vervang de verwijder button door de delete-button view.

Uitwerking

/app/views/posts.view.php

Een POST wijzigen - UPDATE

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. Route posts-edit/{id} toegevoegd
  3. Controller posts-edit haalt de gegevens van de betreffende post op uit de database en stuurt deze naar de view
  4. posts-edit.view toont de gegevens uit de database in een formulier met update button
  5. Bij klik op update button wordt formulier verstuurd naar route: posts-update/{id}
  6. De controller posts-update doet een update op de database en stuurt terug naar /posts

 

Opgave J6.1 - wijzig button toevoegen

Voeg een 'Wijzig' button toe aan de 'posts' overzichtspagina. Doe dit als een formulier net als bij de verwijder button.

  • method="get"
  • action="/posts-edit/{id}"  waarbij {id} = $post['id'] is dus: action="/posts-edit/<?= $post['id'] ?>"
  • Geef de button als opmaak: class="border-1 border-gray-600 rounded-md px-2 py-1 hover:bg-gray-100 cursor-pointer"

Tip:

zet beide formulieren binnen een div met class="flex justify-between" voor een wat mooiere opmaak

<div class="flex justify-between mt-4">
    <!-- button wijzig -->
    <!-- button verwijder -->
</div>
 
Uitwerking
 

Opgave J6.2 - Routes toevoegen

 

Voeg twee nieuwe route's toe aan router.php

$route->get('posts-edit/{id}', "controllers/posts-edit.php");
$route->post('posts-update/{id}', "controllers/posts-update.php");

 

Uitwerking

 

 

 

Opgave J6.3 - Edit controller

Maak een nieuw bestand controllers/posts-edit.php

  • Valideer of id is meegestuurd
  • zo ja, haal de gegevens op uit de database
  • Stuur de opgehaalde gegevens naar de view: posts-edit

 

Uitwerking

/app/controllers/posts-edit.php

 

Opgave J6.4 - Wijzig formulier

Maak en nieuw bestand views/posts-edit.view.php

  • Copy-paste de inhoud van bv home.view.php en haal weg wat niet nodig is.
  • Pas de titel aan (regel 2)
  • Maak een formulier met:  method="post" en action="/post-update/<?= $post['id']?>"
  • csrf-token
  • textfield voor title (met value="<?= $post['title'] ?>" )
  • textarea voor content (met value="<?= $post['content'] ?>" )
  • submit button om te versturen

 

Uitwerking

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

 

Opgave J6.5 - Wijzig controller

 

Maak een controller controller/posts-update.php

  • Valideer of id, title en content zijn verstuurd
  • Maak een database object aan
  • Voer een update-query uit (gebruik cheatSheat voor eventuele hulp of doe een wijziging met HeidiSQL en kijk onderaan)
  • Geef een flash message "Post succesvol gewijzigd"
  • verwijs door naar /posts

 

Uitwerking

/app/controllers/posts-update.php

 

Routes

We hebben nu alle verschillende database transacties gedaan. Meestal geldt voor de meeste tabellen in de database dat je een view, create, update, delete nodig hebt. Dit wordt ook wel CRUD (create, read, update, delete) genoemd.

De volgende routes zijn daarvoor nodig

  • index (/posts)  GET
  • show (/posts-show/{id})  GET
  • create (/posts-create)  GET
  • edit (/posts-edit/{id})  GET
  • store (/posts-store)  POST
  • update (/posts-update/{id})  POST
  • destroy (/posts-destroy)  POST

 

Je bent in alle  bestanden vrij om de naamgeving te geven die jij zelf handig vindt. Toch kunnen afspraken daarbij ook handig zijn, zeker als je met een team samenwerkt. Je ziet dat we nu ook al flink wat bestanden hebben aangemaakt alleen voor de post. Je zou deze ook in bv een mapje kunnen plaatsen met de naam posts

 

URL Method Endpoint Beschrijving
/posts get /controllers/posts/index.php Overzicht van alle posts
/posts-create get /controllers/posts/create.php Aanmaken nieuwe post
/posts-store post /controllers/posts/store.php Opslaan van de nieuwe post
/posts-show/{id} get /controllers/posts/show.php Bekijken van een post
/posts-edit/{id} get /controllers/posts/edit.php Bewerken van een post
/posts-update/{id} post /controllers/posts/update.php Opslaan van een bewerkte post
/posts-delete/{id} post /controllers/posts/destroy.php Verwijderen van een post

 

 

Selectbox

Een selectbox is een formulierveld dat vaak in HTML formulieren wordt gebruikt.

Hieronder is een klein stukje HTML code die een selectbox maakt waar je kan kiezen uit drie personen. Wanneer het formulier wordt verstuurd zal het getal bij 'value' gebruikt worden. Dus als Piet is geselecteerd zal user_id=2 worden verstuurd.

<select name="user_id" class="border border-gray-600 rounded-md px-2 py-1">
    <option value="1">Henk</option>
    <option value="2">Piet</option>
    <option value="3">Klaas</option>
</select>

Meestal worden de keuze opties bij een selectbox uit de database gehaald. Dit gebeurt in de controller. Het resultaat van de query wordt dan aan een view meegegeven.

Controller

<?php

//gegevens uit database ophalen
$db = new Database();

$users = $db->query("SELECT * FROM users ORDER BY name")->fetchAll();

//aan de view de gegevens doorgeven
view('admin-users',[
    'users' => $users
]);

View

In de view zal een loop worden gebruikt om alle gebruikers stuk voor stuk in een HTML option veld te plaatsen

<form method="post" action="/do-iets-store">
    <?=csrf()?>

    <select name="user_id" class="border border-gray-600 rounded-md px-2 py-1">
        <?php foreach ($users as $user): ?>
            <option value="<?= $user['id'] ?>"><?= $user['name'] ?></option>
        <?php endforeach; ?>
    </select>

</form>

Dus bij de selectbox wordt de id van de user gebruikt als value en de naam van de user als label.

K Validatie

Validatie

Er zijn twee vormen van validatie:

  • client side validatie
  • server side validatie

Client side validatie

Let op deze validatie heeft twee grote nadelen:

Kwetsbaarheid voor manipulatie: Client-side validatie vindt plaats op de computer van de gebruiker, wat betekent dat kwaadwillende gebruikers de validatieregels gemakkelijk kunnen omzeilen of uitschakelen door de client-side code aan te passen of uit te schakelen. Dit maakt het onbetrouwbaar voor het beveiligen van gevoelige gegevens of kritieke processen.

Browserafhankelijkheid: Client-side validatie kan verschillen tussen browsers.

Maar deze validatie is zeer eenvoudig toe te passen dus wel wenselijk om als extra beveiliging te gebruiken.

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.
 
Door middel van DevTools is de bron van de pagina aan te passen en kunnen eenvoudig de client validaties worden verwijderd. Client validatie is handig, maar niet voldoende.
 
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
We kunnen hiervoor ons $request object gebruiken. Deze is altijd beschikbaar en bevat een validate methode. Deze methode wil een array als input met als key het verstuurde veld en als value de validatie regel die toegepast moet worden.
Onderstaande hebben we daarvoor al eens gebruikt.
$request->validate([
    'title' => 'required',
    'content' => 'required'
]);

Indien de validatie mislukt zal de gebruiker terug worden gestuurd naar de pagina waar hij/zij vandaan kwam. Op de pagina is dan de methode errors() beschikbaar om de eventueel validatie fouten te tonen.

Stel in ons voorbeeld van post-store.php dat content leeg zou zijn. Dan worden we teruggestuurd naar de pagina post-create.Op deze pagina willen we graag de foutmelding tonen. Bijvoorbeeld in het rood onder het input veld.

<textarea name="content" placeholder="Content..." class="border-1 rounded-md py-1 px-2"></textarea>
<?php if (errors('content')): ?>
    <p class="text-red-500 text-sm my-2"><?= errors('content') ?></p>
<?php endif; ?>
<br>

Indien er een error op het veld content is dan zal in het rood de error boodschap verschijnen.

Uiteraard kunnen we hetzelfde toevoegen bij het veld title.
Je ziet dat het veld title wat misschien wel al gevuld was leeg terugkomt na een validatie fout. Dit is niet wenselijk. De gebruiker moet dan door één foutje alles opnieuw invoeren. Om dit te voorkomen kan de helper functie old() gebruikt worden. Dit geeft de waarde weer die was ingevuld voor het versturen.

<form action="/posts-store" method="post">
    <?= csrf() ?>
    <input type="text" name="title" placeholder="Titel" value="<?= old('title') ?>" class="border-1 rounded-md py-1 px-2" required>
    <?php if (errors('title')): ?>
        <p class="text-red-500 text-sm my-2"><?= errors('title') ?></p>
    <?php endif; ?>
    <br>
    <textarea name="content" placeholder="Content..." class="border-1 rounded-md py-1 px-2"><?= old('content') ?></textarea>
    <?php if (errors('content')): ?>
        <p class="text-red-500 text-sm my-2"><?= errors('content') ?></p>
    <?php endif; ?>
    <br>
    <input type="submit" value="Opslaan" class="border-1 rounded-md px-2 py-1 hover:bg-gray-100 cursor-pointer">
</form>

 

 

Opgave K1.1 - errors en old toevoegen

Voeg de error teksten toe (als er een error is) aan de view post-edit.view.php en aan de post-create.view.php. Zoals in het voorbeeld hierboven.

Gebruik de functie old( ) om de verstuurde waarde te tonen. Gebruik hierbij twee parameters. Als eerste de meegestuurde waarde, en als tweede de waarde uit de database, bv:

old('content', $post['content'])

 

Uitwerking

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

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

Validatie regels

Het voorgaande voorbeeld hebben we validatie gedaan op het aanwezig zijn van een meegestuurde waarde. Maar we hebben meer mogelijkheden voor validatie. Zoals een geldig emailadres, lengte maximaal 50, etc...

required
Controleren of veld aanwezig is.

$request->validate([
    'id' => 'required'
]);

integer
Controleren of het veld een geheel getal is

$request->validate([
    'id' => 'integer',
]);

length
Controleren of een string een bepaalde lengte heeft. Gebruik hiervoor: length:3,10 lengte van een string moet tussen de 3 en 10 karakters inzetten (inclusief)

$request->validate([
    'naam' => 'length:2,255',
]);

email
Controleren of veld een geldig email adres is

$request->validate([
    'email' => 'email',
]);

url
Controleren of het veld een geldige URL is

$request->validate([
    'link' => 'url',
]);

date
Controleren of een veld een geldige datum is

$request->validate([
    'geboortedatum' => 'date',
]);

min
Controleren of een getal minimale waarde heeft. Of de lengte van een string minimaal zoveel karakters heeft. Te gebruiken als min:4 (minimaal 4 karakters of cijfer minimaal 4)

$request->validate([
    'naam' => 'min:2',
    'leeftijd' => 'min:16',
]);

max
Controleren of een getal maximale waarde heeft. Of de lengte van een string maximaal zoveel karakters heeft. Te gebruiken als max:4 (maximaal 4 karakters of cijfer maximaal 4)

$request->validate([
    'naam' => 'max:50',      //naam mag niet langer zijn dan 50 tekens
    'leeftijd' => 'max:18',  //leeftijd mag niet hoger zijn dan 18
]);

between
Controleren of cijfer tussen twee waarde zit. Of de string lengte tussen de .. karakters lang is. Te gebruiken als between:4,10

$request->validate([
    'naam' => 'between:2,50',      //naam moet tussen 2 en 50 karakters zijn
    'leeftijd' => 'between:2,18',  //leeftijd moet tussen 2 en 18 karakters zijn
]);

in
Controleren of de waarde voorkomt in een rij gegevens. Te gebruiken als in:red,green,blue De waarde mag dan alleen red, green of blue zijn.

$request->validate([
    'color' => 'in:red,green,blue',     //color moet red, green of blue zijn
]);

not_in
Controleren of de waarde niet voorkomt in een rij gegevens. Te gebruiken als not_in:red,green,blue De waarde mag dan niet red, green of blue zijn.

$request->validate([
    'color' => 'not_in:red,green,blue',     //color mag niet red, green of blue zijn
]);

numeric
Controleren of het veld een getal is

$request->validate([
    'saldo' => 'numeric',     // moet een nummer zijn
]);

image
Controleren of het meegestuurde bestand een afbeelding is

$request->validate([
    'foto'=>'image'
]);

Meerdere regels toepassen op een veld

Op een bepaald veld zijn meerdere validaties tegelijkertijd toe te passen.

$request->validate([
    'title' => 'required|min:3',
    'content' => 'required'
]);

We gebruiken hiervoor de | als scheidingsteken.

Ook mag je alle regels in een array zetten

$request->validate([
    'title' => ['required', 'min:3'],
    'content' => 'required'
]);

Als je een validatie regel mist. Kan je dat uiteraard zelf toevoegen aan het bestand /src/Request.php.

 

Opgave K2.1 - Validatie regels

 

Pas de validatie regels van controller posts-update.php aan

  • Titel en content moeten minimaal 3 tekens lang zijn en moeten ingevuld zijn.
  • Titel mag maximaal 255 tekens hebben

 

Uitwerking

/app/controllers/posts-update.php

L Inloggen

Stappen plan

Het inloggen gaat er schematisch alsvolgt uit zien.

login flow

 

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

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.

In het framework is een helper functie beschikbaar user(). Hiermee kan een ingelogde gebruiker worden getoond. Bv:

user()->email;  //email van de ingelogde gebruiker

Let op: Indien je geen ingelogde gebruiker bent zal bovenstaande code een error opleveren. Dus dit kan alleen gebruikt worden bij een ingelogde gebruiker

Mocht je ergens in je code niet zeker weten of een user is ingelogd kan je ook het volgende gebruiken.

user()?->email; // email van ingelogde gebruiker of null als niet ingelogd

 

Login view

In het framework zit standaard een inlogpagina. Dit is een template dat afkomstig is van tailwindcomponents.
Uiteraard kun je deze pagina volledig naar eigen wens aanpassen. De view staat in /app/views/login.view.php

 
Het inloggen werkt uiteraard via een POST. Via een GET zou het wachtwoord boven in de URL komen te staan. Niet zo prettig als iemand meekijkt. Dus in het formulier is ook de csrf() token te vinden
<form class="space-y-6" action="/login" method="post">
   <?= csrf() ?>

 

Login routes toevoegen

In onze app/router.php staan drie routes die met inloggen/uitloggen te maken hebben

$route->get('login', "views/login.view.php");
$route->post('login', "controllers/login.php");
$route->get('logout', "controllers/logout.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'. En de laatste om uit te loggen.

Login controller

Hier bespreken we kort het bestand /app/controllers/login.php

Voorbeeld van login controller (controllers/login.php)

<?php
$request->validate([
    'email' => ['required', 'email'],
    'password' => ['required']
]);

$db = new Database();

//gebruiker ophalen uit de database
$user = $db->query("SELECT * FROM users WHERE email = ? LIMIT 1", [
    $request->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
        redirect("/");
    } else {
        $_SESSION['errors']['login'] = "Inloggegevens zijn niet correct";
    }
} else {
    $_SESSION['errors']['login'] = "Inloggegevens zijn niet correct";
}

// inloggen is niet gelukt, terug naar login pagina
view("login");

Hieronder in het kort wat er in de stappen wordt gedaan

$request->validate([
    'email' => ['required', 'email'],
    'password' => ['required']
]);

Verklaring: Validatie op het invullen van email adres en wachtwoord

$user = $db->query("SELECT * FROM users WHERE email = ? LIMIT 1", [
    $request->email
])->fetch();

Zoeken naar een user met het betreffende emailadres

if ($user) {
    ...
} else {
    $_SESSION['errors']['login'] = "Inloggegevens zijn niet correct";
}

Verklaring: Indien de $user gevonden is dan gaan we verder. Anders zetten we errors in de $_SESSION zodat deze op het inlogformulier getoond worden.

if (password_verify($_POST['password'], $user['password'])) {

Hier wordt de hash van het wachtwoord dat in de database staat vergeleken met het meegestuurde wachtwoord.
In de database staat het wachtwoord als hash omdat dan het originele wachtwoord niet of zeer moeilijk is te achterhalen uit een hash. Dus mocht een hacker de database in komen, dan heeft hij nog niet de wachtwoorden van de gebruikers. De functie password_verify(...) zal true teruggeven wanneer het wachtwoord overeen komt met de HASH uit de database.

unset($user['password']);
$_SESSION['user'] = $user;

Daarna wissen we het wachtwoord uit de user gegevens
En we zetten de overige gegevens van de $user in een SESSION

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.

Nieuwe gebruiker maken

Bij het maken van een nieuwe gebruiker is het dus belangrijk dat het wachtwoord een HASH is. Om een HASH van een wachtwoord te maken kan de php functie password_hash(...) worden gebruikt.

Bijvoorbeeld:

password_hash($request->password, PASSWORD_BCRYPT); // het meegestuurde password hashen

 

Stel dat we de hash van het password 'secret' willen maken

<?= password_hash("secret", PASSWORD_BCRYPT); ?>

Als je bovenstaande code maar eens TIJDELIJK toe aan /app/views/home.view.php. Elke keer als je de home pagina bezoekt zal er een andere hash op het scherm worden geschreven. Het is niet mogelijk om van de hash weer terug te gaan naar het originele wachtwoord (zonder brute force). 

Uitloggen

In view/parts/navigatie-menu.view.php is een link te vinden om uit te loggen

Heel eenvoudig zou dat er ongeveer zo uitzien. Maar omdat we in het framework meteen een uitklapbaar menu hebben ziet het er iets ingewikkelder uit dan hieronder. Maar het idee blijft hetzelfde

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

Verklaring:

auth() geeft aan of een gebruiker is ingelogd. De functie geeft een boolean terug. True bij ingelogd. False bij niet ingelogd.

Helper functies

Voor de inlog hebben we een aantal helper functies

auth() => boolean wel of niet ingelogd zijn (authenticated user zijn)

if(auth()) {
    echo "Je bent ingelogd";
} else {
    echo "Je bent niet ingelogd";
}

user() => array met alle gegevens van een gebruiker. user()->name zal bijvoorbeeld de naam van de ingelogde gebruiker geven.

<?php if(auth()): ?>
    <p>Goedemorgen <?= user()->name ?></p>
<?php endif; ?>

hasRole( role ) => controleren of een gebruiker een bepaalde rol heeft. Deze functie geeft een boolean terug. Voorbeeld:

if(hasRole('admin')){
    echo "You are an admin";
} else {
    echo "You are not an admin";
}

geeft true als de gebruiker een administrator is. En anders false.

Je kan in de database bij het veld 'role' een gebruiker een bepaalde rol toekennen.

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 de volgende code die een post invoert in de database:

$db->query("INSERT INTO posts (title, content, user_id) VALUES (:title, :content, :user_id)", [
    'title' => $request->title,
    'content' => $request->content,
    'user_id' => 63, // bij ingelogde gebruiker ipv 63 de id van de ingelogde gebruiker: user()->id
]);

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. Dit kan met $_SESSION['user']['id']  of met de veel makkelijkere helper functie user()->id

$db->query("INSERT INTO posts (title, content, user_id) VALUES (:title, :content, :user_id)", [
    'title' => $request->title,
    'content' => $request->content,
    'user_id' => user()->id,
]);

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.
Je router.php kan er dan bijvoorbeeld zo uit gaan zien

//Alleen als je ingelogd bent
if (auth()) {
    //hier komen routes die je alleen kan bereiken als je ingelogd bent
    $route->get('posts-create', "views/posts-create.view.php");
    $route->post('posts-store', "controllers/posts-store.php");
}

//alleen toegankelijk als administrator
if (hasRole('admin')) {
    //hier komen de routes die alleen toegankelijk zijn voor een admin
    $route->post('posts-destroy/{id}', "controllers/posts-destroy.php");
    $route->get('posts-edit/{id}', "controllers/posts-edit.php");
    $route->post('posts-update/{id}', "controllers/posts-update.php");
}

 

 

Opgave L1.1 - Alleen toegankelijk voor ingelogde gebruikers

Pas de volgende bestanden aan zodat je dit alleen door middel van inloggen kan doen.

  • /app/router.php
  • /app/controllers/posts-store.php
  • /app/views/posts.view.php

 

Uitwerking

/app/router.php
/app/controllers/posts-store.php
/app/views/posts.view.php

 

M Model (optioneel)

Model

Wat is een model?

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

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

Aanmaken van een Model

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

Een voorbeeld van een user Model

<?php

class User extends Model
{
    protected $table = "users";
}

Je ziet dat er 'extends Model' achter staat. Dat neemt alle eigenschappen van Model over.

Omdat je alle bestanden moet kunnen gebruiken. Moet je deze wel allemaal toevoegen aan je index.php 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 query
dd($users);

Deze query builder heeft op de achtergrond uiteraard een protectie tegen SQL-injecties

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

De eerste gevonden user ophalen

$user = (new User)->first();
dd($user);

User aanmaken

//aanmaken van een user
$user = (new User)->create([
    'name' => 'Piet Puck',
    'email' => 'testttw@mail.nl',
    'password' => password_hash('password', PASSWORD_BCRYPT)
]);
dd($user);

Zoeken van een user op id

//zoeken van een user op id
$user = (new User)->find(2);
dd($user);

Gegevens van een opgehaalde user tonen

$user = (new User)->find(2);
//voornaam van de gebruiker op het scherm schrijven
echo $user->voornaam;

Gegevens van een user aanpassen

$user = (new User)->find(2);

//naam wijzigen
$user->name = 'John Doe';

//opslaan in database (alleen als er werkelijk iets gewijzigd is)
$user->save();

of

$user = (new User)->find(1);
$user->update([
    'name' => 'Nieuwe naam'
]);

User verwijderen

//user met id=6 ophalen
$user = (new User)->find(6);
//de zojuist opgehaalde user verwijderen uit de database
$user->delete();

Zelf eigenschappen toevoegen

Bij een User zullen we vaak de volledige naam op het scherm willen tonen. We kunnen hier een methode toevoegen aan User.php

<?php

class User extends Model
{
    protected $table = "users";
    
    public function name(){
        return $this->voornaam." ".
            $this->tussenvoegsel." ".
            $this->achternaam;
    }
}

Let op dat er voor bovenstaande code wel velden in de database moeten bestaan met de namen 'voornaam', 'tussenvoegsel' en 'achternaam' deze velden zijn in onze default database niet aanwezig. De code zal dan ook fouten opleveren bij aanroep.

Nu kun je in je code een user selecteren en snel de naam op het scherm schrijven

$user = (new User)->find(4);
echo $user->name();

Debuggen

Soms wil je graag even zien welke queries er allemaal worden uitgevoerd. Dat kan door de dumpQuerLog() methode te gebruiken. Dit zal alle uitvoerde queries op het scherm schrijven en het script verder afbreken.

$user = (new User)->find(2);
//uitgevoerde queries bekijken
$user->dumpQueryLog();

 

Model User

Opgave M1.1 - Post model

Maak het bestand /app/models/Post.php. (let op dat het bestand met een hoofdletter begint)

Definieer in model Post.php de database tabel die wordt gebruikt.

Voeg toe in /webroot/index.php (op een logische plaats)

require __DIR__ . "/../app/models/Post.php";

 

Uitwerking:

/app/models/Post.php

/webroot/index.php

 

Models en relaties

Opgave M2.1 - Posts van een User

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

app/models/User.php

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

 


Alle geplaatste posts van gebruiker 2 ophalen is nu heel eenvoudig

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

 

Voeg de methode posts() toe aan /app/models/User.php

 

Uitwerking

/app/models/User.php

 

Opgave M2.2 - User bij Post

Uiteraard kan je ook de omgekeerde relatie in model Post toevoegen.

public function user()
{
    return (new User)->where('id', $this->user_id)->get(true);
}

Voeg de methode toe aan Post

Uitwerking

/app/models/Post.php

 

Voorbeeld van gebruik

$post = (new Post)->first();
$post->user()->name; //geeft de naam van de gebruiker die de post heeft gemaakt

 

Post model gebruik

Opgave M3.1

Pas onderstaande controllers aan zodat model post wordt gebruikt in de queries i.p.v. $db

  • /controllers/posts.php
  • /controllers/posts-destroy.php
  • /controllers/posts-show.php

En de volgende view

  • /views/posts-show.view.php
  • /views/posts-edit.view.php

Let op

Wanneer een model wordt gebruikt met (new Post)->find(...)

Dan is het resultaat een object dus bv. $post->title  ipv $post['title']

Bij meerdere resultaten (new Post)  ... ->get()

Dan is het resultaat een array die doorlopen kan worden en dan gebruiken we de array notatie.

 

Uitwerkingen:

Controllers

Views

 

 

N Drop down menu (optioneel)

Voor een drop down menu hebben we een stukje javascript nodig. In dit voorbeeld maken we gebruik van alpinejs. Omdat we niet alles zelf willen schrijven

Stap 1

Deze stap is hoogst waarschijnlijk al gedaan. Zo niet voeg de javascript file toe in onze header van elke pagina dus in views/parts/header.view.php

<script defer src="/scripts/alpine.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

 

N Dynamisch zoeken (optioneel)

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 /app/views/parts/header.view.php in de <head> de volgende scripts toevoegt. Waarschijnlijk is dit al gedaan. Alpine is voor het interactieve, axios is voor het dataverkeer tussen de browser en de webserver.

<script defer src="/scripts/alpine.min.js"></script>
<script src="/scripts/axios.min.js"></script>

/app/views/parts/header.view.php

 

Stap 2

In de map controllers maken we een nieuw map met de naam 'api' en een bestand /app/controllers/api/users.php met de volgende inhoud.

<?php
$request->validate([
    'name' => 'required'
]);

//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 :name
          LIMIT 10", [
    "name" => "%" . $request->name . "%"  //zoeken naar alles wat er op lijkt
] //we hebben twee plekken waar we $name in moeten vullen
)->fetchAll();

// het resultaat geven we terug in json formaat
echo json_encode($users);

/app/controllers/api/users.php

 

Stap 3

Aan onze router.php voegen we twee nieuwe routes toe

if (hasRole('admin')) {
    //hier komen de routes die alleen toegankelijk zijn voor een admin
    $route->get('api/users', "controllers/api/users.php");
    $route->get('users', "views/users.view.php");
}

Maak één of meerdere gebruikers admin, door in de database (HeidiSQL) bij veld 'role' admin in te voeren.

/app/router.php

 

Stap 4

We maken een bestand /app/views/users.view.php

<?php
view("parts/header", ['title' => '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" class="border border-gray-300 rounded p-1">
        <!-- 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?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: $router->get('users-show/{id}', 'controllers/users-show.php');
                    location.href = '/users-show/' + id;
                }
            }
        }

    </script>

<?php
view("parts/footer");

/app/views/users.view.php

 

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

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

O Content-Security-Policy (optioneel)

UNDER CONSTRUCTION

 

Dit onderdeel zou gereed moeten zijn, maar er zit nog ergens een bug waardoor onze JavaScript worden geblokkeerd en de pagina opmaak er niet uitziet.

Uiteraard zijn er bonuspunten te verdienen als jij het probleem oplost!

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

/webroot/index.php

 

csp.php

<?php
header("Content-Security-Policy: base-uri 'self';"
    . "connect-src 'self';"
    . "default-src 'self';"
    . "form-action 'self';"
    . "media-src 'self';"
    . "object-src 'none';"
    . "script-src 'self' 'nonce-" . getNonce() . "' 'unsafe-eval';" // cdn.jsdelivr.net cdn.tailwindcss.com
    . "style-src 'self' 'nonce-" . getNonce() . "';" //cdn.jsdelivr.net cdn.tailwindcss.com
    . "img-src 'self' data:;"
    . "font-src 'self';"
    . "frame-ancestors 'none';"
);

/app/src/csp.php

 

parts/header.view.php

<!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><?= $title??config('app.name') ?></title>
    <script src="/scripts/tailwind.min.js" nonce="<?= getNonce(); ?>"></script>
    <script defer src="/scripts/alpine.min.js" nonce="<?= getNonce(); ?>"></script>
    <script src="/scripts/axios.min.js" nonce="<?= getNonce(); ?>"></script>
    <link rel="stylesheet" href="/styles/default.css" nonce="<?= getNonce(); ?>">
</head>
<body>

Het framework bevat een functie getNonce() die style en javascript kan ondertekenen.

/app/views/parts/header.view.php

 

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

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

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

<style nonce="<?= 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

P Afbeelding uploaden (optioneel)

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 profielfoto-create.view.php

In profielfoto-create.view.php komt het formulier te staan.

In het formulier :

<?php
view("parts/header", ['title' => 'profielfoto']);
view("parts/navigatie-menu");
?>
<?php if (auth()): //dit wil je alleen kunnen als je ingelogd bent ?>

    <div class="m-10">
        <form action="/profielfoto-store" method="post" enctype="multipart/form-data">
            <?= csrf() ?>
            Selecteer een profielfoto:<br>
            <input type="file" name="foto" id="foto" class="border border-1 rounded-md px-2 py-1">
            <!-- om eventuele errors te tonen -->
            <?php if (errors('foto')): ?>
                <p class="text-red-500 text-sm my-2"><?= errors('foto') ?></p>
            <?php endif; ?>
            <br>
            <input type="submit" value="Upload profielfoto" name="submit" class="border border-1 rounded-md px-2 py-1 hover:bg-gray-100 cursor-pointer shadow-sm mt-2">
        </form>
    </div>
<?php endif; ?>
<?php
view("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. Eén voor de view en één voor het opslaan van de foto.

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

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. Het framework maakt het echter makkelijk door het $request object hiervoor te gebruiken. Hierdoor is validatie ook direct mogelijk.

$request->validate([
    'foto' => 'required|image' //valide afbeelding
]);

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.

Hieronder een voorbeeldje van een controller profielfoto-store.php

<?php
$request->validate([
    'foto' => 'required|image' //valide afbeelding
]);


//voeg een upload directory toe aan de configuratie bv:
//$uploadDirectory = config('app.uploadDirectory'); // map waarin de afbeeldingen worden opgeslagen
$uploadDirectory = 'images'; // map waarin de afbeeldingen worden opgeslagen


//uploadFileAs is de naam zoals wij de afbeelding uiteindelijk gaan opslaan
$uploadFileAs = trim($uploadDirectory, '/') .
    '/' .
    hash('sha256', date('YmdHms') . random_int(1, 1000000)) //unieke hash
    . "." . strtolower(pathinfo($request->foto['name'], PATHINFO_EXTENSION)); //extensie van de afbeelding

//verplaatsen naar de juiste map
move_uploaded_file($request->foto["tmp_name"], $uploadFileAs);

//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=:profielfoto WHERE id=:id", [
    "profielfoto" => $uploadFileAs,
    "id" => user()->id,  // de op dat moment ingelogde gebruiker
]);

flash('success', 'Profielfoto succesvol geüpload!');


//PAS AAN: stuur de gebruiker naar de gewenste pagina
redirect('/');

 

Als je opnieuw inlogt als gebruiker zal nu in plaats van de naam de profielfoto worden getoond in het menu.

 

X Samenvatting

Waar komt wat

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

Request

  • Klikken op een link / button
  • Versturen van een formulier
  • Intypen van een url in de browser

Index.php

  • Inladen van bestanden die nodig zijn voor de applicatie
    • configuratie bestand
    • functies
    • Objecten

Router (router.php)

  • verzoek bv /home doorsturen naar juiste controller of view

Controller

  • Logica, bv interactie met de database (gegevens ophalen, invoeren, wijzigen, verwijderen)

View

  • Opbouwen van de html pagina
  • Content
  • Formulieren
  • Navigatie menu
  • etc

 

Uitwerkingen

Alle uitwerkingen van de samenvatting zijn hier te vinden

 

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
;

 

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-show/{id}', 'controllers/items-show.php');

De url http://localhost/item/4 zal dan voor id=4 invullen. Omdat de method 'get' wordt gebruikt is in de controller $request->id beschikbaar.

In controller items-show.php

  • gegevens ophalen uit de database
  • gegevens meegeven aan view
<?php
//validate id
$request->validate([
    'id' => 'required'
]);

//initialiseren van database class
$db = new Database();

//view met item teruggegeven
view('items-show', [
    'item' => $db->query("SELECT * FROM items WHERE id = :id", [
        "id" => $request->id
    ])->fetch()
]);

In de view items-show.view.php

<?php
view("parts/header", ['title' => 'item ' . $item['naam']]);
view("parts/navigatie-menu");
?>
    <h1 class="text-3xl my-4">Item <?= $item['naam'] ?></h1>

    <p class="my-4">Elke veld van 'item' kan hier nu worden gebruikt<br>
        id: <?= $item['id'] ?><br>
        naam: <?= $item['naam'] ?><br>
        beschrijving: <?= $item['beschrijving'] ?><br>
        prijs: <?= $item['prijs']; ?><br>
    </p>

<?php
view("parts/footer");

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

 

Tonen van meerdere items

Stappen plan

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

In de router.php

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

In de controller items.php

<?php
//initialiseren van database class
$db = new Database();

//view met item teruggegeven
view('items', [
    'items' => $db->query("SELECT * FROM items ORDER BY naam")->fetchAll()
]);

In de view items.view.php

<?php
view("parts/header", ['title' => 'items']);
view("parts/navigatie-menu");
?>
    <h1 class="text-3xl my-4">Items</h1>

    <p class="my-2">Hieronder een lijst met alle items</p><br>
    <ul class="ml-4">
        <!-- loop door alle items heen -->
        <?php foreach ($items as $item) : ?>
            <li><?= $item['id'] ?> -
                <?= $item['naam'] ?> -
                <?= $item['beschrijving'] ?> -
                <?= $item['prijs'] ?> -
                Link naar item:
                <a href="/items-show/<?= $item['id'] ?>" class="text-indigo-600">
                    <?= $item['naam'] ?>
                </a>
            </li>
        <?php endforeach; ?>
    </ul>

<?php
view("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-store', '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

<?php
view("parts/header", ['title' => 'item toevoegen']);
view("parts/navigatie-menu");
?>
    <h1 class="text-3xl my-4">Item toevoegen</h1>

    <form action="/items-store" 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">
    </form>
<?php
view("parts/footer");

Versie met validatie errors en opnieuw invullen van velden

<?php
view("parts/header", ['title' => 'item toevoegen']);
view("parts/navigatie-menu");
?>
    <h1 class="text-3xl my-4">Item toevoegen</h1>

    <form action="/items-store" method="post">
        <?= csrf(); ?>
        <label for="naam">Naam</label><br>
        <input type="text" name="naam" id="naam" placeholder="naam" value="<?= old('naam') ?>"><br>
        <?php if (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"><?= old('beschrijving') ?></textarea>
        <br>
        <?php if (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="<?= old('prijs') ?>">
        <br>
        <?php if (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">
    </form>
<?php
view("parts/footer");

In controller items-store.php

  • Validatie (voor regels zie cheatsheet)
  • Invoeren in database
  • Doorverwijzen naar andere pagina
<?php
//validatie van de gegevens
$request->validate([
    'naam' => 'required|length:0,50',
    'prijs' => 'required|numeric|between:0.01,1000000'
]);
//voor alle validatie regels zie cheat-sheet

//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' => $request->naam,
    'beschrijving' => $request->beschrijving,
    'prijs' => $request->prijs,
]); //id veld staat op auto increment dus hoeft niet meegegeven te worden

flash("Item " . $request->naam . " is toegevoegd");

//terugsturen naar de index pagina
redirect("/items");

// of terugsturen naar de detail pagina van het item
redirect("/items-show/".$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-edit/{id}', 'controllers/items-edit.php');
$route->post('items-update/{id}', 'controllers/items-update.php');

In controller items-edit.php

<?php
$request->validate([
    'id' => 'required'
]);

$db = new Database();

view('items-edit', [
    'item' => $db->query("SELECT * FROM items WHERE id=:id", [
        "id"=>$request->id
    ])->fetch()
]);

In view items-edit.view.php

Zonder validatie

<?php
view("parts/header", ['title' => 'item wijzigen']);
view("parts/navigatie-menu");
?>
    <h1 class="text-3xl my-4">Item <?= $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="<?= old('naam', $item['naam']) ?>"><br>


        <label for="beschrijving">Beschrijving</label><br>
        <textarea name="beschrijving" id="beschrijving" cols="30" rows="3" placeholder="beschrijving"><?= old('beschrijving', $item['beschrijving']) ?></textarea>
        <br>


        <label for="prijs">Prijs</label><br>
        <input type="number" step="0.01" name="prijs" id="prijs" placeholder="prijs" value="<?= old('prijs', $item['prijs']) ?>">
        <br>


        <input type="submit" value="Wijzigen" class="border b-gray-600 rounded py-1 px-2 hover:bg-gray-100 cursor-pointer">
    </form>
<?php
view("parts/footer");

Met validatie

<?php
view("parts/header", ['title' => 'item wijzigen']);
view("parts/navigatie-menu");
?>
    <h1 class="text-3xl my-4">Item <?= $item['naam'] ?> wijzigen</h1>

    <form action="/items-update/<?= $item['id'] ?>" method="post">
        <?= csrf(); ?>
        <label for="naam">Naam</label><br>
        <input type="text" name="naam" id="naam" placeholder="naam" value="<?= old('naam', $item['naam']) ?>">
        <?php if (errors('naam')): ?>
            <p class="text-red-500 text-sm my-2"><?= errors('naam') ?></p>
        <?php endif; ?>
        <br>

        <label for="beschrijving">Beschrijving</label><br>
        <textarea name="beschrijving" id="beschrijving" cols="30" rows="3" placeholder="beschrijving"><?= old('beschrijving', $item['beschrijving']) ?></textarea>
        <?php if (errors('beschrijving')): ?>
            <p class="text-red-500 text-sm my-2"><?= errors('beschrijving') ?></p>
        <?php endif; ?>
        <br>

        <label for="prijs">Prijs</label><br>
        <input type="number" step="0.01" name="prijs" id="prijs" placeholder="prijs" value="<?= old('prijs', $item['prijs']) ?>">
        <?php if (errors('prijs')): ?>
            <p class="text-red-500 text-sm my-2"><?= errors('prijs') ?></p>
        <?php endif; ?>
        <br>

        <input type="submit" value="Wijzigen" class="border b-gray-600 rounded py-1 px-2 hover:bg-gray-100 cursor-pointer">
    </form>
<?php
view("parts/footer");

In controller items-update.php

<?php
//validatie van de gegevens
$request->validate([
    'naam' => 'required|length:0,50',
    'prijs' => 'required|numeric|between:0.01,1000000'
]);

//wijzigen doorvoeren
$db = new Database();
$db->query("UPDATE items SET naam = :naam, beschrijving = :beschrijving, prijs = :prijs WHERE id = :id", [
    'naam' => $request->naam,
    'beschrijving' => $request->beschrijving,
    'prijs' => $request->prijs,
    'id' => $request->id,
]);

flash("Item " . $request->naam . " is gewijzigd");
//terugsturen naar de detail pagina van het item
redirect("/items-show/" . $request->id);

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

 

Verwijderen

Stappenplan

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

Route aanmaken

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

Controller items-destroy.php

<?php
$request->validate([
    'id' => 'required'
]);

$db = new Database();

$db->query("DELETE FROM items WHERE id = :id", [
    'id' => $_POST['id']
]);

flash("Item is verwijderd");

redirect("/items");

Verwijderen kan via een formulier

<form method="post" action="/items-destroy/<?= $item['id'] ?>">
    <?= csrf(); ?>
    <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.

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)

Uiteraard kan je dit bestand bewerken en opmaken met de style die jij zelf wil.

Code voor de verwijder button ergens te plaatsen

<?php
view('parts/delete-button', ['action' => "/items-destroy/".$item['id']]);
?>

helpers

auth()

user()

hasRole('role')

old('name',$item['name'])

errors('name')

config('app.name')

isUri(string $uri)

view(string $file, array $variabele, array $unescaped_variabele)

flash(string $msg, boolean $success, int $duration)

dd(mixed $variabele)

csrf()

redirect(string $url)

getNonce()

Z Proef toets

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 framework opnieuw van github. Sleep de hele directory framework naar je USBwebserver zodat deze is uitgepakt. Waarschijnlijk heb je al een map 'framework', geef deze dan een andere naam. Stel daarna bij USBwebserver settings het juiste path in zodat framework wordt geladen.

bv.

{path}/framework/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 (40p)

Bij deze opdracht moet een formulier gemaakt worden om afspraken in te kunnen voeren in de 'afspraken' tabel van de database 'agenda'. Hiervoor moeten onderstaande stappen worden genomen

  • Maak drie bestanden aan:
    • /app/controllers/afspraak-create.php (1p)
    • /app/controllers/afspraak-store.php (1p)
    • /app/views/afspraak-create.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 (klantnaam 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 d.m.v. 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)
  • Bij validatie fouten worden de velden heringevuld (6p)

 

Het invoer formulier zou er bijvoorbeeld zo uit kunnen zien

 

Opgave 3 (38)

Zoek pagina

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

  • Maak een controller /app/controllers/afspraken.php (1p)
  • Maak een controller /app/controllers/afspraak-destroy.php (1p)
  • Maak een view /app/views/afspraken.view.php (1p)
  • Maak een link in het navigatie 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 datum/tijd 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)

 

Voorbeeld

Opgave 4 (50p)

Klant beheer

Deze opdracht is minder voorgekauwd. Zorg ervoor dat alle functionaliteiten werken en veilig zijn.

Maak iets om een klant te kunnen selecteren. Als de klant geselecteerd is moet je de klantgegevens kunnen aanpassen. (alle velden behalve het id van de klant) Uiteraard is er de juiste validatie en worden fouten getoond. Bij geen fouten worden de klantgegevens 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

 

Voorbeelden

Lijst met klanten

Selectbox om een klant te selecteren

Wijzig formulier

Opgave 5 (24p)

Cijfer

Het cijfer kan berekend worden met

Cijfer = punten*9/150 + 1

 

Uitwerkingen Proef Toets

Het gehele uitgewerkte toetsproject is hier te vinden.

 

 

Opgave 1

/app/config.php

<?php
return [
    'app' => [
        'env' => 'development',
        'name' => 'Agenda',                 //aangepast
        'email' => 'info@magenda-mail.nl',  //aangepast
    ],
    'database' => [
        'user' => 'root',
        'password' => 'usbw',               //aangepast
        'port' => 3306,
        'host' => 'localhost',
        'dbname' => 'agenda',               //aangepast
        'charset' => 'utf8mb4',
    ],
];

Opgave 2

Bestanden aanmaken

Video 01

We maken de benodigde bestanden aan

Menu item aanmaken

Video 02

Toevoegen link aan navigatie-menu.view.php

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

Routes aanmaken

Video 03

Maak in router.php de volgende routes

$route->get('afspraak-create', "controllers/afspraak-create.php"); //(1p)
$route->post('afspraak-store', "controllers/afspraak-store.php");  //(1p)

Invoer formulier maken

Video 04

Maak in afspraak-create-view.php het formulier met de benodigde velden

<form action="/afspraak-store" method="post">
    <?= csrf() ?>

    <select name="klant_id" class="border">
        <?php foreach ($klanten as $klant): ?>
            <option value="<?= $klant['id'] ?>">
                <?= $klant['voornaam'] ?>     <?= $klant['tussenvoegsel'] ?>
                <?= $klant['achternaam'] ?>
            </option>
        <?php endforeach; ?>
    </select><br>

    Datum
    <input type="date" class="border" name="datum" placeholder="Datum" value="<?= old('datum') ?>"><br><!-- 1p -->
    Van tijd<input type="time" class="border" name="van" placeholder="van" value="<?= old('van') ?>"><br><!-- 1p -->
    Tot tijd<input type="time" class="border" name="tot" placeholder="Tot" value="<?= old('tot') ?>"><br><!-- 1p -->
    <textarea name="opmerking" class="border" placeholder="Opmerkingen"><?=old('opmerking')?></textarea><br><!-- 2p -->
    <input type="submit" class="border" value="Opslaan"><!-- 1p -->
</form>

Afspraak opslaan in database

Video 05

Vul de controller afspraak-store.php zodat een afspraak opgeslagen wordt in de database

<?php

$request->validate([
    'datum' => 'required', //1p
    'van' => 'required',   //1p
    'tot' => 'required',   //1p
    'klant_id' => 'required', //1p
]);

$db = new Database();

$db->query("INSERT INTO afspraken (van, tot, klant_id, opmerking) VALUES (?, ?, ?, ?)", [
    $request->datum . " " . $request->van,
    $request->datum . " " . $request->tot,
    $request->klant_id,
    $request->opmerking,
]);

flash("Afspraak is gemaakt", true, 3000);

redirect("/afspraak-create");

Validatie in view toevoegen

Video 06

Voeg de validatie meldingen toe aan afspraak-create.view.php

<form action="/afspraak-store" method="post">
    <?= csrf() ?>

    <select name="klant_id" class="border">
        <?php foreach ($klanten as $klant): ?>
            <option value="<?= $klant['id'] ?>">
                <?= $klant['voornaam'] ?>     <?= $klant['tussenvoegsel'] ?>
                <?= $klant['achternaam'] ?>
            </option>
        <?php endforeach; ?>
    </select><br>

    Datum
    <input type="date" class="border" name="datum" placeholder="Datum" value="<?= old('datum') ?>"><br><!-- 1p -->
      <?php if (errors('datum')): ?>
          <p class="text-red-500 text-sm my-2"><?= errors('datum') ?></p>
      <?php endif; ?>
    Van tijd<input type="time" class="border" name="van" placeholder="van" value="<?= old('van') ?>"><br><!-- 1p -->
      <?php if (errors('van')): ?>
          <p class="text-red-500 text-sm my-2"><?= errors('van') ?></p>
      <?php endif; ?>
    Tot tijd<input type="time" class="border" name="tot" placeholder="Tot" value="<?= old('tot') ?>"><br><!-- 1p -->
      <?php if (errors('tot')): ?>
          <p class="text-red-500 text-sm my-2"><?= errors('tot') ?></p>
      <?php endif; ?>
    <textarea name="opmerking" class="border" placeholder="Opmerkingen"><?=old('opmerking')?></textarea><br><!-- 2p -->
    <input type="submit" class="border" value="Opslaan"><!-- 1p -->
</form>

Voor selecteren selectbox

Video 07

Een selectbox option wordt standaard geselecteerd met de tekst 'selected' als attribute

<option value="1" selected>Piet</option>

Dus wij willen de tekst 'selected' toevoegen als de klant uit de loop overeen komt met de klant die was geselecteerd.

<?= ($klant['id'] == old('klant_id') ? 'selected' : '') ?>

De volledige code wordt dan

<select name="klant_id" class="border">
    <?php foreach ($klanten as $klant): ?>
        <option <?= ($klant['id'] == old('klant_id') ? 'selected' : '') ?> value="<?= $klant['id'] ?>">
            <?= $klant['voornaam'] ?>     <?= $klant['tussenvoegsel'] ?>
            <?= $klant['achternaam'] ?>
        </option>
    <?php endforeach; ?>
</select><br>

 

Opgave 3

Navigatie links en routes aanmaken

Video 01

Maak de gevraagde controllers en views

Voeg aan router.php de routes toe

$route->get('afspraken', "controllers/afspraken.php"); //(1p)
$route->post('afspraak-destroy/{id}', "controllers/afspraak-destroy.php");  //(1p)

Maak een zoekveld

Video 02

Maak in afspraken.view.php een formulier met een datum veld en zoek button

<form action="/afspraken" method="get">
    <input type="date" name="datum" required class="border">
    <input type="submit" value="Zoek" class="border">
</form>

Resultaten tonen

Video 03

Vul de controller afspraken.php met de logica om alle afspraken met de betreffende datum op te halen.

<?php
//database object initialiseren
$db = new Database();

//query uitvoeren (met meerdere resultaten) en opslaan in een variabele $result (array)
$result = $db->query("SELECT afspraken.id, van, tot, voornaam, tussenvoegsel, achternaam 
                        FROM afspraken, klanten 
                        WHERE afspraken.klant_id=klanten.id AND van LIKE ? 
                        ORDER BY van", [
    $request->datum ?? '' . "%"
])->fetchAll();

view('afspraken', [
    'afspraken' => $result
]);

Gebruik in je query een koppeling tussen de afspraken en klanten tabel, want uit beide tabellen wil je gegevens hebben. En maak gebruik van een LIKE om op de betreffende datum te zoeken

Maak in afspraken een loop die door alle afspraken heen gaat en toont

<ul>
    <?php foreach ($afspraken as $afspraak): ?>
        <li>
            <?= $afspraak['voornaam'] . " " . $afspraak['tussenvoegsel'] . " " . $afspraak['achternaam'] . " " . $afspraak["van"] . " " . $afspraak['tot'] ?>
        </li>
    <?php endforeach; ?>
</ul>

Verwijderen van een afspraak

Video 04 met gewone button

Video 05 met bevestigingsbutton

Voeg een verwijder button toe. Het meest eenvoudige is om hier voor de view /parts/delete-button te gebruiken.

<ul>
    <?php foreach ($afspraken as $afspraak): ?>
        <li class="flex gap-4">
            <?= $afspraak['voornaam'] . " " . $afspraak['tussenvoegsel'] . " " . $afspraak['achternaam'] . " " . $afspraak["van"] . " " . $afspraak['tot'] ?>

            <?php
            view('parts/delete-button', [
                'action' => "/afspraak-destroy/" . $afspraak['id']
            ]);
            ?>

        </li>
    <?php endforeach; ?>
</ul>

In de controller afspraak-destroy.php moet het doorvoeren op de database worden toegevoegd.

<?php
$request->validate([
    'id' => 'required'
]);

//database object initialiseren
$db = new Database();

$db->query("DELETE FROM afspraken WHERE id = ?", [
    $request->id
]);

flash('Afspraak verwijderd');

redirect('/afspraken');

 

Opgave 4

Menu en bestanden aanmaken

Video 01

Voeg toe in /views/parts/navigatie-menu.view.php

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

Maak de bestanden

  • /controllers/klanten.php
  • /views/klanten.view.php

In onze router.php voegen we een route toe naar klanten.php

$route->get('klanten', "controllers/klanten.php");

Lijst met klanten maken

Video 02

Haal in de controller klanten.php alle klanten op uit de database en geef mee met de view

<?php
$db = new Database();

$result = $db->query("SELECT * FROM klanten ORDER BY achternaam")->fetchAll();

view('klanten', [
    'klanten' => $result
]);

In klanten.view.php maak je een loop door alle klanten heen en schrijft ze op het scherm

<ul>
    <?php foreach ($klanten as $klant): ?>
        <li>
           <?= $klant["voornaam"] . " " . $klant['tussenvoegsel'] . " " . $klant['achternaam'] ?>
        </li>
    <?php endforeach; ?>
</ul>

Lijst met klanten klikbaar maken

Video 04

Voeg een <a href toe zodat een klant klikbaar wordt. Of voeg een button toe achter/onder de klant.
Zorg ervoor dat er wordt verwezen naar /klanten-edit/<klant_id>

<ul>
    <?php foreach ($klanten as $klant): ?>
        <li>
            <a href="/klanten-edit/<?= $klant['id'] ?>" class="text-indigo-600">
                <?= $klant["voornaam"] . " " . $klant['tussenvoegsel'] . " " . $klant['achternaam'] ?>
            </a>
        </li>
    <?php endforeach; ?>
</ul>

Route naar edit pagina en controller toevoegen

Video 05

Route toevoegen aan router.php

$route->get('klanten-edit/{id}', "controllers/klanten-edit.php"); 

In de controller klanten-edit.php de klant gegevens ophalen

<?php
$request->validate([
    'id' => 'required'
]);

$db = new Database();

$result = $db->query("SELECT * FROM klanten WHERE id=?", [
    $request->id
])->fetch();

view('klanten-edit', [
    'klant' => $result
]);

Wijzigingsformulier maken

Video 06

In klanten-edit.view.php maken we het wijzigingsformulier

<form action="/klanten-update/<?= $klant['id'] ?>" method="post">
    <?= csrf() ?>
    <input type="text" name="voornaam" placeholder="Voornaam" value="<?= old('voornaam', $klant['voornaam']) ?>"
        class="border"><br>

    <input type="text" name="tussenvoegsel" placeholder="Tussenvoegsel"
        value="<?= old('tussenvoegsel', $klant['tussenvoegsel']) ?>" class="border"><br>

    <input type="text" name="achternaam" placeholder="Achternaam"
        value="<?= old('achternaam', $klant['achternaam']) ?>" class="border"><br>

    <input type="text" name="telefoonnr" placeholder="Telefoonnr"
        value="<?= old('telefoonnr', $klant['telefoonnr']) ?>" class="border"><br>

    <input type="submit" value="Opslaan" class="border">
</form>

Het formulier heeft vier tekst velden (voornaam, tussenvoegsel, achternaam, telefoonnr)
Als value gebruiken we old(... verstuurde waarde, ...waarde uit database)

Wijziging doorvoeren

Video 07

In controller klanten-update.php gaan we validatie doen en de wijziging in de database doorvoeren.

<?php
$request->validate([
    'id' => 'required',
    'voornaam' => 'required|min:2|max:50',
    'achternaam' => 'required|max:50',
    'telefoonnr' => 'max:15',
]);
//database object initialiseren
$db = new Database();

$db->query("UPDATE klanten 
SET voornaam =:voornaam, tussenvoegsel=:tussenvoegsel,achternaam=:achternaam,telefoonnr=:telefoonnr 
WHERE id = :id", [
    'voornaam' => $request->voornaam,
    'achternaam' => $request->achternaam,
    'tussenvoegsel' => $request->tussenvoegsel,
    'telefoonnr' => $request->telefoonnr,
    'id' => $request->id
]);

flash('Klant gegevens gewijzigd');

redirect('/klanten');

Validatie aan formulier toevoegen

Video 08

In het formulier klanten-edit.view.php voegen we validatie meldingen toe. Dit doen we in elk geval bij voornaam en achternaam, maar ook zeker bij tussenvoegsel en telefoonnr.

<form action="/klanten-update/<?= $klant['id'] ?>" method="post">
    <?= csrf() ?>
    <input type="text" name="voornaam" placeholder="Voornaam" value="<?= old('voornaam', $klant['voornaam']) ?>"
        class="border"><br>
    <?php if (errors('voornaam')): ?>
        <p class="text-red-500 text-sm my-2"><?= errors('voornaam') ?></p>
    <?php endif; ?>

    <input type="text" name="tussenvoegsel" placeholder="Tussenvoegsel"
        value="<?= old('tussenvoegsel', $klant['tussenvoegsel']) ?>" class="border"><br>

    <input type="text" name="achternaam" placeholder="Achternaam"
        value="<?= old('achternaam', $klant['achternaam']) ?>" class="border"><br>
    <?php if (errors('achternaam')): ?>
        <p class="text-red-500 text-sm my-2"><?= errors('achternaam') ?></p>
    <?php endif; ?>

    <input type="text" name="telefoonnr" placeholder="Telefoonnr"
        value="<?= old('telefoonnr', $klant['telefoonnr']) ?>" class="border"><br>

    <input type="submit" value="Opslaan" class="border">
</form>

Opgave 5

Zie video voor uitwerking

  • Het arrangement V6 p1-2 PHP-MySQL webapplicatie vanaf null v2 is gemaakt met Wikiwijs van Kennisnet. Wikiwijs is hét onderwijsplatform waar je leermiddelen zoekt, maakt en deelt.

    Laatst gewijzigd
    2025-10-07 14:52:10
    Licentie

    Dit lesmateriaal is gepubliceerd onder de Creative Commons Naamsvermelding 4.0 Internationale licentie. Dit houdt in dat je onder de voorwaarde van naamsvermelding vrij bent om:

    • het werk te delen - te kopiëren, te verspreiden en door te geven via elk medium of bestandsformaat
    • het werk te bewerken - te remixen, te veranderen en afgeleide werken te maken
    • voor alle doeleinden, inclusief commerciële doeleinden.

    Meer informatie over de CC Naamsvermelding 4.0 Internationale licentie.

    Aanvullende informatie over dit lesmateriaal

    Van dit lesmateriaal is de volgende aanvullende informatie beschikbaar:

    Toelichting
    In deze cursus leer je stap voor stap een PHP-MySQL webapplicatie te bouwen. Daarnaast zal er aandacht zijn voor diverse onderwerpen die bij web-development van belang zijn.
    Eindgebruiker
    leerling/student
    Moeilijkheidsgraad
    gemiddeld
    Trefwoorden
    crud, mvc, mysql, php, usbwebserver

    Gebruikte Wikiwijs Arrangementen

    JEL informatica. (2023).

    V6 p1-2 PHP-MySQL webapplicatie vanaf null

    https://maken.wikiwijs.nl/192641/V6_p1_2_PHP_MySQL_webapplicatie_vanaf_null

  • Downloaden

    Het volledige arrangement is in de onderstaande formaten te downloaden.

    Metadata

    LTI

    Leeromgevingen die gebruik maken van LTI kunnen Wikiwijs arrangementen en toetsen afspelen en resultaten terugkoppelen. Hiervoor moet de leeromgeving wel bij Wikiwijs aangemeld zijn. Wil je gebruik maken van de LTI koppeling? Meld je aan via info@wikiwijs.nl met het verzoek om een LTI koppeling aan te gaan.

    Maak je al gebruik van LTI? Gebruik dan de onderstaande Launch URL’s.

    Arrangement

    IMSCC package

    Wil je de Launch URL’s niet los kopiëren, maar in één keer downloaden? Download dan de IMSCC package.

    Voor developers

    Wikiwijs lesmateriaal kan worden gebruikt in een externe leeromgeving. Er kunnen koppelingen worden gemaakt en het lesmateriaal kan op verschillende manieren worden geëxporteerd. Meer informatie hierover kun je vinden op onze Developers Wiki.