WT: Implementation & Security | Studiehandleiding

Inmiddels weet je hoe je een relatief simpel programma kan schrijven met Processing/Java (SPB). Je kan ook requirements lezen en definiëren voor software (FAT). In deze course gaan je die skills inzetten om een website te (leren) maken. Maar het web werkt nogal anders dan Processing.

In Web Technology: Implementation & Security (WTIS) leer je een pagina (zoals je geleerd hebt in WTUX) te serveren op het web in een zogenoemde multi-tier-architectuur. Je leert daarbij nadenken over veiligheidsrisico’s die dit met zich meebrengt. Je leert ook hoe überhaupt het internet werkt waarop deze websites allemaal draaien. Om een website echt nog krachtig te maken wil je deze ook vullen met data die dynamisch is. In andere woorden data die voor elke gebruiker specifiek is en niet alleen statische teksten. Dat gaan we doen door data uit een Microsoft SQL server te halen.

Schema

Studiepunten en studiebelasting

De course WTIS heeft een omvang van 5 studiepunten, dit zijn 140 studiebelastingsuren.

Samenhang

Deze course hangt nauw samen met de course WTUX. Daarin wordt ‘de andere helft’ van websites behandeld. Niet de serverkant, maar de client side. In WTUX wordt aandacht besteed aan HTML, CSS en wireframes. De course SPB leert je programmeerbeginselen zoals: condities, lussen, variabelen en functies. Daarmee ga je ook aan de slag in WTIS alleen dan in PHP in plaats van in Processing/Java.

Praktische Informatie

WTIS is 5 weken lang. In de eerste week maken we de eerste start met onze ontwikkelomgeving in PHP en leren we die gebruiken. In week 2 gaan we verder met PHP en leren we over functies, arrays en condities en lussen. We maken ook kennis met de protocollen die het internet mogelijk maken. In week 3 gaan we verder met functies en koppelen we deze kennis met het gebruik van HTML-formulieren en sessies. In week 4 gaan we bezig met het koppelen van de website met de database en hoe we dit kunnen beveiligen. In de laatste week is er ruimte om het beroepsproduct verder af te maken.

Net zoals in de WTUX-course wordt er gaandeweg steeds verder uitgebreid op het beroepsproduct.

Erg belangrijk is dat deze course voor elke les voorbereiding heeft. Dat betekent dat je ongeveer 1,5 uur bezig bent met het voorbereiden van de les. Dit gaat dan om dingen zoals:

  • Software downloaden en klaar zetten.
  • Voorbereidende video’s kijken.
  • Documentatie lezen van PHP.
  • Kleine oefeningen doen.

De lessen bestaan altijd uit de volgende onderdelen:

  • terugkoppeling op vragen uit de voorbereiding;
  • een stuk uitleg;
  • korte demonstraties;
  • kleine oefeningen; en
  • huiswerk.

Als het goed is ben je na vier weken klaargestoomd voor het grote werk. Het beroepsproduct, waarover hieronder meer.

Beroepsproduct: Veilige Webapplicatie

Het beroepsproduct is dezelfde (dynamische) website uit WTUX maar nu gekoppeld met PHP en een database en voorzien van juiste veiligheidsmaatregelen. Alle teksten die hierin staan, zijn dus niet meer hetzelfde, maar maken gebruik van de database.

De hele opdracht staat hier en is voor iedereen hetzelfde. Echter zal iedereen een andere uitwerking hebben met een andere invulling van kleuren, plaatjes en ook broncode.

Het beroepsproduct levert een individueel cijfer en wordt dus ook individueel uitgevoerd.

Toetsing

De course WTIS kent 2 toetsen: het beroepsproduct (50 %), het schriftelijke tentamen (kennistoets) (50 %). Deze onderdelen moeten met een 5,5 of hoger beoordeeld worden en tellen gezamenlijk voor 100 % mee als eindcijfer.

Welke competenties je nodig hebt voor elke toets en de specifieke beoordelingscriteria vind je terug op iSAS.

Inlevering en archivering

Het beroepsproduct dient volgens het toetsschema te worden ingeleverd. Het inleveren gebeurt digitaal via iSAS. Raadpleeg Intro-ICT of een docent op tijd als je hier niet uit dreigt te komen.

Tijdens het vak maak je gebruik Git via GitHub for Desktop.

P.S.: Een kapotte laptop of verloren bestanden zijn geen excuus voor uitstel. Maak regelmatig backups van je data en bewaar ook eerdere versies. Alle kleine oefeningen die je maakt kun je misschien later gebruiken.

Materialen

Beoordelingsmodel

Het beoordelingsmodel vind je op hier op iSAS

Web Technology: Implementation & Security - beroepsproduct

Inleiding

Het beroepsproduct voor WTIS is hetzelfde als WTUX maar nu als webapplicatie (webapp). Deze keer moet de website voor echt in bedrijf kunnen worden genomen. Het belangrijkste dat je moet toevoegen is het verwerken van invoer van personen die de applicatie gaan gebruiken.

Naast HTML en CSS moet je daar nu PHP en een SQL Server RDBMS voor gaan gebruiken. Nu je meer technologieën zal gebruiken, is het goed toepassen van techniek (implementeren) belangrijker geworden. Vooral de prestatie-efficiëntie en beveiligbaarheid van de applicatie worden nu je uitdaging.

Je beroepsproduct start je o.b.v. een github Assignment. Zie de handleiding bij het template voor uitleg over hoe je dit opstart. De data wordt automatisch ingeladen. De webpagina’s baseer je op je uitwerking van het vorige beroepsproduct.

Casus

Pizzeria Sole Machina 🍕

De Pizzeria Sole Machina heeft een applicatie nodig waar klanten bestellingen kunnen doen en waarin de keuken een overzicht krijgt van de bestellingen. In Figuur 2 kun je het data model vinden van Sole Machina.

Pizzeria Ellen He

Figuur 1: Pizzeria door Ellen He

Een klant doet een bestelling waarin meerdere items te plaatsen zijn zoals: Cola, Pizza, Focaccia. Elke bestelling betaat dus uit Order_Items. Items hebben een type (drinken, eten, etc.) en kunnen ingrediënten hebben. Zoals een pizza Caprese de ingrediënten basilicum, tomaat en mozzarela heeft.

Een bestelling wordt altijd gekoppeld aan een klantgebruiker en aan een personeelsgebruiker zodat duidelijk is voor wie de bestelling is en wie verantwoordelijk is om deze bestelling voldaan te krijgen. Een gebruiker heeft geen adres, maar een bestelling wel omdat een gebruiker niet altijd op hetzelfde adres een bestelling hoeft te doen.

Een gebruiker heeft een rol om te bepalen of dit een personeelslid of een klant is.

Data model pizzeria Figuur 2: Data model Sole Machina

Voor klanten:

  • KL-01: Als klant wil ik het menu kunnen bekijken en items aan mijn bestelling kunnen toevoegen zodat ik mijn maaltijd kan aanpassen.
  • KL-02: Als klant wil ik een bestelling kunnen plaatsen met meerdere items en de hoeveelheid van elk item kunnen specificeren zodat ik voor mezelf en anderen kan bestellen.
  • KL-03: Als klant wil ik bij het plaatsen van een bestelling een afleveradres kunnen opgeven zodat mijn bestelling op de juiste locatie kan worden afgeleverd.
  • KL-04: Als klant wil ik kunnen registreren, zodat ik de volgende keer geen afleveradres hoef op te geven en sneller kan bestellen.
  • KL-05: Als klant wil ik mijn bestellingen kunnen inzien, zodat ik kan zien wat de status ervan is (bijvoorbeeld: onderweg, in de oven, etc.).

Voor personeelsleden:

  • PE-01: Als personeelslid wil ik kunnen inloggen op mijn account zodat ik bestellingen geplaatst door klanten kan beheren.
  • PE-02: Als personeelslid wil ik alle actieve bestellingen kunnen bekijken zodat ik ze efficiënt kan voorbereiden en vervullen.
  • PE-03: Als personeelslid wil ik de bestellingen kunnen wijzigen, zodat gebruiker en mederwerker de status van de bestelling kunnen bijhouden.

Pagina’s

Om een beetje houvast te geven kun je de indeling maken op basis van de onderstaande pagina indeling. Let wel dat dit puur fictioneel is. Je kunt zelfs misschien 2 pagina's hebben voor 1 user story als dat beter past in jou applicatie.

PaginaBeschrijvingUser Stories
MenuEen pagina waar alle bestelbare items weergegeven wordenKL-01
WinkelmandjeEen pagina waar je alles wat er in je bestelling zit kunt bevestigenKL-02, KL-03
ProfielOp deze pagina kun je alle bestellingen die je hebt gemaakt inzienKL-04
Registratie/LoginHier kun je een account maken en/of inloggenKL-05, PE-01
Bestellingoverzicht PersoneelOverzicht van alle bestelligen en mogelijkheid om status aan te passen (alleen voor personeel)PE-02, PE-03
Detailoverzicht BestellingDetails van een bestelling inzien voor bijvoorbeeld een bezorgerPE-02
PrivacyverklaringEen officiële verklaring van hoe de gegevens gebruikt moet worden overeenkomstig GDPR / AVG.Juridisch verplicht

Documentatie

In de les over webappbeveiliging ben je bezig geweest met het in kaart brengen en analyseren van beveiligingsrisico's. Nu doe je dat voor het beroepsproduct.

Kies uit de top 10 van OWASP 5 risico's en werk voor elk van de risico's de volgende elementen uit:

  • Risicotabel zoals uitgelegd in Hoofdstuk 7
  • Een korte uitleg over de gevolgen van een doorbraak bij dit risico
  • Een korte samenvatting hoe je de applicatie hiervoor beveiligd hebt, met behulp van screenshots en code uit je eigen applicatie

In te leveren

Ingepakt als ZIP-bestand, via iSAS:

  1. Alle documentatie, broncode en overige bestanden van de applicatie die je hebt gemaakt (netjes en schoon).
  2. Een zelf ingevuld beoordelingsmodel (zelfbeoordeling), waarin je eerlijk inschat hoe je hebt gepresteerd op ieder criterium.

Beoordeling

  1. De beoordeling volgt het beoordelingsmodel (iSAS). Bij aspect WT-IS-3, criterium 2, over de vereisten gaat de beoordeling over implementatie van de user stories die tabel 1 opsomt.
  2. Bij de beoordeling gebruikt de beoordelaar uitsluitend de inhoud van je ZIP, zonder nog bijzondere handelingen te doen als bestanden downloaden of scripts draaien. Deze ‘testdata’ moet beschikbaar zijn zodra je beoordelaar zelfstandig de applicatie draait, via een gebruikersvriendelijke herstelfunctie. Deze herstelfunctie moet werken volgens de instructies voor het herstellen van een databasebackup die al in het template voor het beroepsproduct staan.
  3. Als iets naar jouw oordeel nog onvoldoende af is, beoordeelt je beoordelaar je op dat moment niet op dat criterium. Op de overige criteria wel. Je beoordelaar stemt zijn/haar evt. feedback af op je zelfbeoordeling.

Herkansing

Mocht je WTUX in het voorgaande jaar gehaald hebben en WTIS niet dan kan het zijn dat je een frontend hebt gebouwd voor een andere casus.

In dit geval maak je de opdracht precies zoals hierboven in de user stories omschreven staat. Maar doe je dat met kale HTML en PHP om deze HTML te genereren.

Je mag hier uiteraard nog CSS voor schrijven, maar dat beïnvloedt het cijfer niet.

In github classroom kun je de opdracht accepteren: https://classroom.github.com/a/i2FS8hI7

Ontwikkelomgeving installeren

Om PHP en alles wat erbij hoort volledig te laten werken, hebben we een ontwikkelomgeving nodig. Bij deze course gaan we uit van

  • Visual Studio Code voor het schrijven van broncode.
  • Docker als platform voor de web- en databaseserver.

Installeren en gebruiken van ontwikkelomgeving

Om te kunnen gaan programmeren in PHP op je machine moet je de volgende dingen installeren:

  • Docker Desktop (voor Windows, voor macOS).
    • De meestvoorkomende problemen zijn omtrent de virtualisatie-instellingen. De oplossingen vind je bij Virtualization.
  • Download of clone de Git-repository die je van je docent hebt gekregen.

❗️ Mocht je ergens in vastlopen, schroom niet om contact op te nemen met de studentassistenten. Die zijn elke werkdag tussen 19:00 en 20:00 beschikbaar op Teams.

Je doet dit thuis en als je tegen problemen aanloopt neem je even contact op met je docent of je collega-studenten.

❗️ Waarom Docker vraag je je misschien af? Docker maakt het mogelijk om makkelijk software te draaien in een afgeschermde omgeving (een zogenaamde 'container'). Je kunt dit vergelijken met een virtual machine. Op jouw computer draait dus een virtuele machine die toegang geeft tot een webserver en een databaseserver (containers draaien alleen iets performanter dan echte virtuele machines).

Hieronder een schematische tekening van hoe de browser dan bij het goede adres komt. Container architectuur

Hoe ontwikkel je in de ontwikkelomgeving?

De README beschrijft hoe je aan het werk gaat met ontwikkelen in die omgeving.

Inleiding

Als het goed is, weet je hoe HTML en CSS werken en kan je een website bouwen. Dan ben je nu toe aan een volgende stap. Een website met puur HTML en CSS is als een flyer bij de VVV. Iedereen krijgt dezelfde uitgereikt en een deel van de informatie is misschien verouderd.

In plaats van een statische website, met telkens dezelfde pagina’s maken we nu iets dynamischs, iets waar een bezoeker kan inloggen, zoeken, bestellingen doen en berichtjes achterlaten. Daarvoor hebben we nieuwe technieken en kennis over de principes van het internet nodig.

Deze reader gaat over PHP, een programmeertaal die op een webserver wordt uitgevoerd. Daar waar HTML en CSS in een browser naar mooie pagina’s vertaald worden, zorgt PHP op de server ergens in de wereld ervoor dat de bezoeker van een website optimaal bediend wordt. Er zijn ook andere talen, maar PHP wordt mede door het succes van WordPress en andere content management systemen (CMS) het meest gebruikt. Let’s start!

Vooraf

Bij deze course gaan we ervan uit dat je een werkende ontwikkelomgeving hebt om de website in te bouwen.

PHP/HTML en strings

PHP en HTML-broncode

PHP maakt HTML niet overbodig, het werkt er nauw mee samen. De webserver moet wel weten dat iets PHP is. Daarvoor krijgt een bestand .php als extensie, zoiets als index.php of bestellen.php. Binnen de HTML-broncode geven we met

<?php
  // Hier schrijf je broncode.
?>

aan dat iets geen HTML, maar PHP is. De PHP engine interpreteert (vertaalt) de broncode en voert de erin beschreven opdrachten (statements) uit. Met de opdracht echo wordt de tekst die erna komt op de webpagina geplaatst.

De broncode ziet er dan bijvoorbeeld zo uit:

<!DOCTYPE html>
<html lang="nl">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>PHP-voorbeeld</title>
</head>
<body>
  <h1>Dit is een gewone pagina</h1>
  <?php
      echo "Of toch niet?";
  ?>
</body>
</html>

Variabelen

Je hebt al programmeerervaring. Je weet dus wat een variabele is of een string. Er zijn wel een paar dingen bijzonder in PHP:

Variabelen beginnen altijd met een dollarteken, zoals: $naam, $beroep, $gewicht. In het volgende voorbeeld zie je hoe je variabelen gebruikt:

// Variabelen aanmaken (variabelen beginnen in PHP met '$')
// string
$naam = "Jan Janssen";
// integer
$leeftijd = 27;
// float
$spaarsaldo = 1050.68;
// boolean
$getrouwd = false;

/*
Bij SPB/SPAD hebben jullie geprogrammeerd in Processing met een statisch getypeerde
programmeertaal.
Dat merk je bijvoorbeeld op het moment dat je in Processing een variabele declareert: je geeft (naast de naam en eventueel een waarde) het type van de variabele op: */
int aantal = 50;
/*
Types van variabelen zijn dus vóór programmauitvoer (tijdens het compileren) bekend, en kunnen niet meer wijzigen.
Een eerste verificatie van het programma (of er bijvoorbeeld nergens strings bij integers worden opgeteld) kan op dat moment al plaatsvinden, waardoor het programma snel is en minder fouten bevat.

PHP daarentegen is een dynamisch getypeerde taal: types hoeven niet worden aangegeven bij het declareren van een variabele.
Het type zal tijdens het uitvoeren van het programma worden afgeleid op basis van de waarde die er in wordt gestopt (type inference).
Dit leidt ertoe dat programma's sneller geschreven kunnen worden, maar mogelijk manifesteren zich fouten tijdens uitvoer.

In bovenstaand voorbeeld is $leeftijd van het type integer omdat 27, een geheel getal, toegekend is.
Tijdens de uitvoering van het programma kun je aan een variabele andere waarden toekennen waardoor ook het datatype verandert.
*/

// Variabelen aanpassen
$leeftijd = 27.33;          // integer -> float
$leeftijd = "27 jaar";      // float -> string
$leeftijd = false;          // string -> boolean
/*
Deze wijzigingen van datatype zijn toegestaan bij dynamisch getypeerde talen (PHP in dit geval).
Bij statisch getypeerde talen (bijvoorbeeld Processing) is dat niet toegestaan.
*/

// Werken met constanten
define("PI", "3.15");
define('PI2', '3.1415');

Ze zijn ook superflexibel. Je kan er tekst (strings), getallen (hele en decimale, ofwel integers en floats), true/false (boolean) en andere soorten gegevens in opslaan, zonder dat PHP dat erg vindt. PHP zoekt zelf uit wat voor een type een waarde heeft. (Overigens, vanaf PHP 7 kan je types wel specificeren.)

Handige functies voor tijdens het ontwikkelen (debuggen) zijn:

  • gettype($var) om het type van een variabele te achterhalen
  • var_dump($var) om het type en de waarde van een variabele te beschrijven als string met een HTML-fragment.

⚠️ Bovenstaande functies horen niet thuis in de broncode van een softwaresysteem dat in bedrijf is, oftewel buiten de ontwikkeling! var_dump genereert bijvoorbeeld ongeldige/verouderde HTML-broncode. Ook kan de aanroep van dit soort functies aanvallers helpen door onnodig interne informatie prijs te geven.

Handige functies om datatypes om te zetten zijn:

  • is_numeric() om te controleren of een waarde een getal is.
  • round() om een getal met decimalen af te ronden.
  • floor() om een getal met decimalen altijd naar het laagste dichtstbijzijnde gehele getal af te ronden.
  • ceil() om een getal met decimalen altijd naar het hoogste dichtstbijzijnde gehele getal af te ronden.
  • number_format() om een getal om te zetten naar een optimaal leesbare string.
  • intval() om een string naar een int om te zetten. 💣 Tricky!
  • floatval() om een string naar float om te zetten. 💣 Tricky!

Strings

Alles wat op een HTML-pagina moet komen te staan wordt vanuit PHP als tekst = string opgeleverd. In HTML-broncode staat dus alleen maar tekst.

Om het werken met strings een beetje makkelijker te maken zijn er handige opties.

Aanhalingstekens

Gebruik enkelvoudige aanhalingstekens, tenzij je wilt dat variabelen worden vervangen binnen de string.

$naam = 'Marie';
$bericht = "Dag $naam!";

Laat omsluitende aanhalingstekens afwijken van interne technische aanhalingstekens zoals " en ' binnen een string, omdat anders de interne op iedere positie waar ze voorkomen speciaal behandeld (geëscapet) moeten worden. Dus schrijf bijvoorbeeld wel:

$bedrijf = "In PHP is dit een string: 'string'.";
$html = '<img src="foto.jpg" alt="">';

Maar gebruik je aanhalingstekens voor leestekst, gebruik dan geen technische maar typografische aanhalingstekens:

$artikels = 'video’s';
$verhaal = "De parkeerwachter zei: “Gaat u, a.u.b …”.";
$pad = "Pietje ging dat wel even ‘regelen’ …";

Je kan in de resterende gevallen desnoods een escape-teken '\' gebruiken om ervoor te zorgen dat PHP het teken daarachter letterlijk overneemt, en niet als eindemarkering van de string ziet.

$pad = "C:\\Users\\Jan\\Documenten";

Variabelen tussenvoegen

Teksten en variabelen kunnen met een punt aan elkaar worden gekoppeld of bij dubbele aanhalingstekens gewoon ertussen gevoegd worden. Dus let op dat er wel een klein verschil in functie is tussen het gebruik van ' en ". Als je geen variabelen tussenvoegt in een string, gebruik dan ook geen " maar '-tekens.

echo 'Dit zijn ' . $artikels . ' van ' . $naam;
echo "Dit zijn $artikels van $naam";

Functies in PHP

Functies vormen de hoeksteen van gestructureerd programmeren. Ook in PHP.

Om mooie herbruikbare stukjes broncode te krijgen die we kunnen gebruiken voor onze website kunnen we dus ook functies gebruiken om HTML-broncode terug te krijgen van PHP.

<?php
$naam = "Meron";

// We kunnen dit direct invullen.
$html ="<h1>{$naam}</h1>";

// Maar we kunnen ook een functie definiëren die we vaker kunnen gebruiken.
function titel($titelTekst) {
  return "<h1>{$titelTekst}</h1>";
}

// We plakken nu het resultaat van de functie titel aan de variabele $html
// en slaan dit op in de variabele $html
$html = $html . titel($naam);
?>
<!DOCTYPE html>
<html>
  <body>
    <?=$html?>
  </body>
</html>

Werken met strings (~ 30 min.)

In PHP zijn er verscheidene manieren om strings aan elkaar te plakken, ook wel concatenatie genoemd (Engels: concatenate/concatenation). Anders dan in Java gebeurt dat niet met een + maar met een .

echo 'Dit zijn ' . $artikelen . ' van ' . $naam;
echo "Dit zijn $artikelen van $naam";
echo "Dit zijn {$artikelen} van {$naam}";

Hier boven valt het op dat er drie verschillende manieren genoemd worden.

  • Met enkele quotes,
  • met dubbele quotes,
  • en dubbele quotes met accolades.

Ga na of ze hetzelfde werken in jouw programmeeromgeving.

Stringfuncties

Omdat strings zo’n belangrijke rol spelen zijn er tal van ingebouwde stringfuncties. Op PHP.NET - Strings kan je ze allemaal vinden en er details over lezen.

Een paar belangrijke zijn:

Probeer de volgende broncode om het resultaat van deze functies te zien.

$tekst = '<h1>PHP - Hoofdstuk 1</h1>';

echo $tekst . '<br>';

echo strlen($tekst) . '<br>';

echo strtoupper($tekst) . '<br>';

echo strip_tags($tekst) . '<br>';

Lange teksten of meerdere regels

Als je meerdere regels in een string wilt opslaan dan kun je dat doen met:

$mijnTekst = <<<EOT
Hier komt nu een enorm lange tekst over
allemaal dingen die stopt als de bovenstaande karakters
worden aangeroepen zoals hieronder:
EOT;
// Hier is de string dus gestopt.

Hieronder staan strlen en substr kort uitgelegd. Probeer zelf uit te zoeken wat strtoupper, strip_tags en strtolower doen.

strlen

Je ziet dat bij de strlen documentatie deze regel staat.

strlen( string $string ) : int

Dit betekent dat deze functie één parameter verwacht van het type string en hier genoemd $string. Het antwoord van deze functie is een int.

Probeer dat uit in je eigen PHP-omgeving.

// In het volgende voorbeeld begin je niet een string met " of ' maar met <<<EOD.
// Deze eindigt vervolgens ook weer met EOD, wat staat voor END OF DEFINITION.
// Je geeft eigenlijk aan dat je hier een string wilt die over meerdere regels doorgaat.
$campert = <<<EOD
Verzet begint niet met grote woorden
maar met kleine daden

zoals storm met zacht geritsel in de tuin
of de kat die de kolder in zijn kop krijgt

zoals brede rivieren
met een kleine bron
verscholen in het woud

zoals een vuurzee
met dezelfde lucifer
die een sigaret aansteekt

zoals liefde met een blik
een aanraking iets wat je opvalt in een stem

jezelf een vraag stellen
daarmee begint verzet

en dan die vraag aan een ander stellen.
EOD;

echo strlen($campert);

substr

Bij de documentatie van substr zie je veel meer informatie staan.

substr ( string $string , int $start [, int $length ] ) : string

In de beschrijving staat: [, int $length]. Dit betekent dat deze parameter optioneel is, en de functie dus ook zou kunnen werken met twee parameters, in plaats van drie.

Probeer de voorbeelden uit.

PHP en HTML-broncode scheiden

Voor het echte werk proberen we PHP en HTML-broncode zo veel mogelijk te scheiden. Dit doen we door bovenaan eerst de PHP uit te werken en dan pas met het HTML document te beginnen. Binnen de HTML-broncode hoeft dan alleen af en toe een PHP echo te verschijnen om de boven gegenereerde tekst ertussen te plakken.

Voor een pure echo is er ook een verkorte versie. Hierbij moet je de variabele dan zonder spaties inzetten.

<?=$variabele?>

<?php
$eenVariabele = <<<EOD
Dit is een interessant stuk tekst,
maar let vooral op de body van het
volgende stuk html."
EOD;

?>
<!DOCTYPE html>
<html lang="nl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PHP-voorbeeld</title>
</head>
<body>
    <p><?=$eenVariabele?></p>
</body>
</html>

Arrays

Introductie op Arrays

PHP kent uiteraard ook arrays, reeksen van variabelen die op de één of andere manier bij elkaar horen. Het grote verschil met bijvoorbeeld Processing en Java is dat er in één array verschillende typen van gegevens bewaard kunnen worden en dat aan een bestaande array zomaar items kunnen worden toegevoegd.

Om een lege array aan te maken:

$items = [];

Maar je kan een array ook aanmaken en meteen met gegevens vullen:

$landen = ['Nederland','Frankrijk','België','Duitsland'];

Een nieuw element toevoegen gaat met:

$landen[] = 'Italië';

Arrays hebben een index, een positie. Het eerste element heeft altijd index 0, het tweede 1 enzovoort. Als we de inhoud van een element van een array willen pakken doen we dit met de index.

echo $landen[2]; // Print België.
$landen[0] = 'Spanje'; // Vervangt Nederland door Spanje.

Met var_dump() kunnen we als ontwikkelaars checken, welke gegevens in een array zitten.

var_dump($landen);

Associatieve arrays

In PHP bestaan er niet zomaar associatieve arrays, nee elke array is van nature een associatieve array.

Dat kan je duidelijk zien als je de volgende broncode draait:

$priemgetallen = [3, 5, 7, 11, 13, 17];
var_dump($priemgetallen);

In de browser zie je nu dat de index (0, 1, 2, 3) wordt weergegeven met een =>.

Als je nu een nieuw item toe wilt voegen kan dat gewoon met een key in plaats van een index-getal.

$priemgetallen['volgende'] = 19;

Raar maar waar..

Je kan arrayelementen altijd opvragen met de sleutel (key) en een element met een begrip specificeren. Denk eens aan de courses SPAD en SPB. Je hebt een knop en wil de posities X en Y, de breedte en de hoogte en de tekst in één variabele opslaan.

Bij PHP kan dat:

$knop['posX'] = 100;
$knop['posY'] = 50;
$knop['breedte'] = 300;
$knop['hoogte'] = 70;
$knop['tekst'] = 'Start';

Het kan ook in één keer met een associatie operator: =>

$knop = [
  'posX' => 100,
  'posY' => 50,
  'breedte' => 300,
  'hoogte' => 70,
  'tekst' => 'Start'
];

Voor het oproepen gebruik je de naam van de array plus de naam van de sleutel. Deze zet je tussen vierkante haakjes en aanhalingstekens. Wanneer je het wil verwerken binnen een string zijn er verschillende opties, zie hieronder:

$omvang = (2 * $knop['breedte']) + (2 * $knop['hoogte']);

/* Een string aanmaken met behulp van concatenatie. */
echo '<button>'. $knop['tekst'] . '</button>';

/* Met dubbele aanhalingstekens. */
echo "<button>$knop['tekst']</button>";

/* Met dubbele aanhalingstekens, arrayvariabele tussen accolades. */
echo "<button>{$knop['tekst']}</button>";

Een ander voorbeeld:

/* Een lijst met gegevens over een meubel. Per element wordt een key (sleutel, naam) aangemaakt */
$meubel['type'] = 'tafel';
$meubel['houtsoort'] = 'beuken';
$meubel['prijs'] = 287;

/* En hier een 2e lijst op een andere manier aangemaakt. */
$andermeubel = ['type' => 'kast', 'prijs' => 1788, 'houtsoort' => 'eiken'];

/* De waarde van een element kan via zijn key opgeroepen worden */
$html =  '<h1>' . $meubel['type'] . '</h1>';
$html .= '<p>van: ' . $meubel['houtsoort'] . '<br>voor: € ';
$html .=  number_format($meubel['prijs'], 2, ',', '.') ;
$html .=  '</p>';

echo $html;

Hier wordt gebruik gemaakt van de samengestelde concatenatie operator: .=

De inhoud van de variabele links van de operator ($html in het voorbeeld) wordt uitgebreid met de expressie rechts (een stuk tekst). Er worden dus steeds stukken tekst erachter geplakt. HTML-broncode is voor PHP gewoon tekst.

Samengestelde operatoren

Samengestelde operatoren zijn er voor tekst- en rekenkundige operaties.

operatorbeschrijving
.=tekst aan bestaande variabele toevoegen
+=de waarde rechts bij de variabele links op tellen en opslaan
++een variabele met één verhogen (increment)
-=de waarde rechts van de variabele links aftrekken en opslaan
--een variabele met één verminderen (decrement)
*=de variabele vermenigvuldigen met de waarde rechts en opslaan
/=de variabele links met de expressie rechts delen en opslaan
%=modulo op devariabele links met de expressie rechts en opslaan
$getal = 0;
$tekst = '';

$getal += 10;
$getal --;
$getal %= 3;
$getal *= 133;
$getal ++;

$tekst .= 'Berekeningen <br>';
$tekst .= 'De uitkomst is: ' . $getal;

foreach

Soms willen we binnen een programma bedragen optellen, een reeks van gegevens langslopen en deze achter elkaar uitprinten of iets dergelijks. Hiervoor hebben we zoiets als een for-lus. Maar arrays kunnen we ook met een foreach-constructie langslopen.

Het principe: voor elk element uit de lijst, doe iets. Het element krijgt de tijdelijke, lokale naam item.

De syntax :

foreach ($lijst as $item) {
  // Hier kan je nu iets doen met $item.
  echo $item;
}

Voorbeeld met een simpele geïndexeerde array:

$overzicht = '';

// Dit is dan de `foreach`-statement.
foreach ($steden as $stad) {
  $overzicht .= $stad . '<br>';
}

echo $overzicht;

Voorbeeld met een associatieve array, de sleutel wordt hier ook als variabele gebruikt:

$consumpties = [
  'Bier' => 3.50,
  'Spa rood' => 2.40,
  'borrelnoten' => 4
];

$totaal = 0;
$bon = '';

// hieronder is `$consumptie` de key
// en `$prijs` de value
foreach ($consumpties as $consumptie => $prijs) {
  $bon .= "$consumptie: $prijs<br>";
  $totaal += $prijs;
}

echo $bon . "--------------------<br>Totaal: " . $totaal;

Meerdimensionale arrays

Arrays kunnen om meerdere dimensies worden uitgebreid. Een tweedimensionale array kan je je als een tabel voorstellen.

Hier een array met cijfers: per student (hier met studentennummer) wordt een reeks cijfers bijgehouden.

$cijfers = [];

$cijfers['123876'] = [7.6, 4.9, 6.0];
$cijfers['767476'] = [5.6, 4.9, 6.4, 7.2, 7.0];
$cijfers['322355'] = [8.6, 6.9, 7.4];

Een driedimensionale array kunnen we ons voorstellen als een kubus. We breiden ons voorbeeld van zonet met courses uit:

// een nieuwe rij cijfers voor student '123876' en het vak SPB.
$cijfers ['123876']['SPB'] = [9.0, 8.0];

// Hier worden bij de student '123876'op de rij van SPB nieuwe cijfers toegevoegd.
$cijfers ['123876']['SPB'][] = 7.2;

// Hier worden bij cijfers een student '886322' met zijn cijfers voor WT en SAQ aangemaakt.
$cijfers['886322'] = [
  'WT' => [6.7, 5.8, 7.5],
  'SAQ' => [7, 6.5]
];

Best complex allemaal! Het goede nieuws is dat als wij dit goed programmeren de computer het werk doet en we er verder geen omkijken naar hebben. Uiteraard moeten we wel de principes en de syntaxis goed kennen.

Hier nu nog het voorbeeld van de meubels waarbij alle meubels langsgelopen worden en per meubel de prijs bij het totaal geteld wordt.

$totaalprijs = '';
foreach ($meubels as $meubel) {
  // Een `$meubel` heeft meerdere waardes. Wij willen de prijs.
  $totaalprijs += $meubel['prijs'];
}

echo $totaalprijs;

Arrayfuncties

Ook voor arrays bestaan een groot aantal gespecialiseerde functies. Alle arrayfuncties en details over hoe ze gebruikt worden vind je op PHP.NET - Array Functions.

De sorteerfuncties sorteren de bestaande array. Je hoeft niet het resultaat ervan ergens anders op te slaan.

En hier een paar voorbeelden:

$aantal = count($spelers);                  // Aantal elementen in array.
$aanwezig = in_array('Robben', $spelers);   // Staat hij in de lijst?
sort($spelers);                             // Waardes sorteren.
$zangers = [
  'Tabitha' => 'Foen-A-Foe',
  '2Pac' => 'Shakur',
  'Kanye' => 'West',
  'Kurt' => 'Cobain',
  'Lil' => 'Kleine'
];
asort($zangers);    // op achternaam sorteren en de voornaam erbij houden
ksort($zangers);    // op voornaam (sleutel-key) sorteren

array_slice()

Lees samen de documentatie van array_slice.

sort(), ksort() en asort()

Lees samen de documentatie:

  • sort - ’gewone’ sorteerfunctie voor een array.
  • ksort - sorteer een array op basis van de key.
  • asort - sorteer een array op zonder de assocatie te veranderen.

count() (~10 min.)

Lees nu in je eentje de documentatie van count.

isset()

Lees nu in je eentje de documentatie van isset.

Met isset kan je dus kijken of iets in een array beschikbaar is. Heel handig bijvoorbeeld als je op basis daarvan wilt handelen.

Bijvoorbeeld:

if (isset($films[0]) && isset($films[0]['title'])) {
  echo <h1>$films[0]['title']</h1>
}

Of:

// Bestaat de sleutel `'opleiding'`?
$bestaat = isset($student['opleiding']);

Lussen en Condities

Software moet aan de lopende band beslissingen nemen. Uiteraard geldt dit ook voor webapplicaties. Mag deze mevrouw naar binnen? Is alles ingevuld? Heeft hij op 'Verzenden' geklikt?

if () {} - if () {} else {}

Na de if staat er tussen de haakjes de conditie. Als de uitkomst van dat wat er tussen de haakjes staat true is, wordt de opdracht erna uitgevoerd. PHP is heel soepel wat de true betreft. Alles wat geen false, 0, null of '' (lege string) is, is voor PHP waar.

Als je een HAVO diploma hebt, kan je je inschrijven ....

of:

$havo_diploma = true;
if ($havo_diploma) {
  echo '<button type="button" class="inschrijven">Schrijf je in</button>';
}

Als je minstens 18 bent mag je een biertje drinken.

if ($leeftijd < 18) {
  $display = '<p><strong>Geen alcohol!</strong></p>';
} else {
  $display ='<p><strong>Biertje?</strong></p>';
}
echo $display;

Vergelijkingsoperatoren

Vaak gaan we tussen de haakjes achter de if iets vergelijken. Ben jij jonger dan 18? Hebben wij dezelfde leeftijd? Komen de wachtwoorden overeen?

OperatorBeschrijving
===Is gelijk en ook nog hetzelfde type? (5 === '5' geeft false)
!==Is ongelijk en ook nog hetzelfde type? (De uitkomst is true als ongelijk.)
>Is groter dan?
>=Is groter dan of gelijk aan?
<Is kleiner dan?
<=Is kleiner dan of gelijk aan?

if () {} elseif () {} else {}

Met elseif erbij kunnen meerdere opties gecheckt worden. Per conditie kunnen dan specifieke opdrachten worden vastgelegd. Als één conditie true is worden de opties verderop niet meer gecheckt. De evaluatievolgorde is dus van links naar rechts en van boven naar beneden.

Voor kleine kinderen kost het niks, voor schoolkinderen € 5, voor studenten € 7 en voor alle anderen € 10.

Of in PHP commentaar geven op basis van het cijfer:

if ($cijfer < 4) {
  $commentaar = 'Er mist nog heel veel aan kennis.';
} elseif ($cijfer < 5.5) {
  $commentaar = 'Iets meer oefenen!';
elseif ($cijfer <= 6.5) {
  $commentaar = 'Okay, maar het kan beter!';
} elseif ($cijfer <= 8) {
  $commentaar = 'Goed gedaan!';
} elseif ($cijfer > 8 && $cijfer <= 10) {
  $commentaar = 'Uitstekend!';
} else {
  $commentaar = 'Ingevoerd cijfer klopt niet.';
}

Logische operatoren: && - || - !

Soms wordt er een combinatie van omstandigheden gebruikt om te beslissen of iets wel of niet moet gebeuren.

Je bent minimaal 21 en hebt een rijbewijs.

Je bent medewerker of student van de HAN.

Je hebt een diploma maar geen werkervaring of andersom.

  • && (‘logische en’): beide condities moeten waar zijn, oftewel beide expressies moeten true opleveren.
  • || (‘logische of’): één van de condities moet waar zijn, minimaal één expressie moet true opleveren.
  • !: draai om: de waarde true wordt false, en false wordt true.

Hier een aantal voorbeelden met meerdere condities om te checken.

if ($cijfer < 1 || $cijfer > 10 || !is_numeric($cijfer) ) {
  $commentaar = 'Geen geldig cijfer';
} elseif ($cijfer >= 5.5 && $cijfer < 6) {
  $commentaar = 'Erg krap';
}

if ($mentor === 'Vries' && ($klas === 'I1A' || $klas === 'I1B')) {
  $bericht = 'Het mentoruur vervalt';
}

Herhalen en uitpakken met foreach()

De foreach-loop is een statement waarmee je items direct uit een array kan pakken, dat is dus anders dan een klassieke for-loop.

Een goed voorbeeld spreekt boekdelen:

$cijfers = [3, 4, 6, 7];
$som = 0
foreach($cijfers as $cijfer) {
  $som += $cijfer;
}

Uitpakken met sleutel en waarde

Een foreach kan ook een associatieve array uitpakken met sleutel en waarde.

Dus bijvoorbeeld:

$pizzas = [
  'Bloemkool Bechamel' => 7.95
  'Fauxlami Fest' => 8.95,
  'Margarhita' => 6.95
];

$lijst = "<ul>";
foreach($pizza as $pizza => $prijs) {
  $lijst .= "<li>{$pizza} - {$prijs} </li>";
}

In bovenstaande voorbeeld zie je dus dat zowel de pizzanaam (de sleutel) als de prijs (de waarde) uit te lezen vallen.

Zolang herhalen als: while () {}

Naast met foreach kan je met een while-loop statements herhaald uitvoeren. Dit doe je door vooraf een beginwaarde in te stellen, bij de conditie een eindwaarde en binnen het while-blok het telmechanisme.

while (conditie) {
  // Schrijf hier de broncode die je herhaald wilt uitvoeren.
}

Het voorbeeld met de tafel van 10 ziet er met een while-statement zo uit:

$getalMaximum = 10;
$tafel = '';
$getal = 1;
while ($getal <= 10) {
  $tafel .= "$getal * $getalMaximum = " . $getal * $getalMaximum . '<br>';
  $getal++;
}

Je gebruikt while als van tevoren niet vaststaat hoelang een conditie aanhoudt. Een typisch voorbeeld zou een applicatie zijn die een temperatuur verwerkt, die voortdurend met een sensor gemeten wordt.

Laat een waarschuwing zien zolang het vriest (temperatuur < 0). Uiteraard moet je dan binnen de herhaling telkens weer de temperatuur inlezen.

Functies en Bestanden organiseren

Functies

Functies doen het werk voor ons. Wij roepen ze op, vaak met speciale wensen, en krijgen iets gedaan of iets terug. Denk aan een restaurant waar je een bestelling opgeeft en een lekker gerecht geserveerd krijgt.

We hebben al diverse ingebouwde functies gebruikt: var_dump(), isset(), strlen(), count(), sort(). Er zijn verschillen!

var_dump() en sort() doen iets voor ons (een overzicht uitprinten, een array sorteren) en that’s it. In een restaurant zou je bijvoorbeeld kunnen vragen of de tafel schoon gemaakt kan worden.

isset(), strlen() en count() daarentegen geven iets terug, iets wat we dan verder verwerken (zoals opeten).

Uiteraard kunnen we ook onze eigen functies schrijven. Het schrijven van een functie is als het uitbesteden van werk. Je schrijft broncode en geeft het een naam. Dat stukje broncode kan je dan aanroepen. Het is heel goed in het doen van een speciale klus (bijvoorbeeld het maken van salades).

Functie definiëren

Bij het definiëren van de functie leggen we vast wat deze moet doen.

function doeIets() {
  // Hier komt de functiebody.
}

Een uitgewerkt voorbeeld:

function groeten()
{
  $uur = date('H');
  if ($uur < 12) {
    return 'Goedemorgen!';
  } elseif ($uur < 18) {
    return 'Goedemiddag!';
  } else {
    return 'Goedenavond';
  }
}
  • We beginnen altijd met keyword function.
  • Daarachter komt een naam, die duidelijk maakt wat de functie doet. Omdat functies werk verrichten gebruiken we er in ieder geval ook een werkwoord bij.
  • De variabele $uur is alleen binnen de functie bekend.
  • Met return geven we iets terug. De aanroepende broncode kan dit dan weer gebruiken om zijn werk te doen.
  • Zo gauw een return is uitgevoerd, stopt de functie. Broncode die daarna komt wordt niet meer uitgevoerd.

Functie aanroepen

Een functie doet niks uit zichzelf. Pas als iemand de opdracht geeft, de functie bij zijn naam roept, komt deze in actie.

echo '<p>' . groeten() . '<p>';
  • Een functie aanroepen doen we door de naam en daarachter ronde haakjes te typen.
  • Als een functie iets teruggeeft (return), kan het resultaat meteen verder verwerkt worden. In het voorbeeld hierboven wordt de tekst die teruggegeven wordt tussen andere tekst (in dit geval HTML-elementen) geplakt en daarna met een echo op de webpagina geplaatst.

Parameters

Omdat we vaak speciale wensen hebben (‘breng een biertje’ in plaats van ‘breng iets’), schrijven we meestal functies met parameters.

function doeIets($metDit, $enDat) {
}

Functie met parameter(s) definiëren

function groetenMetNaam($naam) {
  $uur = date('H');
  if ($uur < 12) {
    return "Goedemorgen, $naam!";
  } elseif($uur < 18) {
    return "Goedemiddag, $naam!";
  } else {
    return "Goedenavond, $naam!";
  }
}

Een voorbeeld van een functiedefinitie met een parameter:

  • Deze functie verwacht een parameter $naam, dat betekent dat ze alleen werkt als je ook een naam meegeeft als argument bij het aanroepen.
  • $naam is ook een lokale variabele, die alleen binnen de functie bekend is.
  • Met return wordt weer een tekst (string) teruggegeven. Deze keer met de naam die bij het aanroepen is ingevuld erin verwerkt.

Een functie met parameter aanroepen

echo '<p>' . groetenMetNaam('Robert') . '</p>' ;

En hier de oproep van de hierboven gedefinieerde functie met de string 'Robert' als argument:

  • Hier wordt Robert gegroet.
  • Bij het aanroepen van de functie bepaal ik, wie ik wil groeten.
  • Later zal de bezoeker van onze website zijn naam invullen en dan kunnen wij haar/hem met zijn naam welkom heten.
  • Bij het aanroepen noemen we dat wat we aan de functie meegeven meestal een argument.
  • Ook hier wordt weer de teruggegeven tekst verder verwerkt en uiteindelijk getoond.

Parameters met default waarde

Uiteraard kunnen we ook meerdere parameters gebruiken. De laatste (of twee, drie laatsten) kunnen ook optioneel zijn. Daarvoor geven we de parameter bij het definiëren een default (standaard) waarde.

function aanhef($voornaam, $achternaam, $tussenvoegsel = "") {
  return "Beste $voornaam $tussenvoegsel $achternaam, <br>";
}

function dobbelen($hoogste = 6) {
  return mt_rand(1, $hoogste);
}
  • Bij aanhef() hebben we drie parameters, $voornaam en $achternaam zijn verplicht om bij het aanroepen van de functie in te vullen, $tussenvoegsel is optioneel.
  • $tussenvoegsel krijgt als standaardwaarde een lege string mee.
  • de functie dobbelen() kunnen we zonder argument aanroepen.
  • de variabele $hoogste wordt dan automatisch zes (het hoogste cijfer van een dobbelsteen). Maar we zouden net zo goed met een dobbelsteen met 20 zijden, etc. kunnen spelen.

Zo kun je de functies gebruiken:

echo '<p>' . aanhef("Jan", "Sloot", "van der") . '</p>';
echo '<p>' . aanhef("Piet", "Veenstra") . '</p>';

echo '<p>Je gooit een <span>' . dobbelen(20) . '</span></p>';
echo '<p>Je gooit een <span>' . dobbelen() . '</span></p>';
  • Functieoproepen met en zonder optionele parameters.
  • Verplichte parameters moeten wel worden ingevuld.

Global scope

Zoals onder Functies beschreven, zijn variabelennamen binnen een functie alleen daar bekend. Elke functie is een eigen wereld. De communicatie met de buitenwereld gebeurt via de argumenten (invoer) en de returnwaarde (uitvoer).

Het is echter mogelijk voor speciale gevallen om via het keyword global toegang tot een globale variabele te krijgen. Hier laat ik een voorbeeld zien en benoem ik daarna de bijzonderheden.

$config = ['websitenaam' => 'WebTech International', 'taal' => 'nl'];

function genereerHead() {
  global $config;
  $html = <<<HEAD                  // Begin van een HEREDOC-constructie
  <!DOCTYPE html>                  // inhoud van HEREDOC. Er hoeft niet op
  <html lang="$config['taal']">    // aanhalingstekens gelet te worden.
  <head>
    <meta charset="UTF-8">
    <title>$config[websitenaam]</title>
  </head>
HEAD;                              // Einde van HEREDOC (er mag geen spatie voor staan).
  return $html;
}

echo genereerHead();
  • Er wordt een array met diverse algemene instellingen voor een website aangemaakt.
  • Binnen de definitie van de functie genereerHead() wordt met global verwezen naar de globale $config met instellingen voor deze website.

HEREDOC

Bij het aanmaken van de string-variable $html maken we hierboven gebruik van de zogenoemde HEREDOC-syntaxis. Deze kan vooral gebruikt worden als we grotere stukken HTML-code binnen PHP mee willen nemen. We hebben dan minder gedoe met aanhalingstekens.

  • Een HEREDOC-blok wordt aangezet door <<<EENNAAMINHOOFDLETTERS
  • Daarna kan gewoon HTML-code worden geplaatst, met eventueel PHP variabelen ertussenin
  • Het blok wordt weer afgesloten met weer EENNAAMINHOOFDLETTERS. Deze moet echter op een eigen regel en dan helemaal vooraan (ook geen spaties) worden geplaatst.
  • De sleutels van arrayelementen worden zonder aanhalingstekens geschreven.

Broncode in aparte bestanden organiseren

Het is Good Practice om broncode die we vaker nodig hebben in aparte bestanden op te slaan. Dat kan HTML-broncode zijn die op elke pagina meer of minder hetzelfde is of PHP-broncode die op meerdere plekken tussengevoegd moet worden. Omdat we PHP-broncode sowieso vaak in aparte functies plaatsen kunnen we het beste deze functies in eigen bestanden opslaan.

require_once

Als require_once het vereiste bestand niet kan vinden, geeft PHP een foutmelding en wordt de verdere uitvoer afgebroken. require_once wordt gebruikt om benodigde functies te laden. Het is niet geschikt om letterlijk een stuk PHP-broncode middenin een PHP-bestand in te voegen. Maar invoegen is een slechte praktijk, omdat hierdoor de broncode binnen één bestand onleesbaarder, en ook de interactie tussen broncode in het ene en het andere PHP-bestand ondoorzichtiger wordt. Bij require_once wordt voorkomen dat een functie vaker geladen wordt als hetzelfde require_once-statement herhaald wordt uitgevoerd (binnen alle broncode die in één keer uitgevoerd wordt, dus voor het produceren van één response). Deze herhaling kan voorkomen bij een uitgebreide applicatie met veel broncode verdeeld over veel bestanden.

<?=
require_once 'components/footer.php';
require_once 'components/header.php';
require_once 'components/overzicht.php';

maakHeader();
?>
<main>
  <h2>Diverse onderwerpen</h2>
  <p>Met een heleboel tekst ...</p>
  <?=
    maakOverzicht('fietsen');
  ?>
</main>
<?=
HTML_FOOTER
?>
  • Met require_once 'components/overzicht.php'; voert PHP een PHP-bestand overzicht.php uit waarin verschillende functies zijn gedefinieerd. Als deze component bijvoorbeeld al vanuit eerdere bezochte pagina is ingeladen haalt PHP het resultaat daarvan op uit het geheugen.

  • maakOverzicht() is een functie die in components/overzicht.php gedefinieerd staat en door de require_once-statement bovenaan beschikbaar is om aan te roepen.

  • In components/footer.php bestaat een functie maakFooter() en wordt alvast op de volgende manier een constante gemaakt voor de footer (die voor alle pagina’s hetzelfde is).

    define('HTML_FOOTER', maakFooter());
    

    Op deze manier hoeft de vaste HTML-broncode van de footer niets telkens opnieuw geschreven te worden. Ook wordt maakFooter() niet iedere keer opnieuw aangeroepen als er een pagina geproduceerd wordt. Dat zou wel zo zijn als de aanroep maakFooter() onder </main> geschreven was.

Gebruikersgegevens verwerken

Formulieren

Formulieren hebben we eerder bij HTML besproken. Maar nu wordt het menens. Tot nu toe konden we ze bouwen, nu gaan we ze gebruiken. Even ter herinnering een typisch formulier met alles erop en eraan.

<form method="get" action="inschrijven.php">
  <label for="naam">Naam: </label>
  <input type="text" name="naam" id="naam"><br>
  <label for="vak">Vak: </label>
  <select name="vak" id="vak">
    <option value="">...</option>
    <option value="fat">FAT</option>
    <option value="spb">SPB</option>
    <option value="dbsql">DBSQL</option>
    <option value="wtux">WTUX</option>
    </select><br>
  <label for="opmerking">Opmerkingen: </label>
  <textarea name="opmerking" id="opmerking"></textarea><br>
  <input type="submit" name="verzenden" value="Verzenden">
</form>
  • We hebben een <form>-element en daarbinnen verschillende formulierelementen zoals <input> in verschillende smaken, <select>, <textarea>.

  • <form> heeft een method-attribuut. De mogelijke waarden hiervan zijn get en post. get is al de standaardwaarde, dus hoef je niet op te geven.

  • <form> heeft ook een action-attribuut. Hierin wordt vastgelegd, welk bestand opgeroepen wordt nadat op ‘Verzenden’ of ‘Submit’ is geklikt.

  • Alle formulierelementen hebben een name-attribuut. Met name wordt vastgelegd onder welke naam de stukjes informatie die zijn ingevuld, verstuurd worden.

  • Sommige elementen hebben ook een value-attribuut. Dat is de waarde die er of standaard is ingevuld of bij een keuzelijst bijvoorbeeld als mogelijk antwoord opgestuurd wordt.

Een GET-querystring

Als we bij <form method="get"> instellen (of beter nog, method niet zelf instellen), kunnen we (en iedere bezoeker) in de URL lezen wat we bij welk veld (het name-attribuut geeft dit aan) ingevuld hebben en op deze manier naar de server verstuurd wordt. Bij ons voorbeeldformulier zou dit zoiets kunnen zijn:

localhost/inschrijven.php?naam=Pieter&vak=wtux&opmerking=Dit%20wil%20ik%20leren&verzenden=Verzenden

  • We zien inschrijven.php, het bestand dat bij action stond ingevuld.
  • Het vraagteken daarna geeft aan dat hierachter de gegevens uit het formulier komen te staan.
  • We zien daarna telkens een sleutel (naam, vak, opmerking, verzenden) en een bijbehorende waarde (Pieter, wtux, Dit wil ik leren, Verzenden).
  • De verschillende sleutel/waardeparen worden door een & gescheiden.
  • Spaties en andere bijzondere tekens moeten door een procentcode worden vervangen. Hierboven zien we steeds %20 waar in het formulier een spatie getypt werd.

$_GET en $_POST

Aan de serverkant kunnen we gegevens die per get verstuurd zijn met de array $_GET en gegevens die per post verstuurd zijn met de array $_POST uitlezen. Deze superglobals worden automatisch ingevuld als een formulier verstuurd is.

GET

De querystring kan je met de volgende broncode verwerken:

print_r($_GET);

$antwoord = "<p>Beste {$_GET['naam']},</p>
  <p>Je hebt je opgegeven voor:
    <strong>{$_GET['vak']}</strong>
  </p>
  <p>Opmerking:<br> <em>{$_GET['opmerking']}</em></p>";
$antwoord .= "<p>Bedankt voor je inschrijving!</p>";

echo $antwoord;
  • De print_r() staat voor ‘print array’ en wordt hier gebruikt om alle binnengekomen gegevens weer te geven.
  • $_GET['naam'] enzovoort zijn elementen van de GET-superglobal-array die nu met hun sleutel (= het name-attribuut van het formulierelement) op te vragen zijn.
  • Het is good practice om HTML-broncode eerst op te bouwen en dan in één keer op de goede plek te echo’en.

POST

Hier nu met method="post":

 <form method="post" action="aanmelden.php">
  <!-- Hier wordt de waarde ingevuld met de datum van vandaag. -->
  <input type="hidden" name="datum" value="<?=date('d-m-Y')?>">
  <div>
    <label for="naam">Naam:</label>
    <input type="text" name="naam" id="naam">
  </div>
  <div>
    <label for="password">Wachtwoord:</label>
    <input type="password" name="password" id="password">
  </div>
  <div>
    <label for="vak">Afgerond:</label>
    <select name="vakken[]" id="vak" multiple>
      <option value="fat" selected>FAT</option>
      <option value="spb" selected>SPB</option>
      <option value="dbsql">DBSQL</option>
      <option value="wtux">WTUX</option>
    </select>
  </div>
  <div>
    <p>Vooropleiding:</p>
    <label for="opleiding_havo">havo</label>
    <input type="radio" name="opleiding" id="opleiding_havo" value="havo">
    <label for="opleiding_mbo">mbo</label>
    <input type="radio" name="opleiding" id="opleiding_mbo" value="mbo">
    <label for="opleiding_anders">anders</label>
    <input type="radio" name="opleiding" id="opleiding_anders" value="anders" checked>
  </div>
  <input type="submit" name="verzenden" value="Verzenden">
</form>

Bijzonderheden hier zijn:

  • Een input-veld type="hidden". Dit is een voor de bezoeker onzichtbaar veld waar door de programmeur gegevens ingevuld kunnen worden. Vanuit de server in een verborgen veld data invullen die niet veranderd mag worden is geen goede praktijk. De waarde kan door de client worden aangepast. Als de datum in voorgaand voorbeeld alleen een suggestie is, verberg dan de datum niet.
  • een input-veld type="password" met de beroemde sterretjes die je te zien krijgt als iemand zijn wachtwoord invult. Belangrijk is dat we gegevens die niet zomaar gezien mogen worden niet met method="get" versturen omdat dan alsnog iets in de adresbalk en ook in de geschiedenis van de browser te zien is.
  • Bij de keuzelijst, het select-element kunnen we meerdere keuzes tegelijk maken (attribuut multiple). Deze keuzes moeten we dan wel als array meegeven. Dit doen we door bij name="vakken[]" met vierkante haakjes aan te geven dat dit meerdere waardes zullen zijn.

En dit uitgebreide formulier met de method="post" kan nu aan de serverkant zo worden verwerkt:

$naam = $_POST['naam'];
$password = $_POST['password'];
$datum = $_POST['datum'];
$opleiding = $_POST['opleiding'];
// Hier wordt een array van vakken binnengehaald.
$vakken = $_POST['vakken'];
$antwoord = "<p>Beste $naam, </p>
             <p>Je vooropleiding is:
              <strong>$opleiding</strong><br>
              De volgende vakken heb je afgerond: <strong>";

// Om alle elementen langs te gaan, gebruiken we `foreach`.
foreach ($vakken as $vak) {
  $antwoord .= $vak . ' ';
}

$antwoord .= '</strong></p>';

echo $antwoord;
  • Nu is het de $_POST-array die op de server ingevuld wordt met alle gegevens van het post-formulier, met telkens weer de name van elk element als sleutel.
  • Omdat er meerdere vakken konden worden gekozen en die in een mini-arraytje verwerkt werden, moeten we in de verwerkingsbroncode in PHP op de server ook alle elementen van de array vakken langslopen. Let op! Dit zijn alleen de door de bezoeker geselecteerde opties.

GET versus POST

Er zijn duidelijke situaties wanneer we GET als method kiezen en andere, waar POST aangewezen is. Zoals eerder gezegd zullen we wachtwoorden, bankgegevens en dergelijke altijd met POST versturen. Overal waar we iets toevoegen (bijvoorbeeld een overschrijving, een bestelling, een registratie) gebruiken we in principe POST. Daar waar we iets opvragen/opzoeken, gebruiken we GET. POST voor posten en GET voor krijgen.

Het grote voordeel van GET is dat we de URL met de bijbehorende gegevens als hyperlink kunnen bewaren en/of aan iemand doorsturen. Door een simpele klik krijg je dan hetzelfde resultaat, dat eerder door het invullen van een formulier verkregen is. Elke hyperlink, met of zonder querystring erachter leidt bij het klikken erop tot een HTTP GET-request. HTTP is het protocol dat de communicatie tussen browsers, of clients in het algemeen, en servers regelt.

Een formulier en zijn verwerking in één bestand

Soms is het handig om de verwerking van een formulier op dezelfde pagina te doen. Denk bijvoorbeeld aan een soort rekenmachine. Je wilt het resultaat zien, maar ook het formulier omdat je misschien meteen een andere berekening wilt kunnen uitvoeren. Hieronder een voorbeeld waar of het formulier of de reactie op het ingevulde weergegeven wordt.

<?php
  if(!isset($_GET['verzenden'])){
?>
  <!-- Let op. Hier begint weer zuivere HTML-broncode. -->
  <h3>Graag uw gegevens!</h3>
  <form>
    <div>
      <label for="voornaam">Voornaam:</label>
      <input type="text" name="voornaam" id="voornaam">
    </div>
    <div>
      <label for="achternaam">Achternaam: </label>
      <input type="text" name="achternaam" id="achternaam">
    </div>
    <input type="submit" name="verzenden">
  </form>
<?php
  } else {
    $welkom = '<h3>Hartelijk welkom, '. htmlspecialchars($_GET['voornaam']) . ' ' . htmlspecialchars($_GET['achternaam']) . '!</h3>';
    echo $welkom;
  }
?>
  • Met isset($_GET['verzenden']) kijken we of iemand via de verzendknop van het formulier of via een gewone paginalink is binnengekomen.
  • Als er niet op ‘Verzenden’ is geklikt, !isset(), laten we het formulier zien, anders genereren we met de ingevulde en opgestuurde gegevens een antwoord.
  • Hier is ook mooi te zien dat if/else-blokken ook over stukken HTML-broncode heen gebruikt kunnen worden.
  • De <form>-attributen action en method zijn niet ingesteld. Bij gebrek aan een waarde voor action, stuurt de browser het formulier op naar de URL van de huidige pagina. method is standaard gelijk aan get.

Nadeel van verwerken in één bestand

Een belangrijk nadeel van bovenstaande oplossing treedt op wanneer het formulier verstuurd is en de pagina herladen wordt. In dat geval wordt het formulier een tweede keer verwerkt. In bovenstaand voorbeeld is dat natuurlijk geen probleem, maar wanneer de gegevens daadwerkelijk verwerkt worden (denk aan het opslaan van gegevens of het doen van een aankoop in een webwinkel) wil je voorkomen dat er op deze manier per ongeluk twee keer iets gedaan wordt wat maar één keer mag gebeuren. Een oplossing daarvoor wordt verderop in dit hoofdstuk behandeld. Die oplossing wordt gezien als good practice.

Sessions

Sessions zijn een apart verhaal bij een webapplicatie. In het begin van je carrière als webontwikkelaar is dit soms lastig te begrijpen. Het ‘probleem’ bij HTTP is dat het stateless is. Dat wil zeggen dat de server niet bijhoudt wie er bij hem allemaal aan de deur klopt om een pagina op te vragen. Als jij tien keer dezelfde pagina opvraagt zal de server dat in principe niet opmerken. Er komen immers zoveel mensen langs, er is geen beginnen aan.

Het lastige is echter dat de server uit zich zelf helemaal niks bijhoudt. Dus als jij inlogt en naar een andere pagina gaat, weet de server al niet meer wie je bent. Onhandig als jij bij een webshop iets wilt bestellen. Een winkelwagen is dan niet zomaar mogelijk.

Voor dit soort situaties zijn sessions bedacht. Als de programmeur bepaalt dat er met sessies gewerkt moet worden, houdt de server bij wie je bent en kan de server jouw gegevens (bestellingen) over verschillende pagina’s heen vasthouden. De server bewaart dan alle gegevens per sessie (= per bezoeker of client) vijftien minuten. Nadat server vijftien minuten of een andere tijd (die we kunnen instellen) niks van de klant gehoord heeft, vergeet de server deze sessie. De client (browser) bewaart een unieke ID voor de sessie in een cookie. De ID geeft de browser telkens door aan de server, zodat de server de juiste sessiegegevens kan opzoeken.

Fig. 1: Het opbouwen van een sessie in PHP.

Fig. 1: Het opbouwen van een sessie in PHP. Een sessie kan in een database worden bewaard maar bijvoorbeeld ook als bestand op de server.

session_start()

Elke pagina die mee wil doen aan de sessie moet in eerste instantie een sessie starten of weer oppikken. De bezoeker krijgt dan een session-ID. Deze wordt gedurende het hele ‘gesprek’ bewaard.

$_SESSION[]

Met session-variabelen die in de superglobal $_SESSION kunnen worden opgeslagen worden gegevens over pagina’s heen doorgegeven. Elke pagina die hier aan mee wil doen moet wel eerst een session_start()-aanroep doen.

Voorbeelden

De volgende stukken broncode staan voor twee pagina’s.

// Pagina 1
<?php
session_start();
if ($_POST['user'] === 'Jan' && $_POST['password'] === "1234") {
  $_SESSION['user'] = $_POST['user'];
  $_SESSION['paginasBezocht'] = 0;
}

Hier wordt een sessie opgestart of een bestaande weer opgepakt, en er worden sessievariabelen $_SESSION['user'] en $_SESSION['paginasBezocht'] aangemaakt.

<?php
  session_start();
if (!isset($_SESSION['user'])){
  session_destroy();
  header('Location: login.php');
} else {
  $_SESSION['paginasBezocht']++;
?>
  <!DOCTYPE html>
  <html lang="nl">
  ...
  </html>
<?php } ?>
  • Dit is een andere pagina, waar een bestaande sessie weer wordt opgepakt of een nieuwe wordt aangemaakt.
  • Dan wordt er gecheckt of de sessionvariabele $_SESSION['user'] bestaat. Als die niet bestaat (iemand komt op deze pagina binnen) wordt de sessie opgeheven en de loginpagina (het vorige broncodevoorbeeld) weergegeven.
  • Als de $_SESSION['user'] bestaat, bestaat ook $_SESSION['paginasBezocht'] en wordt deze teller met één verhoogd. Op deze manier kan bijhouden worden hoeveel pagina’s een bezoeker bekijkt of tenminste aanklikt.

header('Location: pagina.html')

Met de functie header() en de string Location: … met op de puntjes een werkende URL voor GET-requests, kan de bezoeker naar een andere URL worden omgeleid. Heel typisch voor sites waar je je moet aanmelden om mee te mogen doen, is dat je naar login.php wordt omgeleid als je je nog niet hebt aangemeld.

Een formulier en zijn verwerking in twee bestanden

Bij de uitleg van de verwerking van een formulier in één bestand werd al aangegeven dat dat een belangrijk nadeel heeft: bij herladen van de pagina worden de gegevens opnieuw verstuurd. De volgende oplossing, die als good practice wordt beschouwd, lost dat probleem op.

Het formulier: formulierpagina.php

<?php
  // De sessie wordt gestart.
  session_start();

  // In het verwerkingsbestand wordt deze sessievariabele gezet.
  if (isset($_SESSION['voornaam'])) {
    $welkom = '<h3>Hartelijk welkom, ' . htmlspecialchars($_SESSION['voornaam']) . ' ' . htmlspecialchars($_SESSION['achternaam']) . '!</h3>';
    echo $welkom;
  } else {
?>
<form method="post" action="verwerk.php">
  <div>
    <label for="voornaam">Voornaam:</label>
    <input type="text" name="voornaam" id="voornaam">
  </div>
  <div>
    <label for="achternaam">Achternaam: </label>
    <input type="text" name="achternaam" id="achternaam">
  </div>
  <input type="hidden" name="oorsprong" value="formulierpagina">
  <input type="submit" name="verzenden">
</form>
<?php
  }
  // De sessievariabelen weer opruimen:
  unset($_SESSION['voornaam']);
  unset($_SESSION['achternaam']);
?>

De verwerking: verwerk.php

Naar dit bestand wordt verwezen in het bovenstaande bestand formulierpagina.php. Dit is geen ‘view’ (een scherm voor de gebruiker), maar een technische pagina. Je moet dus de bezoeker na het uitvoeren van deze broncode weer terugsturen naar een normale pagina.

<?php
  // De sessie wordt gestart of opnieuw opgepakt.
  session_start();

  // We kopiëren deze naar de sessie zodat de formulierpagina de gegevens kan tonen.
  $_SESSION['voornaam'] = $_POST['voornaam'];
  $_SESSION['achternaam'] = $_POST['achternaam'];

  // Hier zou je iets met de gegevens kunnen doen, zoals ze opslaan in een database.

  // Maak zelf de URL aan met een ‘allowlist’.
  // Vertrouw niet letterlijk de waarde uit het formulierveld `oorsprong`.
  // Daar kan vanalles in staan.
  if ($_POST['oorsprong'] === 'formulierpagina') {
    // Dit zorgt ervoor dat de browser de bezoeker weer terugstuurt naar een andere pagina.
    header('Location: /formulierpagina.php');
  }
?>

Feedback voor de gebruiker

Als je als gebruiker een fout hebt gemaakt bij het invullen van het formulier is het handig dat je weet wat er mis is gegaan. Als programmeur kan je dat makkelijk maken voor de gebruiker, door op de volgende dingen te letten:

Als je dan overal waar het misgaat ook nog bijhoudt in bijvoorbeeld een $errors-array, dan kan je dat makkelijk teruggeven aan de gebruiker.

Zie het volgende voorbeeld.

$fields = [
  'username',
  'password',
  'password_check'
];
$errors = [];

foreach ($fields as $field) {
  if (!empty($_POST[$field])) {
    // Doe iets met de data.
  } else {
    $errors[] = $field;
  }
}

if (count($errors) > 0) {
  foreach ($errors as $error) {
    $html .= "<p class=\"error\">{$error} geeft een fout.<p>";
  }
}

Database

In dit hoofdstuk gaan we een database ontsluiten in PHP. Als het goed is weet je hoe je een database kan gebruiken en wat het is.

Queries maken als gebruiker met een database

❗️ Let er op je onderstaande niet kunt doen als je de WTIS-omgeving (Docker) niet hebt geïnstalleerd. De voorbeelden gegeven staan ook in die omgeving, zodat je ze werkend kunt zien.

Om een website zoals de onze bruikbaar te maken moeten gebruikers bijvoorbeeld kunnen zoeken of een query kunnen doen. Met een query doen bedoelen we niet dat gebruikers zelf een SQL-query op de database kunnen afvuren, maar ze moeten wel data kunnen opvragen waar ze naar zoeken.

Bijvoorbeeld als een gebruiker op een site als IMDb naar ‘Bourne’ moeten ze ook ‘Bourne Identity’ of zoiets binnenkrijgen.

Dat betekent dat je jouw website moet gaan koppelen met de database.

Er zijn verschillende Relational Database Management Systems (RDBMS’en). Bekende spelers zijn Microsoft SQL Server, Oracle Database, PostgreSQL en MySQL (zie ook de Stack Overflow survey in 2020). Omdat wij bij de course DBSQL gebruik maken van SQL Server gebruiken we deze ook hier.

In de video’s zijn de voorbeelden met de RDBMS MySQL, hoewel dit een productieklare database is (ook wel gebruikt door Spotify, Booking.com etc.) gebruiken wij Microsoft SQL Server omdat we dit toch al gebruiken in DBSQL en ook gebruikt wordt door veel grote bedrijven (Rolls Royce, Stack Overflow).

PDO en RDBMS’en

PDO (PHP Data Objects) is een PHP library (uitbreiding) dat ervoor zorgt dat de databasefuncties voor alle RDBMS’en hetzelfde zijn en werken. De systemen hebben wel eigen drivers nodig (software die de communicatie met apparaten en software regelt). Ook de connection string, een soort URL om verbinding te leggen met een database, verschilt. Als de verbinding er is, zijn de opdrachten voor alle RDBMS’en hetzelfde. Het is mogelijk een applicatie aan een ander databasesysteem te koppelen zonder al te veel in de PHP-broncode te veranderen.

Als de database met haar tabellen er is, kunnen we vanuit PHP een verbinding leggen.

$verbinding = new PDO('sqlsrv:Server=database_server;Database=muziekschool;ConnectionPooling=0',
  'mijnGebruiker',
  'ditIsOnveiligWachtwoord");

Hier komt een heleboel bij kijken. PDO is een class en dat heeft te maken met object-geörienteerd programmeren. Dat betekent dat we een speciale syntaxis gebruiken.

  • Met new PDO() wordt een nieuw verbindingsobject aangemaakt. Op dit object kunnen dan verschillende acties worden uitgevoerd.
  • Door de connectiestring te starten met sqlsrv geven we aan wat voor databaseprotocol we gaan gebruiken (SQL Server van Microsoft).
  • Bij Server=..... noemen we de databaseserverlocatie. Als de database op dezelfde computer draait, kan dit ook met localhost of (local) worden aangeduid. In dit voorbeeld wordt een server op naam aangeroepen. Bij ons draaien zowel de PHP webserver als de database in hun eigen Docker container en heet de DB service database_server, dus gebruik database_server.
  • Bij Database=..... hoort de naam van de database waar we mee willen werken. In de voorbeelden van deze reader is dat de muziekschool-database (dus Database=muziekschool)
  • Daarna komen, gescheiden door komma’s, de user en het wachtwoord. Bij een simpele installatie van SQL Server heb je de user sa, met een wachtwoord dat je zelf hebt ingesteld. Dit is alleen erg onveilig. Op een productiesite bij een provider is dit vaak een ander verhaal, omdat er dan vaak credentials worden gegenereerd.

Iets netter is het definieren met variabelen (code staat in bestand db_connectie.php in de WTIS-omgeving):

<?php
// defined in file 'variables.env'
$db_user    = 'sa';          // db user
$db_password = 'abc123!@#';  // password db user

$db_host = 'database_server'; // de database server
$db_name = 'muziekschool';    // naam van database

// Het 'ssl certificate' wordt altijd geaccepteerd (niet overnemen op productie, daar altijd "TrustServerCertificate=0"!!!)
$verbinding = new PDO("sqlsrv:Server=$db_host;Database=$db_name;ConnectionPooling=0;TrustServerCertificate=1",
  $db_user,
  $db_password);

Nou zul je in een echte ontwikkelomgeving gebruik maken van omgevingsvariabelen of externe koppelingen voor de database.

In de [ontwikkelomgeving](https://github.com/hanaim-webtech/wt-is-beroepsproduct-env/) die wij gebruiken ziet de verbinding er uit zoals in de file [applicatie/db_connectie.php](https://github.com/hanaim-webtech/wt-is-beroepsproduct-env/blob/master/applicatie/db_connectie.php):

```php
<?php

// defined in 'variables.env'
$db_host = 'database_server'; // de database server
$db_name = 'muziekschool';    // naam van database

// defined in sql-script 'movies.sql'
$db_user    = 'sa';          // db user
$db_password = 'abc123!@#';  // wachtwoord db user

// Het 'ssl certificate' wordt altijd geaccepteerd (niet overnemen op productie, verder dan altijd "TrustServerCertificate=1"!!!)
$verbinding = new PDO('sqlsrv:Server=' . $db_host . ';Database=' . $db_name . ';ConnectionPooling=0;TrustServerCertificate=1', $db_user, $db_password);

// Bewaar het wachtwoord niet langer onnodig in het geheugen van PHP.
unset($db_password);

// Zorg ervoor dat eventuele fouttoestanden ook echt als fouten (exceptions) gesignaleerd worden door PHP.
$verbinding->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Functie om in andere files toegang te krijgen tot de verbinding.
function maakVerbinding() {
  global $verbinding;
  return $verbinding;
}
?>

Bovenstaande code is zoals wij het in onze WTIS-omgeving gaan gebruiken. Zoals je ziet wordt het SA account gebruikt, een onveilig wachtwoord én de connectie is onbeveiligd (TrustServerCertificate=1). Voor ons is dat niet erg, wij werken lokaal op onze machine waar de firewall het verkeer van buitenaf blokkeert. Ga je een dergelijke omgeving voor een echte webomgeving opzetten (productie), zul je deze connectie-string en server-omgeving veiliger moeten maken. Studenten die het ISM-profiel gaan volgen zullen hier veel over leren in de hoofdfase.

Gegevens tonen met één PHP-bestand

Over de code hierboven:

  • $verbinding is een PDO-object.
  • Dit object heeft functies (methoden) om databasehandelingen uit te voeren.
  • Een voorbeeld is $verbinding->query($sqlquery) waarmee je een query op de database uitvoert.
  • De cryptische regel $verbinding->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); zorgt ervoor dat niet alleen PHP-fouten maar ook query- of databasefouten worden getoond.

De code staat opgeslagen in het bestand db_connectie.php en dat bestand gaan we vervolgens op verschillende plekken gebruiken met behulp van require_once.

In een ander bestand, componist-aantalstukken.php, gaan we nu de query uitvoeren op de database.

Startpunt

Gebruik het volgende bestand als startpunt.

<?php
require_once 'db_connectie.php';

// 1. Ophalen van de data
// ..... TODO .....

// 2. Renderen van de data
// ..... TODO .....

?>
<!DOCTYPE html>
<html lang="nl">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Componisten stukken</title>
</head>
<body>
  <h1>Componisten met aantal geschreven stukken</h1>
  <!-- 3. Weergeven van de data -->
  <!--      .... TODO .....     -->
</body>
</html>

Zoals je ziet worden er drie dingen van elkaar gescheiden:

  1. het ophalen van de data (uit de database),
  2. het renderen van de data (het mooi weergeven in html)
  3. het weergeven van de data (op de pagina zelf)

In eerste instantie gaan we deze scheiding van ophalen, renderen en weergeven in één bestand aanbrengen. Later volgt een korte uitleg hoe je dit verder kunt opdelen naar bestanden, zodat je slimme functies op meerdere pagina's kunt gebruiken.

In dit voorbeeld maken we een overzicht van alle componisten en aantal stukken dat die componist geschreven heeft. Dergelijke queries heb je leren maken in het vak Databases, de kennis uit dat vak gaan we hier nu toepassen.

Ophalen van de data

Als eerste stap wordt de verbinding met de database gelegd en de data voor de pagina opgevraagd. Die eerste stap ziet er zo uit:

// 1. Ophalen van de data
// maak verbinding met de database (zie db_connection.php)
$db = maakVerbinding();

// haal alle componisten op en tel het aantal stukken
$query = 'select c.componistId as id, c.naam as naam, count(S.stuknr) as aantal
          from Componist C left outer join Stuk S on C.componistId = S.componistId
          group by C.componistId, C.naam
          order by naam';
// voer de query uit op de database
$data = $db->query($query);

Wat gebeurt hier:

  1. De verbinding met de database wordt gemaakt met maakVerbinding() en opgeslagen in de variabele $db.
  2. Dan wordt query samengesteld en opgeslagen in een variabele $query.
  3. Daarna wordt de query uitgevoerd op de database met $db->query($query) en het resultaat van de query opgeslagen in $data.

Als deze code is uitgevoerd, heeft de variable $data alle records die de query oplevert. In SQL Management Studio kun je de query ook uitvoeren en zo controleren of de query juist is.

Renderen van de data

In de tweede stap wordt de data netjes in HTML uitgewerkt, het zogenaamde renderen. Die HTML kan dan weer in de pagina zelf worden opgenomen. Eerst de code, daaronder een korte beschrijving van wat er gebeurt.

// 2. Renderen van de data
// Begin van de "table"
$componisten_table = '<table>';
// De "table heads"
$componisten_table = $componisten_table . '<tr><th>Id</th><th>Naam</th><th>Aantal stukken</th></tr>';

// Elke rij als een "table row"
foreach($data as $rij) {
  $id = $rij['id'];
  $naam = $rij['naam'];
  $aantal = $rij['aantal'];

  $componisten_table = $componisten_table . "<tr><td>$id</td><td>$naam</td><td>$aantal</td></tr>";
}

// Eind van de "table"
$componisten_table = $componisten_table . "</table>";

Wat gebeurt er:

  1. Eerst wordt er een variabele $componisten_table gemaakt en gevuld <table> (zodat het een tabel in HTML wordt).
  2. Daarna worden de koppen van de kolommen toegevoegd. De query selecteert drie waardes (id, naam en aantal), voor elke waarde een <th>...</th>.
  3. Dan wordt elke rij één voor één opgehaald met foreach($data as $rij), de variabele $rij bevat steeds een associatieve array met de waardes uit het record.
  4. In de foreach-loop worden alle velden uit een record opgehaald, bijv. $naam = $rij['naam']; waarbij de waarde uit kolom 'naam' in de variabele $naam wordt gezet.
  5. Aan het eind van de loop worden alle waardes in een row gezet en toegevoegd aan de variabele $componisten_table.

Weergeven van de data

In de vorige stap, renderen van de data, is de data in HTML gezet. In ons voorbeeld is de componistendata in een HTML-table gezet. Deze HTML wordt op de juiste plek in de pagina gezet. Dat kan als volgt (om het wat overzichtelijk te houden is alleen de <body> opgenomen):

<body>
  <h1>Componisten met aantal geschreven stukken</h1>
  <!-- 3. Weergeven van de data -->
  <?= $componisten_table ?>
</body>

Wat gebeurt hier:

  1. De eerdere aangemaakte en gevulde variabele $componisten_table wordt hier in de HTML geplakt.
  2. De syntax <?= $componisten_table ?> is gelijk aan <?php echo($componisten_table) ?>, alleen iets korter.

In dit voorbeeld wordt er maar één data-element gebruikt in de pagina (de gegevens over componisten). In complexere websites, zoals je zelf gaat maken, zullen er meerdere data-elementen zijn. Probeer zoveel mogelijk de logica (stap 1 en 2) bovenaan de pagina te doen. En waar de HTML komt te staan, zo min mogelijk code, het liefst alleen <?= $var ?> of <?php echo($var) ?>. Hanteer de vuistregel in het PHP-deel (bovenaan in het bestand) variabelen vullen, in het HTML-deel (onderste stuk) alleen variabelen uitlezen.

Volledige code

componist-aantalstukken.php

<?php
require_once 'db_connectie.php';

// 1. Ophalen van de data
// maak verbinding met de database (zie db_connection.php)
$db = maakVerbinding();

// haal alle componisten op en tel het aantal stukken
$query = 'select c.componistId as id, c.naam as naam, count(S.stuknr) as aantal
          from Componist C left outer join Stuk S on C.componistId = S.componistId
          group by C.componistId, C.naam
          order by naam';
// voer de query uit op de database
$data = $db->query($query);

// 2. Renderen van de data
// Begin van de "table"
$componisten_table = '<table>';
// De "table heads"
$componisten_table = $componisten_table . '<tr><th>Id</th><th>Naam</th><th>Aantal stukken</th></tr>';

// Elke rij als een "table row"
foreach($data as $rij) {
  $id = $rij['id'];
  $naam = $rij['naam'];
  $aantal = $rij['aantal'];
  $componisten_table = $componisten_table . "<tr><td>$id</td><td>$naam</td><td>$aantal</td></tr>";
}

// Eind van de "table"
$componisten_table = $componisten_table . "</table>";

?>
<!DOCTYPE html>
<html lang="nl">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Componisten stukken</title>
</head>
<body>
  <h1>Componisten met aantal geschreven stukken</h1>
  <!-- 3. Weergeven van de data -->
  <?= $componisten_table ?>
</body>
</html>

Dit bestand vind je ook terug in je WTIS-omgeving. In de browser ziet het er nu niet zo mooi uit, met een beetje CSS-styling kun je mooier maken.

Gegevens tonen in drie-lagenstructuur

Hoewel het erg overzichtelijk is om alle code voor het ophalen, verwerken en tonen van data in één PHP-pagina te zetten, loop je al vrij snel tegen dubbele code aan. Dezelfde data-elementen gebruik je vaak op verschillende pagina's en dan is het juist handig die stukken code op één plek te zetten. Hieronder gaan we een eenvoudig voorbeeld uitwerken naar een drie-lagenstructuur m.b.v. functies verdeeld over verschillende bestanden. Dit lijkt hier erg omslachtig, maar een dergelijke opzet biedt meer mogelijkheden tot hergebruik van elementen én maakt het ook mogelijk om eenvoudiger testdata te gebruiken.

Startpunt

Voor de drie lagen maken we drie verschillende bestanden. Dit voorbeeld is erg klein, bij grotere websites is het gebruikelijk om meer opdeling te maken (functies verdelen over meerdere bestanden).

De bestanden:

  1. data_functies.php bevat functies om gegevens op te halen uit bijv. een database.
  2. view_functies.php bevat functies om HTML-code te maken met de data
  3. niveaus.php de uiteindelijk pagina die de complete HTML samenstelt

In de WTIS-omgeving zijn deze bestanden (de volledige uitwerking) terug te vinden. Om de bestanden overzichtelijk te houden, zijn de twee bestanden data_functies.php en view_functies.php in de folder includes geplaatst. Als je deze bestanden wil gebruiken, moet je bij include of require het pad erbij geven dus zoiets als require_once './includes/data_functies.php';.

Algemene structuur

Globaal gezien ziet de code per bestand er als volgt uit:

  • data_functies.php

    <?php
    require_once 'db_connectie.php';
    
    function haalAlleNiveausOp() {
      // query uitvoeren
      // return data
    }
    ?>
    
  • view_functies.php

    <?php
    function niveausNaarHtmlTable($data) {
        // Verwerk de data naar HTML
        // geef de HTML terug
    }
    ?>
    
  • niveaus.php

    <?php
    // 1. Ophalen van de data
    $niveaus = haalAlleNiveausOp();
    
    // 2. Renderen van de data
    $niveausHtml = niveausNaarHtmlTable($niveaus);
    ?>
    <html
    <body>
      <!-- 3. Weergeven van de data -->
      <?= $niveausHtml ?>
    </body>
    </html>
    

Ophalen van de data

De functie haalAlleNiveausOp() uit het bestand data_functies.php maakt verbinding met de database, selecteert alle rijen en geeft dat terug. Wat eerder in stap 1 gebeurde, is nu verplaatst naar een functie.

data_functies.php

<?php
require_once 'db_connectie.php';

function haalAlleNiveausOp() {
    // maak verbinding met de database (zie db_connection.php)
    $db = maakVerbinding();

    // haal niveaus op (code en omschijving)
    $query = 'select niveaucode, omschrijving
              from Niveau';
    // voer de query uit op de database
    $data = $db->query($query);
    // Geef de data terug
    return $data;
}
?>

De code spreekt grotendeels voor zich, de werking is gelijk aan de eerder besproken code waar alles in één pagina verwerkt wordt (zie Ophalen van de data).

Renderen van de data

Het omzetten van de data naar HTML wordt in de functie niveausNaarHtmlTable($data) gedaan. Deze functie krijgt de gegevens binnen middels een parameter en gaat niet zelf de data ophalen. Hierdoor is de functie een stuk flexibeler, want nu kun je ook een andere set van gegevens meegeven (bijv. minder rijen door filtering toe te passen).

view_functies.php

<?php
function niveausNaarHtmlTable($data) {
    // Begin van de "table"
    $html = '<table>';
    // De "table heads"
    $html = $html . '<tr><th>Code</th><th>Omschrijving</th></tr>';

    // Elke rij als een "table row"
    foreach($data as $rij) {
        $niveaucode = $rij['niveaucode'];
        $omschrijving = $rij['omschrijving'];
        $html = $html . "<tr><td>$niveaucode</td><td>$omschrijving</td></tr>";
    }
    // Eind van de "table"
    $html = $html . "</table>";

    // Geef de HTML terug
    return $html;
}
?>

Ook hier is de werking van de code gelijk aan de eerder besproken code (zie Renderen van de data).

Weergeven van de code

De basisstructuur van de pagina die de uiteindelijke HTML samenstelt is nagenoeg identiek aan die eerder besproken is (zie Volledige code). Het grootste verschil is dat het PHP-deel van de pagina een stuk kleiner is.

<?php
require_once './includes/data_functies.php';
require_once './includes/view_functies.php';

// 1. Ophalen van de data
// Haal alle niveaus op (zie data_functies.php)
$niveaus = haalAlleNiveausOp();

// 2. Renderen van de data
// Maak de HTML-code (zie bestand view_functies.php)
$niveausHtml = niveausNaarHtmlTable($niveaus);

?>
<!DOCTYPE html>
<html lang="nl">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Niveaus</title>
</head>
<body>
  <h1>Alle niveaus</h1>
  <!-- 3. Weergeven van de data -->
  <?= $niveausHtml ?>
</body>
</html>

Je kunt zelfs de methoden direct in de HTML uitvoeren (let wel op dat je code leesbaar blijft, zodat je het later nog begrijpt en kan aanpassen).

<?php
require_once './includes/data_functies.php';
require_once './includes/view_functies.php';
?>
<!DOCTYPE html>
<html lang="nl">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Niveaus</title>
</head>
<body>
  <h1>Alle niveaus</h1>
  <?= niveausNaarHtmlTable(haalAlleNiveausOp()); ?>
</body>
</html>

Grote applicatie opdelen

Het voorbeeld (de muziekdatabase) in deze reader is vrij klein. In website-projecten in de praktijk zijn applicaties al vrij snel veel groter. Een opdeling in maar twee bestanden (./includes/data_functies.php en ./includes/view_functies.php) wordt dan ook onoverzichtelijk. Men deelt dan de functies op naar meerdere bestanden en/of folders. Zo zou je kunnnen kiezen voor een opdeling als ./includes/componist_data_functies.php, ./includes/muziekschool_data_functies.ph etc. dus per component een apart bestand. Een opdeling in folders zie je ook vaak, dus dat er per component een folder is met de bijbehorende bestand, bijv. ./includes/componist/data_functies.php etc. Zo krijgt elk component z'n eigen folder.

Speciale tekens uit de database verwerken

In de voorgaande voorbeelden zijn we er vanuit gegaan dat de teksten die uit de database komen geen speciale karakters bevatten (denk aan <, >, & etc.). De tekstvelden uit de database zou je eigenlijk altijd moeten checken op speciale tekens. PHP heeft daar een standaard functie voor, namelijk htmlspecialchars. Deze functie vervangt speciale tekens met de HTML-code die daarvoor is. Bijv de < wordt vervangen met &lt;.

In het bestand view_functies.php zou de cel 'omschrijving' een speciaal karakter kunnen bevatten (bijv. niveaucode D met omschrijving expert & te moeilijk).

De regel :

  $omschrijving = $rij['omschrijving'];

wordt dan:

  $omschrijving = htmlspecialchars( $rij['omschrijving'] );

Verbinding maken met Microsoft SQL management studio

Bij het vak Database heb je een eigen database geïnstalleerd waar je met MSSQL Management Studio verbinding maakt. De WTIS-omgeving met Docker bevat ook een MS SQL server. Hier kun je verbinding mee maken, maar dan moet je in Management Studio de juiste server aangeven. Bij het verbinden gebruik je de volgende instellingen:

  • Server name: localhost, 1434
  • Authentication: SQL Server Authentication
  • Login: SA
  • Password: abc123!@#

MS SQL server inloggen

Nadat je ingelogd bent heb je de beschikking over de Muziekdatabase en de database die je nodig hebt voor het beroepsproduct. Op deze databases kun je nu met Management Studio de queries testen. Probeer maar eens uit:

  1. Maak een 'New Query'

  2. Stel een query op en voer die uit

    use muziekschool
    go
    
    select stuknr, titel, speelduur
    from stuk
    where speelduur < 5.0
    
  3. Dat geeft de volgende mogelijke uitvoer:

    Query

Errors

Als je een error krijgt probeer de error dan goed te lezen. Hieronder staan drie scenario’s die vaak misgaan.

Helaas staan de errors altijd een beetje verstopt na de volgende statement:

Uncaught PDOException: SQLSTATE[42000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]

Dat geeft alleen aan waar de error vandaan komt. Dus uit de driver van SQL Server komt een error. En de foutmelding volgt dan daarna:

Cannot open database "Applicatisde" requested by the login. The login failed.

Foutieve databasenaam

In het volgende voorbeeld van een foutmelding is het enige wat misgegaan is dat de databasenaam verkeerd staat.

( ! ) Fatal error: Uncaught PDOException: SQLSTATE[42000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot open database "Applicatisde" requested by the login. The login failed. in /srv/webserver/applicatie/public/db_conn.php on line 8
( ! ) PDOException: SQLSTATE[42000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot open database "Applicatisde" requested by the login. The login failed. in /srv/webserver/applicatie/public/db_conn.php on line 8
Call Stack
#TimeMemoryFunctionLocation
10.0018405056{main}( ).../films.php:0
20.0028406856require_once( '/srv/webserver/applicatie/public/db_conn.php' ).../films.php:3
30.0028407248__construct ( ).../db_conn.php:8

Foutieve username of foutief wachtwoord

De volgende foutmelding zie je als je de verkeerde username hebt gebruikt of het wachtwoord niet goed hebt. ⚠️ Let op, je moet goed zoeken naar de belangrijkste fout: Login failed for user 'sharona'.

( ! ) Fatal error: Uncaught PDOException: SQLSTATE[28000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Login failed for user 'sharona'. in /srv/webserver/applicatie/public/db_conn.php on line 8
( ! ) PDOException: SQLSTATE[28000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Login failed for user 'sharona'. in /srv/webserver/applicatie/public/db_conn.php on line 8
Call Stack
#TimeMemoryFunctionLocation
10.0018405056{main}( ).../films.php:0
20.0028406848require_once( '/srv/webserver/applicatie/public/db_conn.php' ).../films.php:3
30.0028407240__construct ( ).../db_conn.php:8

Bij deze error is het handig om even in de log te gaan kijken van SSMS. Dan staat er vaak iets als:

Login failed for user 'applicatie'. Reason: Password did not match that for the login provided.

of

Login failed for user 'sharona'. Reason: Could not find a login matching the name provided.

Fout in de query

Als er iets misgaat in de query, dan geeft de query niet een object terug waar je ->fetch() op kan draaien. Noch kan je dan erdoorheen lopen met foreach.

Je ziet dan iets wat lijkt op het volgende:

( ! ) Warning: Invalid argument supplied for foreach() in /srv/webserver/applicatie/public/films.php on line 19
Call Stack
#TimeMemoryFunctionLocation
10.0017405056{main}( ).../films.php:0

Ga de query goed na of de tabellen en de data die je opvraagt kloppen. Je kan de query ook altijd eerst testen in SSMS.

Veilig gegevens verwerken

Inleiding

Webappbeveiliging (webapplicatiebeveiliging) is een deelgebied van informatiebeveiliging. Informatiebeveiliging gaat over mensen, die door technische handelingen informatie en software-systemen verstoren. Veiligheid onderscheidt zich van betrouwbaarheid of andere kwaliteitseigenschappen van software-oplossingen door de unieke rol van mensen. Het gaat om mensen die bewust software-systemen proberen te verstoren. Bij andere problemen over kwaliteitseigenschappen (bijvoorbeeld te hoge hardwaretemperatuur, neerslag, te weinig geheugen, slechte gebruikersvriendelijkheid, onverwachte crashes, etc.) is dat niet zo.

Webappbeveiliging heeft veel onderwerpen gemeen met gegevensbescherming, in spreektaal privacy genoemd. Als in je webapp tot mensen herleidbare informatie of informatie over mensen voorkomt of rondstroomt (persoonsgegevens), dan moet je over gegevensbescherming nadenken. Daarbij betrek je soms informatiebeveiliging, maar soms staat privacy daar los van.

📖 Security should not be so scary!

Een kwetsbaarheid is een ‘beveiligingsgat’ dat in theorie kan worden uitgebuit door een aanvaller met een aanval met (een) aanvalstechniek(en). De situatie waarin er een mogelijke aanvaller i.c.m. een kwetsbaarheid is, heet een dreiging. Die gevallen waarin de aanval echt gebeurd is en geslaagd, heten incidenten. Naar aanvallen die wel gebeurd zijn maar niet geslaagd, verwijs je nog gewoon met aanvallen. Het risico van een dreiging is de kans dát de dreiging een incident wordt, maal het gevolg áls de dreiging een incident wordt (m.a.w. de ernst).

Waarom je over webappbeveiliging leert

Webapps zijn een van de belangrijkste leveringsvormen van software aan gebruikers. Desktopsoftware en mobiele apps zijn ook belangrijk. Maar als er een desktop- of mobiele softwareoplossing voor bijvoorbeeld een streamingmediaproduct is, is er meestal ook een webapp. Andersom is dat minder vaak. Beveiligingsincidenten met webapps vormen in de praktijk de grootste risico’s.

Webappbeveiliging bestaat in deze course uit vier stappen:

  1. De onderdelen van de webapp in kaart brengen.
  2. De risico’s in kaart brengen.
  3. De maatregelen tegen die risico’s vastleggen.
  4. De maatregelen doorvoeren.

De risico’s in kaart brengen

Je wilt concreet weten wat voor dreigingen er zijn, om het risico ervan te bepalen. Hierbij kan je putten uit de ervaring van eerdere projecten waarin dezelfde onderdelen gebruikt werden. Daarvoor heb je in de vorige stap de onderdelen in kaart gebracht. Dan moet je nog wel bepalen op welke manieren die onderdelen aangevallen zouden kunnen worden. Vervolgens moet je nagaan welke aanvallers (wanneer) die dreiging mogelijk zouden omzetten in aanvallen. Zo bepaal je de kans en het gevolg van de dreiging, en daarmee het risico.

Aanvalstechnieken

Aanvalstechnieken zijn te categoriseren, bijvoorbeeld heel technisch en gedetailleerd of juist algemeen. Het begrip aanvalstechniek hangt samen met een kwetsbaarheid. Een vuistregel is dat er één aanvalstechniek voor meerdere kwetsbaarheden bestaat, omdat de kwetsbaarheden vaak op elkaar lijken. Je zoekt in je webapp dus naar kwetsbaarheden vanwege aanvalstechnieken, in plaats van andersom.

In je eigen webapp spelen bijvoorbeeld minstens drie algemene aanvalstechnieken. Gezaghebbende bronnen over webappbeveiliging komen o.a. van de organisatie OWASP. In hun OWASP Top 10 onderbouwen zij de tien aanvalstechnieken die over het algemeen de grootste risico’s vormen.

Kans

Natuurlijk is de kans op allerlei dreiging-risico’s, waarbij jouw webapp ongericht een doelwit is, zelden echt nul. Het ligt eraan wat voor aannames je doet. Tegen wie wil je beschermen? Tegen de NSA? Dan zijn alle theoretische dreigingen realistisch. Tegen je zusje? Dan waarschijnlijk niet (of je zusje werkt daar, en heeft een hekel aan je ...).

☠️ Er is een groot verschil dat de algemene kans op webapp-aanvallen veel hoger maakt dan veel leken beseffen: webapps zijn virtueel. Jouw webapp staat open voor een veel grotere groep aanvallers dan een fysiek beveiligd/bewaakt voorwerp. Hoe erg zou het zijn als jouw fietsslot door iedereen in de wereld tegelijk zou kunnen worden gekraakt? Ook zijn aanvallen mogelijk onzichtbaarder en sneller. Bijvoorbeeld, als het kraken niet een kwartier duurt met een betonschaar, maar 500 ms op een Chromebook van een jongetje van twaalf?

Naast de algemene kans, is er de specifieke kans dat mogelijke aanvallers jou nu willen aanvallen. Het is bekend dat allerlei landen in de wereld voortdurend aanvallen ondernemen. Maar dan meestal tegen waardevolle doelwitten, zoals topmensen. Vanuit gegevensbescherming bezien, liggen de risico’s weer anders. Stel jouw webapp is voor afspraakjes. Vanuit beveiliging bezien is die webapp niet bijzonder, want het gevolg van risico’s van dreigingen is beperkt (denk aan: er vallen geen doden, de maatschappij komt niet tot stilstand, er worden geen geldverliezen geleden, etc.). Maar vanuit gegevensbescherming kan het gevolg groot zijn. Veel mensen vinden het erger als hun liefdesleven op straat ligt dan als bijvoorbeeld hun etenbestelhistorie op straat ligt. Zelfs een land kan jouw afspraakjeswebapp willen aanvallen, bijvoorbeeld om affaires van topmensen te ontdekken, om die daarmee onder druk te kunnen zetten.

Besef ook dat de kans plotseling kan toenemen door overmacht. Toen bijvoorbeeld Donald Trump op Twitter actief werd en ook ineens veel bekender en invloedrijker werd, werd Twitter ook actiever aangevallen. ⚠️ Als je webapp eenmaal (uit)ontwikkeld is, blijkt het in de praktijk erg lastig en tijdrovend om ineens nog voldoende beveiligingsmaatregelen te door te voeren. Dan hangt de webapp al af van allerlei vrijheid of ongeregeldheid die je alsnog moet wegnemen.

Gevolg

Het technische gevolg van een dreiging kan bijvoorbeeld zijn dat data uitgelezen wordt. Meestal is data kunnen veranderen erger dan uitlezen. Maar als een aanvaller bijvoorbeeld een wachtwoord kan uitlezen, kan de aanvaller waarschijnlijk uiteindelijk ook data veranderen. Het gaat dus om het ernstigste wat uiteindelijk mogelijk is, los van andere dreigingen. Het kunnen uitvoeren van broncode betekent vaak zowel kunnen lezen als schrijven, en is dus ernstig. Als de aanvaller handelingen kan doen (of broncode kan uitvoeren) als een gebruiker met de hoogste rechten (bijvoorbeeld een beheerder) dan is dat ook ernstiger dan wanneer de aanvaller alleen iets kan doen binnen een normaal gebruikersaccount. Het maatschappelijke gevolg van een dreiging in DigiD is hoger dan van een (technisch vergelijkbaar) inlogsysteem voor, zeg, voorraadbeheer van cafés. In een onderbouwing van het gevolg(en) leg je dus ook uit wat voor soort gevolg jij bedoelt.

Documentatie: risicoanalyse en dreigingsmodel(len)

De risico’s van je webapp leg je vast in een risicoanalyse, vaak een document. Daarop aansluitend maak je één of meer dreigingsmodellen, vaak in diagramvorm. Op deze manier zijn de uitgangspunten van jouw webappbeveiliging duidelijk beschreven voor alle belanghebbenden.

Kans en gevolg, en daardoor het risico, kan je kwantitatief (met getallen) uitdrukken. Maar het is vaak (ook binnen deze course) al voldoende om een categorische ordening (bijvoorbeeld van ‘laag’, ‘middelmatig‘, ‘hoog’) te gebruiken. Zolang je ordening maar onderbouwd is.

Je risicoanalyserapport vormt de basis en de bron van informatie over je webappbeveiliging. Het bevat de risico's, een inventaris van de entiteiten die een rol spelen in beveiliging (zoals aanvallers, kwetsbaarheden, etc.), en tot slot de maatregelen die je neemt tegen het risico. Het rapport zal deels erg technisch zijn. Als de doelgroep breder is dan alleen deskundigen, zouden zulke ingewikkelde zaken daarin aanvullend samengevat kunnen worden.

In de volgende paragraaf alvast een voorbeeld van een risicobeschrijving.

R1: SQL-injectie vanuit views met logincomponent

Er in de PHP-broncode van het beroepsproduct géén gebruikgemaakt van prepared statements in het onderdeel dat loginformulieren verwerkt. De aanvaller is een cybercrimineel die de gegevens uit de gebruikerstabel wil kopiëren om er geld mee te verdienen. Aangezien de loginverwerkings-URL open moet staan voor iedere bezoeker, niet alleen gebruikers, kan hij de kwetsbaarheid makkelijk en ontraceerbaar uitbuiten.

Tabel 1: SQL-injectie.
Risico R1: SQL-injectie vanuit views met logincomponent.
Aanvalstechniek Broncodeinjectie: SQL.
Kans Hoog: staat op nummer één van de OWASP Top 10 (A1:2017-Injection, z.d.).
Gevolg Hoog: PHP-broncode die het SQL statement uitvoert heeft minstens de rechten om de hele gebruikerstabel uit te lezen.

Een doel van een dreigingsmodel kan zijn om de risico’s inzichtelijk te maken voor alle belanghebbenden, en in verband te brengen met concrete aanvallers, dreigingen, aanvallen en incidenten. Je baseert je in het model op de informatie uit het risicoanalyserapport. Het is prima om je dreigingsmodel pas af te ronden nadat je de maatregelen hebt opgeschreven. (Over maatregelen leer je meer in de volgende paragraaf.) ⚠️Deze documenten mogen dus niet in tegenspraak zijn. Verzeker dat door traceerbare codes te gebruiken voor alle verwijzingen naar dingen.

De maatregelen vastleggen

In je risicoanalyserapport moet je ook een hoofdstuk wijden aan welke maatregelen je neemt om elk van deze risico’s te beheersen, en het bedoelde effect daarvan. Het beheersen van het risico kan variëren van ‘geen maatregel’ (dwz. acceptatie van het risico) tot een hele lijst bestaande uit technische en organisatorische maatregelen. Het gaat erom dat je vastlegt en onderbouwt wat je doet, niet dat alle risico’s zonder meer perfect beheerst moeten lijken.

Technische maatregelen

Je hebt voor de onderdelen al beveiligingsadvies van de leverancier opgezocht. Daarin vind je meestal voldoende technische maatregelen voor een minimale beveiligingskwaliteit.

Hiernaast zijn er bronnen van derden over het beveiligen van specifieke onderdelen. Voor PHP zijn er bijvoorbeeld:

Het Nederlandse Centrum voor Informatiebeveiliging en Privacybescherming (CIP) ontwikkelt normen en adviezen hoe je webappbeveiliging dient te waarborgen, zeker als je zaken wilt doen met overheidsorganisaties. Je kan deze bron snel doorlezen om een idee te krijgen van goede beveiligingsvereisten. Je hoeft deze informatie niet uit je hoofd te leren, maar je kan hierin zoeken als je de informatie nodig hebt.

📖 Grip op Secure Software Development (SSD) Beveiligingseisen voor (web)applicaties

Organisatorische maatregelen: management en werkprocessen

Naast technische maatregelen nemen, is ook het management van webappbeveiliging erg belangrijk.

Het voorkomen van kwetsbaarheden

Voorkomen is beter dan genezen. Er zijn een hoop maatregelen die afspraken vragen binnen het ontwikkelteam en tussen de leverancier en opdrachtgever.

Het is belangrijk om alle redelijke beperkingen van het begin af aan in te stellen, zowel binnen het product als in de manier van samenwerken binnen het ontwikkelteam, zodat de kans dat nieuwe kwetsbaarheden worden toegevoegd tijdens ontwikkeling zo laag mogelijk is. Je kan deze bron snel doorlezen om een idee te krijgen van goede maatregelen die een organisatie kan nemen. Je hoeft deze informatie niet uit je hoofd te leren, maar je kan hierin zoeken als je de informatie nodig hebt.

📖 Grip op Agile Secure Software Development (SSD) - Agile Security Management

Het begrippenkader (Nederlands- en Engelstalig) van webappbeveiliging

Alle begrippen, dwz. concepten die niet naar een specifieke technologie verwijzen:

  • Aanval (attack)
  • Aanvaller, actor (threat agent, threat actor)
  • Aanvalsoppervlak (attack surface)
  • Afgestemde kwetsbaarheidsopenbaarmaking (AKO) (coordinated vulnerability disclosure (CVD))
  • Aanvalstechniek (middel, attack vector)
  • Dreiging (threat)
  • Dreigingsmodel (threat model)
  • Beschikbaarheid (availability)
  • Beveiligingslaag (isolation layer, security boundary)
  • Incident (exposure, breach, soms ook, minder correct, exploit)
  • Kwetsbaarheid (vulnerability, security weakness),
  • Maatregel (control)
  • Oorsprong (origin)
  • Persoonsgegeven (personal data, PII)
  • Veiligheid (security, niet: safety)

Voorbeelden van technische maatregelen met PHP

Webappbeveiliging is een heel eigen vakgebied. Als beginnende webontwikkelaar moeten we een paar basisregels ter verdediging kennen. Deze zijn:

  • Formulierinvoer screenen.
  • Wachtwoordbeleid en password hashing.
  • Voorkomen dat SQL-broncode vervuild raakt met aanvalsbroncode.

Invoer sanitizen

Een website wordt kwetsbaar zo gauw bezoekers iets kunnen invoeren en deze invoer op de webserver verwerkt wordt. Om te voorkomen dat iemand de webapp beschadigd door gevaarlijke broncode binnen te sluizen, kan je de invoer beperken en opschonen.

Aanpassingen hievoor in de HTML-broncode van formulieren geven geen garantie tegen misbruik.

Het beperken van de invoer via HTML-attributen zoals type, maxlength, required, min, max helpen de gebruiker om foutieve invoer te voorkomen. Maar alle broncode op de client, voor ons meestal de browser, is voor gebruikers toegankelijk en daarom ook te manipuleren door kwaadwillende gebruikers.

We moeten dus ook aan de serverkant maatregelen nemen om te kijken, of wat iemand heeft ingevuld, door de beugel kan. Wat wel of niet door de beugel kan is helemaal afhankelijk van welke informatie wij van de gebruiker nodig hebben.

  • Moet een veld ingevuld zijn?
  • Verwachten we pure tekst, zoals bijvoorbeeld bij een naam?
  • Verwachten we cijfers of getallen?
  • Mag er slechts uit een reeks vaste antwoorden worden gekozen?
  • Wat is een maximale lengte van de gevraagde informatie?
  • Mogen er speciale tekens tussen zitten?
  • Mag er broncode tussen zitten?

In het kader van deze cursus gaat het vooral om bewustwording. Toch hier een paar PHP functies die we kunnen gebruiken om de invoer te checken of kwaadaardige broncode onschadelijk te maken. De details van deze functies staan op de documentatie van PHP.net (klik op de volgende links).

  • empty() om te kijken of bijvoorbeeld een $_GET of $_POST-variabele leeg is.
  • is_numeric() om te kijken of iets een getal is.
  • strlen() om de lengte van de invoer te checken.
  • strtolower() om van alles kleine letters te maken (voorkomt mismatches).
  • htmlspecialchars() maakt er HTML-entities van gevoelige tekens en voorkomt dat tekst als broncode geïnterpreteerd kan worden.
  • strip_tags() om HTML-tags en hun attributen uit de invoer te strippen.

Password hashen

Vandaag de dag is het absoluut not done om wachtwoorden zomaar in een database op te slaan. Iedere beheerder zou op deze manier de wachtwoorden van gebruikers in kunnen zien en bij een lek liggen de inloggegevens op straat.

Een hash betekent dat een string volgens bepaalde regels verhaspeld wordt tot een hashwaarde, een soort unieke code. Door deze code overal te gebruiken in plaats van het wachtwoord zelf, wordt de kans dat onbevoegden het wachtwoord letterlijk kunnen aflezen kleiner. Deze regels zijn in algoritmes vastgelegd. Het PASSWORD_DEFAULT-algoritme bijvoorbeeld produceert een string van 60 tekens.

password_hash($password, PASSWORD_DEFAULT)

Om te kijken of een ingevoerd wachtwoord overeenkomt met het gehashde wachtwoord gebruiken we de functie:

password_verify($password, $versleuteldpassword)

Een hash van het woordje: test

$2y$10$PURzDKzhHPssBFBz7sfSUOUls8LeU4b01owEeRzV9oHfD2TvdZ3dq

Om een hash te maken en uit te lezen uit de database:


  $wachtwoord = $_POST['wachtwoord'];
  $hash = password_hash($wachtwoord, PASSWORD_DEFAULT);
  // hash opslaan in database, niet wachtwoord.

  // user verifieren.

  // wachtwoord ophalen uit database
  if (password_verify($ingevoerd_wachtwoord, $query['wachtwoord'])) {
    // login met sessie
  }

Prepared statements gebruiken

Prepared statements zijn primair in het leven geroepen om database queries zo efficient en optimaal mogelijk uit te voeren. De statement kan nu meerdere keren uitgevoerd worden zonder dat de Database de SQL-statement nog een keer moet interpreteren.

Prepared statements zijn ook goed bestend tegen SQL injection.

SQL injection is een groot beveiligingsrisico voor websites. Gegevens die een gebruiker invoert worden meestal in een database verwerkt. En deze database kan gehackt worden door in een invoerveld kwaadaardige broncode toe te voegen.

Stel je een klein formulier voor waar je een product_id kan invoeren om dit product uit de productentabel te verwijderen.


Product verwijderen

*Voorbeeldformulier*

Aan de serverkant wordt dit formulier met de volgende broncode verwerkt:

$id = $_POST['id'];
$verbinding->query("DELETE FROM producten where id = $id");

Als nu iemand in het vakje bij ID invult: 105 OR 1 = 1 komt in de SQL-broncode te staan.

DELETE FROM producten where id = 105 OR 1 = 1 --

Dit betekent dat uit de tabel alle producten verwijderd worden omdat 'where 1 = 1' altijd waar is.

Dit is slechts een simpel voorbeeld en niet erg voor de hand liggend, maar laat zien hoe SQL Injection werkt. Prepared statements zijn er om dit te voorkomen.

Prepared statements in PHP

Bij PDO hebben we de mogelijkheid met prepared statements te werken. Een voorbeeld met daarna uitleg:

$niveaucode = $_POST['niveaucode'];
$omschrijving = $_POST['omschrijving'];

// Een string met speciale karakters.
$sql = "INSERT INTO Niveau (niveaucode, omschrijving) VALUES (:niveaucode, :omschrijving)";

// Wordt hier in `prepare()` gegooid
$query = $verbinding->prepare($sql);

// Nu kan je de waardes er aan toewijzen met `->execute()`.
// Door de placeholders is de volgorde NIET van belang
$query->execute([':niveaucode' => $niveaucode, ':omschrijving' => $omschrijving]);
  • Hier wordt de query in twee opdrachten verdeeld: in prepare() en execute().
  • Bij prepare() uitgevoerd op de databasehandler $verbinding wordt een SQL-querystring aangemaakt met op de plaatsen, waar gebruikersinvoer verwerkt wordt, placeholders (:niveaucode, :omschrijving). Dit geeft aan dat er op die plaatsen telkens één string mag worden ingevuld.
  • Bij execute() van het object dat eerst met prepare() is aangemaakt wordt nu de voorbereide SQL-broncode uitgevoerd en wel met waardes die aan de placeholders gekoppeld worden. Dat koppelen gebeurt in de vorm van een associatieve array.
  • Het is ook mogelijk met de methode bindParam() te werken om waardes aan plaatshouders te koppelen.

Als alternatief kunnen ook vraagtekens als plaatshouders gebruikt worden. Bij execute() telt dan de volgorde van de array-elementen.

$niveaucode = $_POST['niveaucode'];
$omschrijving = $_POST['omschrijving'];

// Een string met speciale karakters.
$sql = "INSERT INTO Niveau (niveaucode, omschrijving) VALUES (?, ?)";

// Wordt hier in `prepare()` gegooid
$query = $verbinding->prepare($sql);

// Nu kan je de waardes er aan toewijzen met `->execute()`.
// Door de placeholders is de volgorde WEL van belang!
$query->execute([$niveaucode, $omschrijving]);

👩‍🎓 Extra: Bekijk het advies van een beveiligingsexpert

Vind je webappbeveiliging door een technische bril érg interessant? Dan mag je als extraatje ook naar deze uitleg kijken van ‘de dikste yogainstructeur op de planeet’ 😁.

🎬 The OWASP Top Ten Proactive Controls - Jim Manico

Vind je ook organisatieprocessen interessant? Kies dan voor het profiel Infrastructure & Security Management (ISM). 😎

Netwerken

1 Netwerken

Het internet gebruiken we dagelijks. Dit doen we veelal zonder stil te staan bij hoe het internet werkt. Sterker nog de meeste mensen kunnen prima gebruik maken van het internet zonder dat ze weten hoe het werkt. Het internet zou ook een stuk minder succesvol zijn als dat wel zou moeten. Echter, als HBO-ICT'er willen we begrijpen hoe het internet werkt. Door te begrijpen wat er gebeurt als iemand navigeert naar een website, een whatsappje stuurt of online een game speelt krijg je ook inzage in hoe je zelf een dergelijke applicatie maakt of hoe je een applicatie veiliger maakt.

Daarnaast is het internet een prachtig voorbeeld van hoe we door het scheiden van verantwoordelijkheden een gigantisch groot probleem -de communicatie van biljoenen computers- kunnen oplossen. Let op: met computers bedoelen we bij HBO-ICT niet enkel desktop computers en laptops, maar natuurlijk alles wat een computer bevat zoals mobieltjes, robots, magnetrons, koelkasten, auto's, horloges, ziekenhuisapparatuur, etc.

1.1 Verschillende type netwerken

Het internet is een gigantisch groot netwerk van computers. Om te begrijpen het internet haar taak doet is het verstandig om eerst te kijken naar een kleiner netwerk, zoals het netwerk dat we in ons huis hebben. Een thuisnetwerk noemen we een Local Area Network (LAN). Veel uitdagingen die het internet heeft, heeft een LAN ook. Zoals: hoe weet een computer waar hij een bericht heen moet sturen om te zorgen dat het bij de juiste computer terecht komt?

Voordat we die vraag kunnen beantwoorden kijken we eerste naar hoe we het beste de computers in onze LAN op elkaar kunnen aansluiten om een netwerk te vormen. Dit doen we veelal met netwerkkabels (meestal UTP-kabels genoemd) of via Wi-Fi. Er zijn verschillende manieren van het aansluiten van computers aan elkaar om een netwerk te vormen. Zo kunnen we bijvoorbeeld alle computers met elkaar vinden met 1 lange kabel met aftakkingen of juist elke computer met elke andere computer verbinden. Bij het kiezen van hoe we de computers aansluiten zijn er twee factoren die een rol spelen:

  • Snelheid Over een kabel kan altijd een beperkte hoeveelheid data tegelijkertijd verstuurd worden. En we willen graag dat we zoveel mogelijk data tegelijkertijd verstuurd kan worden. Twee computers kunnen niet tegelijkertijd over dezelfde kabel communiceren. Het bericht van de ene computer wordt dan verstoord door het bericht van de andere computer. Als twee computers elkaars berichten verstoren dan noemen we dat een botsing (of in het engels collision). Computers kunnen wel om de beurt gebruik maken van dezelfde kabel.
  • Kabels Het liefst hebben we zo min mogelijk kabels. Netwerkkabels zijn duur. Daarbij willen we niet elke keer als we een nieuwe computer aansluiten 20 nieuwe kabels moeten aanleggen.

Grofweg hebben we de volgende vier opties voor het verbinden van onze computers: bus, star, ring en mesh. Het onderstaande filmpje legt de verschillende opties uit en benoemd de voor- en nadelen.

De meeste LAN's zijn aangesloten als een star-netwerk met in het midden een hub. Het enige wat een hub doet is alle kabels die er op zijn aangesloten met elkaar verbinden. Als een computer een bericht stuurt naar een hub dan stuurt de hub het vervolgens over alle andere aangesloten kabels. Hub is een engels woord dat naaf betekent. Denk hierbij aan de naaf van een fietswiel waar alle spaken in het midden van het fietswiel mee verbonden zijn.

Het onderstaande filmpje vertelt over de hardware die nodig is om een netwerk te maken. Sommige begrippen worden onder het filmpje ook in de tekst uitgelegd.

1.2 Nullen (0) en enen (1)

Om goed te begrijpen hoe het internet, of in het algemeen computers werken, is het belangrijk om te realiseren dat deze technology alleen werkt omdat we heel veel afspraken hebben gemaakt over hoe ze moeten werken. Deze afspraken noemen we protocollen.

Dit is nodig omdat over een internetkabel enkel stroom kan worden gestuurd. Op de kabel staat wel stroom of er staan geen stroom op. We kunnen lang naar de kabel kijken (of de stroomschokken voelen), maar we kunnen niet zien (of voelen) of de stroom die er wel of niet op staat de letter 'G' betekent of het getal 42. Die betekenis komt er pas door de afspraken die we hebben gemaakt.

Dit is niet enkel bij computers. Bij het gebruik van een (elektrische) telegraaf kan men niet, zonder afspraken, horen aan het geluid welke letter het is. Een elektrische telegraaf maakt daarom meestal gebruik van het protocol morsecode. Interessant genoeg kan men ook een ander protocol gebruiken zonder de telegraaf aan te passen. Het enige wat men moet doen is zorgen dat de persoon die het bericht ontvangt weet wat het protocol is. Dit geldt ook voor computers.

Het geluid van morsecode op een elektrische telegraaf:

Een morsesleutel voor het verzenden morsecode via een electrische telegraaf

Bij morse code kijken we naar lang geluid en kort geluid. Bij computers kijken we of er wel stroom of geen stroom is. In de informatica spreken we meestal over 1 (wel stroom) en 0 (geen stroom). Een plek waar een 1 of een 0 kan staan noemen we een bit. Voor onze 1'en en 0'en geldt natuurlijk hetzelfde als voor morsecode. We kunnen uren kijken naar een reeks 1'en en 0'en zoals: 01100001, maar de betekenis is er pas met de afspraken die we hebben gemaakt. Sterker nog de reeks 01100001 kan voor een computer zowel de letter 'a' (ASCII) betekenen als het getal 97 (binair). Afhankelijk van wat het protocol is, wanneer de reeks binnenkomt of waar het is opgeslagen.

Bij de werking van netwerken zien we veel protocollen. Die definiëren bijvoorbeeld in welke volgorde informatie wordt verstuurd, zodat we weten dat de 28ste bit bijvoorbeeld onderdeel is van een letter en de 421ste bit onderdeel is van het adres van een computer. Maar er zijn ook protocollen die definiëren hoe berichten terecht komen bij de juiste computer. Bijna elke sectie in dit hoofdstuk gaat over een protocol dat nodig is om het internet werkend te krijgen.

1.3 MAC-adressen, hubs en switches

Als een computer een bericht stuurt op een sternetwerk met een hub, dan wordt het bericht (ook wel Ethernet frame of frame genoemd) naar alle computers op het netwerk gestuurd. Ook naar computers voor wie het bericht niet bedoeld is. Computers kunnen dan berichten gaan verwerken die niet voor hen bedoelt zijn. Dit kunnen we oplossen met MAC-adressen.

Elke computer die moet worden aangesloten op een netwerk heeft een netwerkkaart nodig. Deze netwerkkaart heeft een uniek MAC-adres (Media Access Control-adres). Een MAC-adres bestaat uit een reeks letters en getallen. Elke fabrikant van netwerkkaarten begint zijn/haar MAC-adres met een eigen code en houdt vervolgens bij welke MAC-adressen ze al hebben toegekend aan computers. In theorie kan een enkele computer dus meerdere MAC-adressen hebben.

Ondanks dat MAC-adres het woord 'adres' bevat is het beter om MAC-adressen niet te zien als een adres (zoals van je huis), maar eerder als een identificatienummer zoals je Burger Service Nummer die je krijgt van de overheid. Aan een MAC-adres kan men namelijk niet zien waar de computer zich bevind in het netwerk. Dit in tegenstelling tot het IP-adres, dat we behandelen in een latere sectie van dit hoofdstuk. Een IP-adres kan je wel meer zien als het adres van je huis.

Door aan een bericht het MAC-adres van de bestemde computer toe te voegen weten computers met een ander MAC-adres dat als ze het bericht ontvangen ze het kunnen negeren. Een bericht, vanaf nu frame genoemd, heeft daarom de volgende opbouw:

De onderdelen van een frame. Waarbij de frame bestaat uit:

  • Pre-amble Een reeks 1'en en 0'en om te zorgen dat de verzender en de ontvanger gesynchroniseerd zijn en alle bits goed worden ontvangen
  • SFD De Start Frame Delimiter geeft aan dat de pre-amble is afgelopen en hierna echte informatie volgt
  • Destination MAC-address Het MAC-adres van de computer voor wie de frame bedoeld is
  • Source MAC-address Het MAC-adres van de computer dat het frame verstuurt. Dit geeft de ontvangende computer ook de mogelijkheid om een bericht terug te sturen
  • EtherType Het Ethertype veld wordt voor verschillende zaken gebruikt, zoals het aangeven hoe groot de payload is. We gaan hier deze cursus niet op in
  • Payload Het daadwerkelijke bericht. Er is een maximale grootte voor de payload. Een bericht, bijvoorbeeld het kopiëren van een bestand, wordt dus meestal verstuurd via meerdere frames, waarbij de ontvangende computer de payloads van de verschillende frames weer achter elkaar plakt
  • FCS De Frame Check Sequence bevat een waarde die gebruikt kan worden om te controleren of een bericht wel goed is ontvangen

De MAC-adressen kunnen ons helpen met het sneller maken van ons netwerk. Zoals eerder gezegd, stuurt een hub elk bericht dat hij ontvangt door naar alle andere kabels die op de hub zijn aangesloten. Over al die kabels kan vervolgens geen andere communicatie plaatsvinden. Dit kan natuurlijk slimmer. Daarom sluiten we meestal geen hub aan in het midden van een sternetwerk, maar een switch.

Een switch weet van elke aangesloten kabel welke MAC-adres er bij hoort. Bij het ontvangen van een frame stuurt de switch het niet naar alle aangesloten kabels, maar kijkt naar de 'Destination MAC-address' en stuurt het alleen over de kabel die bij dat MAC-adres hoort. Dit betekent dat een switch zelf ook een computer is. Deze heeft maar één taak: het moet de frames kunnen ontvangen en beslissen over welke kabel deze moeten worden verstuurd. We noemen een dergelijke computer een embedded systeem. Krijgt een switch een frame met een voor de switch onbekend MAC-adres, dan stuurt de switch de frame wel over alle kabels.

Het volgende filmpje legt uitgebreider uit het verzenden van een frame gaat met behulp van MAC-adressen, hubs en switches.:

Wil je opzoeken welke MAC-adres(sen) je computer heeft? Op de website vpngids MAC-adres opzoeken vind je hoe je dit kan doen. De uitleg hoe dit moet op MacOS volgt na die van Windows.

2 TCP/IP-protocol

Om communicatie over netwerken, en dus het internet, te laten werken hebben we naast de hardware (zoals kabels, hub en switches) ook protocollen nodig. protocollen zijn afspraken waar iedere computer zich aan moet houden wil het mee kunnen doen aan de communicatie op een netwerk. Als een verzendende computer zich hier bijvoorbeeld niet aan houdt, dan weet de ontvangende computer niet wat de 1'jes en 0'jes die het ontvangt betekenen. We zijn in dit hoofdstuk al twee protocollen tegen gekomen. De opdeling van de inhoud van een frame is een protocol. Maar ook de werking van een switch is een protocol. Immers, als een switch zijn taak anders uitvoert zal het netwerk niet kunnen communiceren.

Er zijn heel veel andere protocollen die verschillende zaken regelen rond de communicatie tussen computers. In de volgende sectie behandelen we de belangrijkste protocollen die samen het TCP/IP-protocol vormen. Ondanks dat er 'protocol' staat en niet 'protocollen' is het TCP/IP-protocol eigenlijk een groep van protocollen die het internet gebruikt om te functioneren.

2.1 IP-adressen

Zoals we in een eerdere sectie hebben gelezen maakt een MAC-adres het mogelijk om met switches gerichter berichten te sturen over een netwerk. Als de computer met de 'Destination MAC-address' met een kabel aan de switch vast zit stuurt de switch de frame enkel over die kabel in plaats van over alle aangesloten kabels. Dit scheelt veel verkeer op de kabels. Echter bij een groot netwerk zoals het internet, zijn er heel veel MAC-adressen die een switch niet kent. In het geval van het internet zullen dat biljoenen onbekende MAC-adressen per switch zijn waarvan de frames steeds over alle aangesloten kabels worden verstuurd.

Het probleem met MAC-adressen is dat we er niet aan kunnen zien waar in het netwerk de computer zich bevindt. De switch kan alleen maar de juiste kabel kiezen, doordat het zelf een tabel bijhoudt waarin staat welke kabel hoort bij welk MAC-adres. Herinner je dat ondanks het woord 'adres' in de naam, een MAC-adres een identificatienummer is zoals je Burger Service Nummer.

Als oplossing zijn IP-adressen bedacht. Deze geven wel informatie over waar een computer zich bevindt in het netwerk. Hierbij hoeft een computer bij het doorsturen niet te weten waar exact in het netwerk de computer met het IP-adres zich bevindt, als de data maar over een kabel gestuurd wordt die in de richting gaat van de juiste computer. Denk hierbij aan adressen zoals wij die ook gebruiken. Om naar 'Ruiterberglaan 26 Arnhem' te gaan is het vanuit Nijmegen voldoende om te weten dat we een weg naar het noorden moeten nemen. We hebben dan genoeg aan de informatie uit het deel met de plaatsnaam en hoeven niet exact te weten waar de Ruitenberglaan 26 is.

Een IP-adres bestaat uit 32 bits (32 1'en en 0'en), maar om het leesbaarder te houden voor mensen vertalen we meestal elke 8 bit naar een getal en scheiden deze 4 getallen met een punt. Een voorbeeld van een IP-adres is: 142.250.179.196. Met 32 bits kunnen 4.294.967.296 (==2^32) verschillende IP-adressen gemaakt worden. Omdat er inmiddels steeds meer computers op het internet aangesloten zijn beginnen de IP-adressen op te raken. Als oplossing is het internet nu aan het overstappen naar IP-adressen die bestaan uit 128 bits. De huidige IP-adressen die bestaan uit 32 bits noemen we IPv4. Die nieuwe IP-adressen van 128 bits noemen we IPv6. IPv6 schrijven we meestal in de hexadecimale notatie (zie voor meer informatie: hexadecimale notatie) per 32 bit gescheiden met dubbele punt. Een voorbeeld van een IPv6-adres is: 2607:f8b0:4000:806:200e.

2.2 Routers

Als je een internetabonnement hebt krijg je van je provider één IP-adres. Al je computers op je thuisnetwerk hebben dus op het internet hetzelfde IP-adres. Dit kan je uitproberen door op verschillende computers naar https://www.watismijnipadres.nl te gaan. Echter op je thuisnetwerk (LAN) hebben de computers wel een eigen IP-adres. Dit kan je zien door een terminal op te starten (voor Windows / voor MacOS en daar ipconfig (Windows) of ifconfig (MacOS) in te vullen. Je ziet dan je verschillende netwerkkaarten en de bijbehorende IP-adressen.

De verbinding van je thuisnetwerk met het internet wordt geregeld door de router. Dit apparaat kan verschillende netwerken met elkaar verbinden en zorgt voor de omzetting van de IP-adressen van de verschillende netwerken. Zo kunnen alle computers van een thuisnetwerk toch verbinden met het internet, ondanks dat er maar één internet IP-adres is voor het thuisnetwerk. Verschillende netwerken maken dus door middel van routers een groot netwerk. Een kleiner deel van een netwerk, zoals je thuisnetwerk een kleiner deel is van het internet, noemen we een subnet.

De router kan ook op basis van een IP-adres een frame de juiste richting op sturen, zonder dat hij het MAC-adres of de exacte locatie van het IP-adres van de eindbestemming kent. Hier dankt de router ook zijn naam aan. De router routeert de frames de goede richting op. Echter tegenwoordig kunnen slimme switches dit ook. Switches kunnen echter niet de verbinding zijn tussen twee verschillende netwerken zoals de router dat doet.

In een eerdere sectie werd al gezegd dat switches ook computers zijn. Routers zijn ook computers. Routers (en slimme switches) hebben om hun functie goed te uitvoeren ook zelf een MAC-adres en een IP-adres.

Adsl connections
Een router.

2.3 Transmission Control Protocol (TCP)-packet

Om frames de juiste richting op te sturen hebben we nog steeds MAC-adressen nodig. De IP-adressen zorgen dat globaal de juiste richting kan worden bepaald, maar MAC-adressen bepalen lokaal over welke kabel de frame moet worden verstuurd.

De frames die we versturen hebben dus nog steeds de 'Destination MAC-address' en de de 'Source MAC-address' nodig (zie 1.3 MAC-adressen, hubs en switches). We moeten dus op een andere plek in de frame de IP-adressen kwijt. Hiervoor gebruiken we het Transmission Control Protocol (TCP). Deze definieert een packet waarin de IP-adressen staan, maar ook andere informatie zodat we de packets makkelijker kunnen versturen en kunnen controleren of ze goed zijn aangekomen. De inhoud van packet staat beschreven in de onderstaande tabel.

Bit offset0–34–78–1516–31
0Source address
32Destination address
64ZerosProtocolTCP length
96Source portDestination port
128Sequence number
160Acknowledgement number
192Data offsetReservedFlagsWindow
224ChecksumUrgent pointer
256Options (optional)
256/288+payload

We zien dat een TCP-packet geen veld biedt voor MAC-adressen. Dat komt omdat we nog steeds frames blijven gebruiken. In plaats van het bericht dat we willen versturen direct in de payload van de frame te plaatsen, plaatsen we ons bericht nu in de payload van het TCP-packet en plaatsen we het TCP-packet in de payload van de frame.

2.4 Routing van packets

Hoe het TCP/IP-protocol samen met routers en switches werkt wordt uitgeleged in het onderstaande filmpje:

2.5 Domain Name System (DNS)

Iets wat tot nu toe nog niet hebben bekeken is hoe het kan dat het internet werkt met IP-adressen, maar wij als gebruikers die niet hoeven in te vullen om te verbinden met een website. Dit komt omdat er gelukkig ook een protocol is dat voor ons leesbare website-adressen, zoals www.tostiszijnnetmensen.nl vertaalt naar IP-adressen. Hoe dit precies werkt wordt uitgelegd in de onderstaande twee filmpjes:

(optioneel) Een nog diepere uitleg over DNS kan je vinden op hello-dns.

2.6 Dynamic Host Configuration Protocol (DHCP)

Het TCP/IP-protocol bestaat uit meerdere protocollen. Een ander belangrijk protocol is Dynamic Host Configuration Protocol (DHCP). Dit protocol wijst automatisch IP-adressen toe aan de verschillende computers op het netwerk. Een netwerk kan ook werken zonder DHCP door elke computer een vast IP-adres te geven, maar dat heeft nadelen. Het grootste nadeel is dat je een probleem hebt als twee computers hetzelfde vaste IP-adres gebruiken. Dit kan bijvoorbeeld ontstaan doordat iemand zijn laptop van zijn huis meeneemt en aansluit op jouw thuisnetwerk.

Het DHCP-protocol bestaat uit een DHCP-server en 4 type berichten die naar of door de DHCP-server worden verstuurd: Discover, Offer, Request en Acknowledge. Het onderstaande filmpje legt uit hoe DHCP werkt. De meeste moderne routers bevatten ook een DHCP-server.

2.7 HTTP Protocol

De communicatie tussen een webserver, de server waar de website op staat, en de webclient, meestal een browser, is ook vastgelegd in een protocol. Dit protocol heet het Hypertext Transfer Protocol (HTTP) protocol. In het HTTP-protocol is vastgelegd welke vragen een webclient aan een webserver kan stellen, welke antwoorden een webserver kan geven op deze vragen en in welke vorm de vragen en antwoorden verstuurd moeten worden. Bij het HTTP-protocol noemen we de vragen HTTP-requests en de antwoorden HTTP-responses.

Er zijn negen verschillende HTTP-requests. De meest gebruikte HTTP-requests zijn GET en POST. GET-requests gebruikt de webclient om alleen informatie van een webserver te vragen. In zowel GET-requests als POST-requests stuurt de webclient een URL mee. Voor een GET-request is dit de locatie van het bestand / de pagina die de webclient wil opvragen. Voor meer informatie over URL zie: wikipedia. POST-requests worden door de webclient gebruikt om informatie door een webserver te laten verwerken op basis van meegestuurde data. Denk hierbij aan het verwerken van informatie ingevuld in een HTTP-formulier. Bij zowel een GET- als de POST-request kan men data mee sturen. Bij de GET-request wordt dit dan meegestuurd als onderdeel van de URL. Bij de POST-request wordt het meegestuurd in een ander veld, waardoor de data niet zichtbaar is in de URL.

Op de HTTP-requests kan de webserver reageren met een HTTP-response. In het geval van een GET-request zal dit hopelijk het gevraagde bestande / de gevraagde pagina zijn. In het geval van een POST-request geeft de webserver aan wat het resultaat is van de verwerking. Een HTTP-response heeft een statuscode, het antwoord (bijvoorbeeld de opgevraagde website) en wat velden met extra informatie (metadata). De meeste voorkomende statuscodes zijn:

  • 200 OK – Het gevraagde document is succesvol opgevraagd.
  • 304 Not Modified – T.o.v. de versie in de cache is de pagina niet gewijzigd.
  • 400 Bad Request - De gebruiker heeft een fout gemaakt in het verzoek waardoor deze niet verwerkt kan worden.
  • 403 Forbidden – Het opgevraagde document mag niet opgevraagd worden.
  • 404 Not Found – Het opgevraagde document bestaat niet.
  • 405 Method Not Allowed – De gebruikte requestmethode is niet toegestaan.
  • 410 Gone – Het opgevraagde document heeft bestaan maar is niet meer beschikbaar. Vergelijkbaar met foutcode 404.
  • 451 Unavailable For Legal Reasons - een website niet kan worden weergegeven vanwege juridische redenen
  • 500 Internal Server Error – De webserver heeft de gevraagde actie niet kunnen uitvoeren.
  • 503 Service Temporarily Unavailable – De webserver is tijdelijk in onderhoud.

De berichten van het HTTP-protocol worden als leesbare tekst verstuurd. Servers waar een HTTP-response of -request langs komt kunnen dus meelezen. Om het versturen van berichten veiliger te maken zijn er andere protcollen die hierop aanvullen zoals het HTTPS-protocol (zie voor meer informatie: wikipedia).

2.8 Error checking (optioneel)

Als je benieuwd bent hoe een computer kan controleren of een ontvangen bericht wel correct is raden we de volgende twee filmpjes aan:

Ontwikkelomgevingen en de architectuur van webapps

1. Multi-tier-architectuur

De combinatie van database-server, webserver en client kunnen we als het onderstaande plaatje weergeven:

Multi-tier-architectuur

Figuur 1: Multi-tier-architectuur van webapps.

Deze type softwarearchitectuur noemen we een multi-tier-architectuur.

Multi-tier-architectuur betekent dat er meerdere lagen in de applicatie zijn die verschillende taken hebben. We onderscheiden vaak deze drie lagen:

  • de presentatielaag
  • de applicatie- of business-logic-laag
  • de persistentie- of datalaag

De applicatie die we gaan bouwen met WTIS maakt gebruik van de multi-tier-architectuur.

Lees Web Application Architecture: How the Web Works t/m ‘Web application architecture components and Three-Tier Architecture.

1.1. De presentatielaag

Deze laag beschrijft wat de gebruiker ziet en zal nagenoeg één-op-één overeenkomen met het beroepsproduct voor WTUX. Binnen dit beroepsproduct zie je al een verdeling van verantwoordelijkheden: HTML doet structuur en CSS de vormgeving. Het lagenmodel werkt soortgelijk.

De presentatielaag bestaat in veel applicaties uit HTML (mogelijk gerenderd door een andere taal zoals PHP, Python, Java of zoiets). Zelfs veel mobiele applicaties op je telefoon geven dingen weer via HTML. De meeste moderne websites hebben hier een hoop client-side broncode nog draaien met JavaScript. Maar dat valt buiten de scope van deze course.

1.2. De applicatie- of business-logic-laag

Deze laag beschrijft hoe de applicatie werkt. Bijvoorbeeld: als ik op een film klik wil ik dat de applicatie data over deze film gaat ophalen.

De applicatielaag is de connectie tussen de wat de gebruiker doet en hoe de database de data vormgeeft voor de gebruiker. Deze laag is waar de PHP-broncode die we gaan schrijven wordt uitgevoerd.

1.3. De persistentie- of datalaag

Deze laag beschrijft hoe de data opgeslagen wordt en welke data dat is. Het woord persistentie betekent in deze context dat iets opgeslagen is en blijft bestaan.

persistent (per·sis·tent): blijvend, volhardend

In ons geval is de persistentielaag de laag waar de databasesoftware Microsoft SQL Server. Maar dat kan voor andere applicaties ook dingen zijn als PostgreSQL, MySQL/MariaDB (zie ook de Stack Overflow survey in 2020).

2. Client, HTTP-server, RDBMS

2.1 Webservers

We zijn in deze lessenreeks begonnen met HTML en CSS, broncode die door een browser geïnterpreteerd (= vertaald) wordt. Een openbare website staat op een webserver. Zoals uit het hoofdstuk over netwerken is gebleken bestaat het internet uit een netwerk van meerdere computers die met elkaar verbonden zijn en verbinding maken door middel van ip-adressen. Een webserver is dus een andere machine die bestanden host en die te benaderen vallen.

Wij gebruiken achter de schermen de ‘built-in web server’ van PHP zelf. Vele andere websites gebruiken servers, zoals:

  • Apache
  • nginx
  • lighttpd

Er zijn nog talloze voorbeelden te noemen.

Het bijzondere van een webserver zoals Apache of nginx is dat ze in plaats van alleen bestanden ook software of programma's kunnen draaien. Deze webserver wijst dan in plaats van naar een bestand naar een ander proces of voert zelf een stuk code uit.

Als wij naar een URL gaan begrijpt de webserver voor ons welke file nu door de PHP-interpreter (vertaler) vertaald mag worden. Nu kunnen we dus een dynamische website en ook uitgebreide webapps bouwen. Een PHP-script wordt elke keer opnieuw uitgevoerd als je er naartoe navigeert met de browser.

Dus je ziet niet meer de broncode zoals dit bestand index.php:

<?php
echo "<h1>Hello World!</h1>";
?>

Maar het resultaat: Hello World

Of in broncode:

<h1>Hello World!</h1>

Hetzelfde resultaat zou je krijgen als je lokaal op je machine php zou aanroepen om het bestand index.php uit te voeren:

> php index.php
<h1>Hello World!</h1>

Om een webserver veilig te installeren en juist te laten reageren is nogal wat informatie voor nodig. In deze course is daar gewoonweg geen tijd voor om daar bij stil te staan. Vandaar dat we in deze course ervoor hebben gekozen om de built-in webserver van PHP te laten draaien in een container.

2.2 Databases

Maar we zijn er nog niet.

Bij de meeste toepassingen, zoals een webwinkel of een blog, halen we de gegevens, bijvoorbeeld producten, uit een database. In PHP kan je gebruik maken van een PHP-uitbreiding: PDO (PHP Data Objects) om verbinding te maken met de database. PDO zorgt ervoor dat het werken met databases uniformer en veiliger maakt. Stapsgewijs gaat dat zo:

  1. verbinding maken met een databaseserver
  2. inloggen
  3. query uitvoeren
  4. resultaten ophalen
  5. resultaten in PHP uitlezen

Veel databases spreken een dialect van SQL (Structured Query Language), een taal om gegevens en alles wat er mee te maken heeft, op te vragen, op te slaan, te wijzigen en eventueel ook weer te verwijderen.

WebTech: Implementatie & Security

Inhoudsopgave

PHP Les 01: Introductie PHP

Doelstelling:

  • Introductie WTIS
  • Introductie programmeertaal PHP
  • Client server
  • PHP-code (verschillen met Processing)
  • Gebruik van $_GET[xxx]

Voorbereiding (studenten)

Installeren ontwikkelomgeving

Voer uit Ontwikkelomgeving installeren

Bestudeer

  • Bestudeer uit de reader Introductie PHP
  • Bekijk video's van CodeCourse op OnderwijsOnline:
    • 01-CodeCouse-PHP Introduction.mp4
    • 02-CodeCourse-First PHP Script.mp4
    • 03-CodeCourse-Variables.mp4
    • 04-CodeCourse-Datatypes-Strings.mp4
    • 05- CodeCourse-Datatypes-Integers and Floats.mp4
    • 06-CodeCourse-Datatypes-Booleans.mp4

Opdrachten

  • Controleer of de webserver werkt

    1. Open met Visual Studio Code de map waar je de ontwikkelomgeving hebt geïnstalleerd (zie Installeren ontwikkelomgeving)
    2. Start de webserver op door onder in de terminal docker compose up in te tikken.
    3. Open in je webbrowser de standaardpagina met http://localhost:8080/.
    4. Als je de tekst Hallo WT'er, de webserver is online en PHP werkt. ziet staan, werkt je webserver. Bij problemen zie de installatiehandleiding (Installeren ontwikkelomgeving)
  • Opdracht Een eerste PHP-pagina:

    1. Maak in de map applicatie (van je ontwikkelomgeving) een nieuw PHP-bestand aan voorbeeld.php.

    2. Plak onderstaande code in dat bestand

      <?php
      $naam = 'Voornaam Achternaam';
      
      $vandaag = date_create('now');
      $datum = $vandaag->format('d-m-Y');
      
      ?>
      <!DOCTYPE html>
      <html lang="nl">
      <head>
          <meta charset="UTF-8">
          <title>PHP voorbeeld</title>
      </head>
      <body>
          Hallo <?= $naam ?>.<br>
          Het is vandaag <?= $datum ?>.
      </body>
      </html>
      
    3. Roep het voorbeeld aan (http://localhost:8080/voorbeeld.php). De output zou er zo uit kunnen zien (natuurlijk met de datum van vandaag):

          Hallo Voornaam Achternaam.
          Het is vandaag 28-03-2022.
      
    4. Vervang Voornaam Achternaam met je eigen naam en roep de pagina opnieuw aan.

    5. De maand wordt nu met een getal afgedrukt, verander dit zodat de maand als tekst wordt afgedrukt. Op de pagina https://www.php.net/manual/en/datetime.format.php staan mogelijkheden. Je hoeft geen rekening met taal te houden.

Lesprogramma

Inhoud van de les

  • Introductie op WTIS
  • Bespreken van huiswerk
  • Uitleg: Introductie op de webserver
  • Uitleg: PHP vs. Processing
  • Gegevens meesturen naar de server

Introductie op WTIS

De docent neemt de studiehandleiding door en laat zien hoe je dit vak kunt halen.

Gegevens meesturen naar de server

Gebruik de superglobal $_GET om de query-parameter voornaam als volgt uit te lezen --> $_GET['voornaam']

Zorg ervoor dat de tekst in PHP wordt ge"echood" en kijk wat er gebeurt als je in de browser navigeert naar: http://localhost:8080/hallo.php?voornaam=jorg

Opdracht: Hoe lang nog voor Sinterklaas?

Omdat je graag wilt weten hoe lang het nog duurt voordat het sinterklaas is maak je een pagina: hoelangnog.php die in de browser weergeeft:

Het duurt nog 251 dagen en 11 uur tot Sinterklaas.

Deze dagen en uren moeten natuurlijk dynamisch mee veranderen.

Tip: Bekijk de documentatie van de functie date-diff

Bonus: Formulier gebruiken voor "Hoe lang nog"

Maak een formulier waarin je de verschillende eigenschappen kan meegeven:

  • Welk event je op wacht (bijv. Sinterklaas)
  • Wanneer dit plaatsvindt (bijv. 05-12-202x)

Tip: gebruik in de form method="GET".

Huiswerk

  1. Datum en omschrijving opgeven via URL:

    In de les heb je de php-pagina hoelangnog.php gemaakt waarmee je kunt opvragen hoe lang het nog duurt tot het Sinterklaas is. Verander de pagina zo dat het mogelijk is om via de URL op te geven welke datum en welke gebeurtenis het is. Je geeft dus op de URL twee waarden mee: omschrijving en datum. Hieronder een paar voorbeelden (verander eventueel de datums naar aankomend jaar):

    • http://localhost:8080/hoelangnog.php?omschrijving=Sinterklaas&datum=2022-12-05

      Geeft op 28 maart 2022: Het duurt nog 251 dagen en 10 uur tot Sinterklaas

    • http://localhost:8080/hoelangnog.php?omschrijving=Nieuwjaar&datum=2023-01-01

      Geeft op 28 maart 2022: Het duurt nog 278 dagen en 10 uur tot Nieuwjaar

    • http://localhost:8080/hoelangnog.php?omschrijving=Koningsdag&datum=2022-04-27

      Geeft op 28 maart 2022: Het duurt nog 29 dagen en 10 uur tot Koningsdag

  2. Is het al weekend?

    Maak een PHP-pagina ishetalweekend.php die afdrukt of het al (bijna) weekend is.

    • Laat zien of het al (bijna) weekend is.

      Tip: https://www.php.net/manual/en/datetime.format.php met de format-string "w" krijg je het dagnummer terug.

      De pagina laat het volgende zien:

      Maandag, dinsdag:    'Nee, nog lang niet.'
      woensdag, donderdag: 'Nog even wachten.'
      vrijdag:             'Bijna!'
      zaterdag, zondag:    'Jaaaaa, het is weekend!'
      
  3. Doe de voorbereidingen van de volgende les.

PHP Les 02: Arrays

Doelstelling les:

  • Herhaling "wat zijn arrays"
  • Associatieve arrays
  • Introductie van functies

Voorbereiding (studenten)

  • Huiswerk vorige les natuurlijk
  • Bestudeer uit de reader:
  • Bekijk video's van CodeCourse op OnderwijsOnline:
    • 07-CodeCourse-Datatypes-Arrays-Basics.mp4
    • 08-CodeCourse-Datatypes-Arrays-Advanced.mp4
    • 15-CodeCourse-Functions-Basics.mp4
    • 16-CodeCourse-Functions-Scope.mp4

Opdracht 1: (~30 min.)

  1. Maak een array: $eenTMTien die gevuld is met de getallen 1 t/m 10.
  2. Maak een array: $zesTMVijftien die gevuld is met de getallen 6 t/m 15.
  3. Maak een array: $samengevoegd door de array uit onderdeel 1 en 2 samen te voegen (je krijgt dus een array met de getallen 1 t/m 15 en met duplicaten). Kijk of je eruit komt door array_merge te gebruiken.
  4. Maak een array: $film met daarin de volgende keys en vul deze array met voorbeeldwaarden:
    • titel;
    • jaar;
    • regisseur;
    • hoofdrolspelers.

Lesprogramma

  • Bespreken huiswerk
  • Uitleg over arrays

Arrays

Tijdens de les bespreken we arrays door middel van een:

  1. for-loop over een één-dimensionale array
  2. for-loop over een twee-dimensionale array (met foreach)
  3. associatieve array

Opdracht menukaart maken

We gaan een "dynamische" menukaart maken.

Die komt er ongeveer zo uit te zien:

Eten

Shoarma€ 6.95
Appels€ 10.95
Tabouleh€ 8.95
Hamburger€ 5.50

Drinken

Cola€ 2.00
Ayran€ 2.30
Fernandes€ 2.50
Bier€ 5.50

In plaats van HTML direct te gebruiken, gaan we PHP inzetten om dit te doen met behulp van associatieve arrays.

Stap 1: Zet onderstaande HTML om in php

<!DOCTYPE html>
<html lang="nl">
  <head>
    <meta charset="UTF-8" />
    <title>Restaurantmenu</title>
    <style>
      td:first-child {
        width: 8em;
      }
      td:nth-child(2) {
        font-style: italic;
        text-align: right;
        width: 4em;
      }
    </style>
  </head>
  <body>
    <h1>Menu</h1>

    <h2>Eten</h2>
    <table>
        <tr><td>Shoarma</td><td>&euro; 6.95</td></tr>
        <tr><td>Appels</td><td>&euro; 10.95</td></tr>
        <tr><td>Tabouleh</td><td>&euro; 8.95</td></tr>
        <tr><td>Hamburger</td><td>&euro; 5.50</td></tr>
    </table>

    <h2>Drinken</h2>
    <table>
        <tr><td>Cola</td><td>&euro; 2.00</td></tr>
        <tr><td>Ayran</td><td>&euro; 2.30</td></tr>
        <tr><td>Fernandes</td><td>&euro; 2.50</td></tr>
        <tr><td>Bier</td><td>&euro; 5.50</td></tr>
    </table>
  </body>
</html>

Stap 2: Samen een (geneste) associatieve array opbouwen

Nu gaan we samen een associatieve array maken (op het bord).

Stap 3: Schrijf nu code voor de kopjes

Probeer eerst het "eerste" niveau van de array te printen door de kopjes weer te geven (Eten en Drinken).

Voeg een extra kopje toe door de associatieve array aan te passen (bijvoorbeeld: Toetjes).

Intermezzo

Nu bespreken we eerst samen even de uitwerking tot dusver.

Wat valt je op aan de inhoud van de associatieve array? Wat kun je met een geneste array?

Stap 4: Schrijf nu de code voor de geneste onderdelen

Schrijf nu de code die de bijbehorende onderdelen bij de kopjes.

Dit is ook het huiswerk, met de toevoeging dat je de URL-parameters erbij moet kunnen gebruiken. Zie huiswerk

Bonus: Zet de getallen om in de "Nederlandse stijl"

In Nederland gebruiken we geen . om fracties op te schrijven maar een ,.

Computers doen dat anders. Je kunt de getallen opschrijven in de "locale" van Nederland door gebruik te maken van: number_format()

Huiswerk

Het huiswerk voor de volgende les.

  1. Maak menukaart volledig af zodat het menu volledig getoond wordt.

    http://localhost:8080/menukaart.php geeft:
    Menukaart

  2. Voeg een extra menu Toetjes toe aan de kaart met wat consumpties (breid dus de array uit).

    http://localhost:8080/menukaart.php geeft:
    Menukaart

  3. Een menukaart kan erg groot worden, het zou handig zijn als je ook een menuonderdeel kunt kiezen.

    http://localhost:8080/menukaart.php?soort=Drinken geeft alleen de drankjes.
    Menukaart

    En http://localhost:8080/menukaart.php?soort=Toetjes geeft alleen de toetjes.
    Menukaart

  4. Bekijk je code eens goed, kun je functies maken die het geheel duidelijker maken? Beschrijf die (naam en parameters) en implementeer deze dan.

  5. De voorbereiding van de volgende les.

PHP Les 03: Koppelen met een database

Doelstelling les

  • Gegevens uit een database kunnen halen en presenteren in een web-pagina
  • Eerste aanzet tot strakke scheiding ophalen data, verwerken data en input en presenteren van resultaten.

Voorbereiding (studenten)

  • Huiswerk vorige les natuurlijk
  • Bestudeer uit de reader hoofdstuk Database
  • Bekijk video's van CodeCourse op OnderwijsOnline:
    • 21-CodeCourse-PDO-Connecting.mp4
    • 22-CodeCourse-PDO-Getting results.mp4

Lesprogramma

Koppelen met de database

Na de uitleg over het verbinden met de database gaan we samen aan de slag met het opbouwen van query's en die weergeven in de browser.

In de ontwikkelomgeving van WTIS zit een database server gebouwd.

  1. Open Microsoft SQL Server Management Studio (SSMS) en maak verbinding met de database:

    • Server: localhost, 1434 (let op dat je het poortnummer correct meegeeft)
    • Login: sa
    • Wachtwoord: abc123!@# (staat in de root map in bestand variables.env)
  2. Voer de query uit en laat resultaat zien. We maken de queries eerst in SSMS voordat we de query uitwerken in PHP.

    SELECT titel
    FROM stuk
    

    titels

  3. Maak een PHP-pagina aan met de naam muziekstukken.php.

    • include het bestand db_connectie.php.
    • Kopieer de query uit management studio en verwerk die in de php-pagina, zodat alle titels getoond worden.
    • Selecteer nu meer gegevens en bouw een table op. select stuknr, titel, genrenaam, niveaucode from stuk.
  4. In de eerder query zit nu een niveaucode, terwijl het veel handiger is om daar de niveaucodeomschrijving te geven. Let op, er is een left outer join nodig, anders vallen stukken met niveau null weg.

    SELECT stuknr, titel, genrenaam, n.omschrijving
    FROM stuk s LEFT OUTER JOIN niveau n ON s.niveaucode = n.niveaucode
    
  5. Voeg nu zelf de naam van de componist toe aan de query en geef deze weer.

Huiswerk

  1. Maak een overzicht van alle componisten (naam en geboortedatum) met de muziekschool (naam en plaatsnaam) waar hij/zij werkzaam is.
    huiswerk 01

  2. Maak het overzicht netter:

    • Geboortedatum als dag maand jaar bijv. 05 Jan 1946
    • Muziekschool en plaatsnaam alleen als die gevuld is (anders leeg)
      huiswerk 02
  3. Voorbereiding volgende les

Netwerken, HTTP & Sessies

Doelstelling van de les:

  • Je herkent en onderscheid netwerkprotocollen. (We staan stil bij DNS, HTTP en TCP/IP)
  • Je kunt persistentie toepassen over meerdere pagina's met behulp van sessies

Voorbereiding

Om iets te begrijpen van wat het maken van interactieve en dynamische websites is het handig om een aantal dingen te weten te komen:

  • hoe het internet werkt;
  • hoe websites geserveerd worden;
  • hoe je verbinding legt met een webserver; en
  • hoe je een Request stuurt en een Response krijgt.

Bestuderen

Bestudeer aan de hand van de onderstaande vragenreeks het hoofdstuk Netwerken en de architectuur van webapps van de reader.

Beantwoord de volgende vragen

  1. Beschrijf in je eigen woorden wat een DNS-server doet?
  2. Bevat de default gateway een MAC-adres of een IP-adres?
  3. Wanneer wordt de default gateway gebruikt?
  4. Wat bevindt zich op de default gateway?

IP-adressen

Hoe je de onderstaande vragen kunt beantwoorden staat uitgelegd in hoofdstuk 2.8, kopje '2.2 Routers' van de reader.

  1. Welk IP-adres heb je op het internet?
  2. Welk IP-adres heb je in het netwerk waar je nu op zit (bijvoorbeeld je thuisnetwerk of Eduroam op de HAN)?
  3. Wat is het IP-adres van www.nu.nl? (vul hiervoor je in je terminal het command ping www.nu.nl in)
  4. Langs hoeveel routers moeten berichten gaan om bij www.nu.nl te komen? (vul hiervoor in de terminal het command tracert www.nu.nl (windows) of traceroute www.nu.nl (MAC) in)

Domain Name Server (DNS)

  1. Van welke DNS krijg je het IP-adres van www.han.nl? (Dit zoek je op door in de terminal het volgende in te vullen: nslookup www.han.nl)
  2. Van welke DNS krijg je het IP-adres van www.nasa.gov?
  3. Wat is je default domein name server? (Dit zoek je op door in de terminal het command nslookup te runnen.)
  4. Hoe kan het dat je het IP-adres van een website van een andere DNS krijgt dan je default DNS?
  5. Is het ook mogelijk dat je naar een website gaat en dan je computer niet het IP-adres hoeft op te vragen aan een DNS? Zo ja, waarom? Zo nee, waarom niet?

HTTP-protocol

  1. Als jij aan het surfen bent op het web, ben jij dan een client of een server?
  2. Wat is het voordeel van data versturen via een POST-request in plaats van een GET-request?
  3. Wat is het voordeel van data versturen via een GET-request in plaats van een POST-request?

Opdracht: Router

Voor deze opdracht ga je inloggen op de router van je thuisnetwerk. Op de hogeschool kan je wegens veiligheidsredenen geen toegang krijgen tot de router. Deze opdracht is dus niet op de hogeschool te maken. Het kan zijn dat je thuis ook geen toegang hebt tot je router, omdat je niet de de eigenaar bent van de router. Deze opdracht is daarom niet verplicht, maar zeker wel leerzaam.

Om in te loggen op je router moet je eerst weten op welk IP-adres binnen je thuisnetwerk de router zich bevindt. Het IP-adres kan je vinden door op het fysieke kastje van router te kijken. Staat het daar niet op google dan op de naam van je router en het woord IP-adres. Meestal geeft de eerste of de tweede zoekresultaat wel het antwoord.

Vul het IP-adres in je browser. Als je het goede adres hebt krijg je nu een inlogscherm. De default inloggevens staan op je router. Zo niet, is ook dit te googlen. Zijn de default inlogggevens aangepast, vraag dan of een huisgenoot het wachtwoord weet. In het uiterste geval biedt elke router de mogelijkheid om hem te resetten naar de fabrieksinstellingen. Hiermee worden de default inloggevens hersteld. Pas hier wel mee op! Ook alle instellingen gaan verloren, zoals de naam en het wachtwoord van je wifi. Deze moet je dan opnieuw instellen.

Vragen:

Als je bent ingelogd op je router beantwoord dan de volgende vragen:

  1. Wat is het IP-adres van je router?
  2. Hoeveel devices zijn er op dit moment verbonden met het netwerk?
  3. Lees het artikel: good and bad neighbors wifi-scanner/. Zit je wifi op de juiste channel?
  4. Wat is het IP-adres van de DNS-server die je router gebruikt?
  5. Bekijk de opties die de router nog meer heeft. Benoem een optie die te maken heeft met de veiligheid van je netwerk en leg in je eigen woorden uit wat het doet.

Lesprogramma: Netwerken

  • Bespreken huiswerk
  • Aan de slag met HTTP en sessies

Webservers, HTTP en Sessies

Aan de hand van de presentatie krijg je een introductie op HTTP en sessies.

👩🏻‍🏫 Samen inlog- en uitlogfunctionaliteit bouwen met sessies (~45 min.)

We hebben nu allemaal verschillende onderdelen geleerd die we nog moeten combineren:

  • een loginformulier in HTML;
  • een sessie;
  • if-statements om te kijken of data gevuld is;
  • if-statements om te kijken of het wachtwoord wel klopt.

We gaan de volgende stappen doorlopen.

  • Neem het onderstaande inlogformulier en sla dit op in een nieuw bestand: login.php.
  • Voeg sessies aan deze pagina toe met session_start().
  • Lees nu netjes het formulier uit door de $_POST superglobal te checken op de waardes.
  • Zorg er nu voor dat je kan inloggen door het formulier te posten en op basis daarvan de $_SESSION superglobal te vullen met relevante data zoals username
  • Als de session nu gevuld is zou de logged_in-variabele nu op true moeten staan en zou een welkomstbericht moeten worden weergegeven.
  • Maak nu een nieuw bestand logout.php waarin je de sessie destroyt en terugverwijst naar de login.php met header().
<?php
$logged_in = false;
if (isset($_SESSION['username'])) {
  $html = "<h1>Welcome {$_SESSION['username']}</h1>";
  $logged_in = true;
}
?>
<!DOCTYPE html>
<html lang="nl">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Testsessie</title>
  </head>
  <body>
    <?php
    if ($logged_in) {
      echo $html;
    }
    ?>
    <!-- TODO: ongeldige waarde voor `action`. -->
    <form action="" method="post">
      <input type="text" name="username">
      <input type="password" name="pass">
      <input type="submit" value="login">
    </form>
    <a href="logout.php">Log uit</a>
  </body>
</html>

Zo zou het eruit moeten zien:

Login

Logged in

👷🏼 Opdracht 2: Maak een uitlogknop (~30 min.)

  1. Maak een nieuwe pagina en plak de volgende broncode:

    <?php
    // Maak sessie beschikbaar.
    session_start();
    
    $html = '';
    $logged_in = false;
    
    // Voeg welkomstgroet toe.
    if (isset($_SESSION['logged_in']) && isset($_SESSION['username'])) {
      $html = "<h1>Welkom {$_SESSION['username']}</h1>";
      $logged_in = true;
    }
    ?>
    <!DOCTYPE html>
    <html lang="nl">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Inlogpagina</title>
    </head>
    <body>
      <?=$html?>
      <form action="login.php" method="post">
        <input type="submit" value="login">
      </form>
      <form action="logout.php" method="post">
        <input type="submit" value="logout">
      </form>
      <?php
      if ($logged_in) {
        require_once 'geheimen.html';
      }
      ?>
    </body>
    </html>
    
  2. Deze pagina heeft twee formulieren in HTML met een POST naar login.php en logout.php

  3. Hier wordt de gebruiker ook verwelkomd met haar eigen username als deze gebruiker ook volgens de sessie ingelogd is.

  4. Als iemand logged_in is, dan worden er ook ‘spannende geheimen’ getoond (uit een ander bestand, zie de require_once).

  5. Maak nu een login.php die:

    • een 'username' opslaat door een key toe te voegen met een waarde aan $_SESSION.
    • en een 'logged_in' key toe die true is.
    • Redirect de gebruiker vervolgens in login.php terug naar de vorige pagina met iets als header('Location: test_login.php');.
  6. In logout.php moet je de sessie nu vernietigen en ook weer terugverwijzen naar de homepage.

  7. Zorg er nog voor dat er een bestand geheimen.html is met hele spannende geheimen.

Het ziet er als het goed is zo uit:

'loginscreen'

Bovenstaand is het scherm dat je ziet als je nog niet bent ingelogd (of weer bent uitgelogd).

ingelogd

Afhankelijk van wat je in geheimen.html hebt gezet en wat de username is zie je iets wat lijkt op het bovenstaande als je ingelogd bent.

👩‍🎓 Extra opdracht: Feedback aan gebruiker over missende waarden

Het is handig is om goede feedback te geven als er iets migaat aan de gebruiker, zodat het duidelijk is wat er anders moet.

Download nu het registratieformulier in HTML/CSS om extra functionaliteit toe tevoegen.

Het registratieformulier staat eigenlijk helemaal klaar voor gebruik. Let goed op de method (post) en de action (register.php). Zorg ervoor dat dit dus op de goede plek uitkomt en wordt uitgelezen.

Als je het formulier verstuurt:

  1. Controleer dat je username geen getallen/numerieke waarden heeft. Onderstaande functies kunnen je helpen.
    1. is_numeric checkt of een variabele een numerieke waarde of string is (bijvoorbeeld: 1 of '1' geeft true),
    2. str_split geeft een string terug als een array van enkele karakters (bijvoorbeeld: 'Arnhem' geeft ['A', 'r', 'n', 'h', 'e', 'm']),
  2. Controleer of de wachtwoorden wel overeenkomen.
  3. Geef dan geschikte errors terug aan de gebruiker.

PHP Les 04: Veilig gegevens verwerken

Doelstelling les:

  • Bewustwording SQL injection

  • Kennismaken met prepared statements

  • Filterfunctie op basis van dropdown

Voorbereiding (studenten)

  • Huiswerk vorige les natuurlijk
  • Lees uit de reader paragraaf Prepared statements gebruiken van hoofdstuk Veilig gegevens verwerken
  • Bekijk video's van CodeCourse op OnderwijsOnline:
    • 23-CodeCourse-PDO-Fetch types.mp4
    • 24-CodeCourse-PDO-Fetching all results.mp4
    • 25-CodeCourse-PDO-Prepared Statements.mp4

Lesplan

  • Huiswerk bespreken
    • Componisten weergeven
    • Prepared statements

Queries dynamisch maken

In deze les gaan we muziekstukken (uit de tabel Stuk) filteren op basis van genre (niet niveaucode want die is vaak NULL).

In de vorige les zijn we al bezig gegaan met data uit de database halen als de query vast staat. Nu gaan we bezig met een aanpassing op de query op basis van een gebruikers actie.

Stap 1: URL-filter toepassen

We gaan gebruik van van query-parameters (bijv. http://localhost:8080/muziekstukken.php?genrenaam=jazz)

Daarvoor gebruiken we de $_GET superglobal.

  1. Maak eerst de query in SQL Managament Studio.

  2. Plak query in php-code

    ❗️ Let op, in de PHP moet je de single qoutes ' escapen met \'. Het is ook mogelijk om dubbele qoutes " te gebruiken, maar dan zet je de deur weer open voor het per ongeluk uitvullen van variabelen (en dus voor sql injection).

  3. Vervang jazz met pop en toon effect.

  4. Nu gaan we de query-parameters gebruiken zoals:

    pop :

    http://localhost:8080/muziekstukken.php?genrenaam=pop

    jazz :

    http://localhost:8080/muziekstukken.php?genrenaam=jazz

    Dit kunnen we doen door direct met $_GET de query aan te passen, maar daar zitten nogal grote gevolgen aan. Want dan zetten we de deur open voor SQL-injection

Intermezzo: SQL-injection

De docent zal laten zien en vertellen wat de gevaren van SQL-injection zijn.

Stap 2: Query-parameters maar veilig met prepared statements

Nu gaan we prepared statements gebruiken. Zie ook het onderdeel in de H7. Veilig gegevens verwerken: Prepared Statements Gebruiken

Kort gezegd zijn prepared statements:

  • voorbereidde queries, m.a.w. een query met placeholders
  • Vaker te gebruiken, maar vooral een wapen tegen code injection
Recept voor prepared statements:
  1. Maak query met placeholders:

    SELECT .... FROM .... WHERE kolom = :kolom (en :kolom is de placeholder)

  2. Laat query door database voorbereiden: $data = $db->prepare($sql)

  3. Laat query uitvoeren met waardes voor de placeholders: $data->execute(..waarden..)

  4. Haal data op (fetch)

Stap 3: Dropdown menu met alle genres

Bovenaan de pagina gaan we nu een dropdown menu maken met behulp van:

  • het <form> element met een
  • <select> die verschillende <option>-elementen.

Denk goed na over welke "action" en "method" het formulier moet hebben.

Aan de hand van de <option> die gekozen is kun je nu de query draaien.

De query SELECT genrenaam FROM Genre voer je nu uit met behulp van de data die het formulier teruggeeft.

Bij de eerste aanroep krijg je een warning, die eerst even negeren. Speel wat met de pagina (selecteer af en toe een ander genre).

Stap 4: Warning weghalen met isset

Poets de warning weg door te controleren of genrenaam is meegegeven, anders een default waarde:

if(isset($_GET['genrenaam']))
{
    $genrenaam = $_GET['genrenaam'];
}
else
{
    $genrenaam = 'jazz'; // of een ander genre als default
}

Stap 5: Gegevens uit dropdown uit de database halen

De dropdown is nu hardcoded gevuld, maar die gegevens staan ook in de database. Als er nu een nieuw genre bij komt, dan moet je ook de html aanpassen. Dat gaan we dus met een query doen.

  1. Voer eerst in SQL Management de query INSERT INTO Genre(genrenaam) VALUES ('blues') uit en laat zien dat die blues dus niet in de dropdown terecht komt.

  2. Maak een functie getGenreSelectBox die de select box genereert.

  3. Voeg nu nog een genre toe in SSMS:

    INSERT INTO Genre(genrenaam)
    VALUES ('tranentrekker')
    

Nu kun je met het formulier dus stukken vinden op basis van de verschillende genres.

Stap 6: Voeg een nieuw muziekstuk toe

Voeg een nieuw stuk toe, bijvoorbeeld:

INSERT INTO stuk (stuknr, componistId, titel, genrenaam, jaartal)  
VALUES (100, 10, 'Kneiterfalse', 'tranentrekker', 2021)`

Korte samenvatting

De docent zal het uiteindelijke formulier demonstreren en nog meer voorbeelden hiervan bespreken.

Welke voorbeelden kun je zelf bedenken?

Huiswerk

  1. (verplicht) Nieuwe pagina maken met alle stukken, filter maken op basis van componistid (in de dropdown de naam zichtbaar, componistid is value)

  2. (uitdaging, optioneel) in het formulier met de dropdown wordt de selectie niet bewaard, maar steeds op de eerste gezet. Een option als selected markeren doe je door selected op te nemen, bijv. <option value="klassiek" selected>klassiek</option>. Geef de functie getGenreSelectBox() een argument $selection wat het geselecteerde genre is. dus:

    function getGenreSelectBox($selection)
    {
        // Toevoegen: geef het geselecteerde genre `selected`
    
        $db = maakVerbinding();
        $sql = 'select genrenaam 
                from Genre';
        $data = $db->query($sql);
    
        $selectbox = '<select id="genrenaam" name="genrenaam">';
        foreach($data as $rij)
        {
            $genrenaam = $rij['genrenaam'];
            $selectbox .= "<option value=\"$genrenaam\">$genrenaam</option>";
        }
        $selectbox .= '</select>';
    
        return $selectbox;
    }
    
  3. Voorbereiding volgende les

PHP Les 5: Gegevens opslaan in een database I

Doelstelling les:

  • Veilig verwerken van gegevens via php middels prepared statements

Voorbereiding

  • Huiswerk vorige les natuurlijk
  • Bestudeer insert queries van het vak Databases
  • Bekijk video's van CodeCourse op OnderwijsOnline:
    • 17-CodeCourse-POST GET.mp4
    • 18-CodeCourse-Forms.mp4
    • 19-CodeCourse-Require Include.mp4

Lesprogramma

  • Bespreken huiswerk
    • Nieuwe pagina met filter
    • Uitdaging: selected toevoegen aan <option>
  • Uitleg over gegevens opslaan in een database

In deze les gaan we aan de slag met een aantal 'recepten' om data op te slaan in een database. We volgen elke keer dezelfde stappen om het makkelijk te maken wat je hier moet doen.

Code richtlijnen

  • input-velden krijgen als id en name de kolomnaam waar het naartoe moet.

  • alle input-velden (GET/POST) worden eerst overgezet naar variabelen waarbij de variabele de naam krijgt van de kolom.

  • alle bewerkingen vinden plaats op de variabelen, de GET/POST wordt nergens meer gebruikt dan op één plek om uit te lezen.

  • opbouw van elke pagina is:

    1. 'overhevelen' van input-inputvariabelen (GET/POST) naar lokale variabelen

    2. Controles op de geldigheid van input-variabelen

    3. bij fouten in controle, vul een $melding en druk die af

    4. bij geen fouten:

      • maak prepared statement waarbij de placeholders de naam van de kolom krijgen (:componistid)
      • laat db gegevens verwerken
      • bij gelukt, wis alle lokale variabelen
      • bij niet gelukt, geef melding

Stap 1: Eenvoudig versturen van gegevens naar een database

We gebruiken een los HTML-document met een formulier die:

  • alle waarden kan versturen
  • het verzonden formulier uitleest
  • de data kan opslaan in de database
  • weergeeft of het gelukt is

We gebruiken onderstaande documenten als beginpunt:

Nu maken we in SQL management studio een insert query om componisten toe te voegen. Als je deze overneemt in PHP moet je er even opletten dat je de ' escaped met: \'.

INSERT INTO componist (componistId, naam, geboortedatum, schoolId) 
VALUES (11, 'Meneer Muzikant', '1992-09-08', 1);

❗️ Let op, componistid wordt automatisch gegenereerd door de database. Dit zelf doen is een security threat.

Alle variabelen vullen vanuit het formulier

We gaan nu eerst alle variabelen invullen door deze uit te lezen uit het verzonden formulier met behulp van $_POST.

// 4 kolommen, dus ook 4 variabelen
$componistId    = $_POST['componistId'];
$naam           = $_POST['naam'];
$geboortedatum  = $_POST['geboortedatum'];
$schoolId       = $_POST['schoolId'];

Voeg de include statement voor de database connectie toe

require_once 'db_connectie.php';

Insert query overnemen en waardes vervangen

We nemen de insert query nu over zoals deze in SSMS zou kunnen werken:

// Insert query
$sql = 'INSERT INTO componist (componistId, naam, geboortedatum, schoolId)
        VALUES (44, \'Meneer Muzikant\', \'1992-09-08\', 1);'; 

Maak hier nu eerst een prepared statement van zoals in de vorige les. Vul daarna de data in een array. En voer dan de query uit.

Als je de query uitvoert krijg je terug of de query geslaagd is.

$succes = $query->execute($data_array);

if ($succes) {
    $melding = 'Gegevens zijn opgeslagen in de database.';
}
else {
    $melding = 'Er ging iets fout bij het opslaan.';
}   

Voeg nu een componist toe met het formulier en laat met select * from componist zien dat die er inderdaad in zit.

Stap 2: Formulier versturen waar niet alles ingevuld is

De velden geboortedatum en schoolid zijn niet verplicht, maar als deze niet ingevuld zijn, krijg je nu een foutmelding. Dat kun je zelf zien, of de docent laat het even zien:

Vul alles in, maar niet het schoolid. Nu krijg je een foutmelding.

Null in niet verplichte velden

Als geboortedatum of schoolid niet is ingevuld, dan zou je dus null moeten gebruiken.

Bijvoorbeeld:

INSERT INTO componist (componistId, naam, geboortedatum, schoolId) 
VALUES (44, 'Meneer Muzikant', NULL, NULL);

Check dus of de variabele leeg is en zet deze dan op null. Bijvoorbeeld:

// Controleer niet verplichte velden
if (empty($geboortedatum)) {
    $geboortedatum = null;
}

Los dit nu ook op voor `schoolid en voeg nu een componist toe met het formulier zonder deze (één van) deze velden in te vullen.

En laat zien met SELECT * FROM componist dat deze ook opgeslagen is.

Stap 3: HTML en PHP samenvoegen

Het form en verwerken daarvan hebben we nu apart in een HTML- en PHP-bestand, dat gaan we samenvoegen.

  1. plak het <form > deel in de HTML van de PHP pagina.
  2. maak het action attribuut van het form leeg
  3. voeg controle op klikken toe aan de php (if(isset($_POST['opslaan'])))

Stap 4: Controles op velden (verplichte velden)

Een HTML-form kan met required velden verplicht maken, maar dat kun je eenvoudig omzeilen. Dus verplichte velden moeten ook in de php gecontroleerd worden. Daarnaast kun je extra controles toevoegen:

// Controleer velden op geldigheid
// componist id (not null, numeric)
if (empty($componistId)) {
    $fouten[] = 'componistId is verplicht om in te vullen.';
}

if (!is_numeric($componistId)) {
    $fouten[] = 'componistId moet een numerieke waarde zijn.';
}

// Naam (not null, text)
if (empty($naam)) {
    $fouten[] = 'naam is verplicht om in te vullen.';
}

if (count($fouten) > 0) {
    // Fouten: maak een melding
    $melding = '<ul class="error">';

    foreach($fouten as $fout)
    {
        $melding .= '<li>'.$fout.'</li>';

    }
    $melding .= '</ul>';
} else
{
    // ....... opslaan in db
}

Huiswerk

a. Stap 1-4 in les 1: huiswerk maak een php-bestand waarin je nieuwe scholen kunt toevoegen.

b. stap 5-6 (7) in les 2: huiswerk, maak formulier voor scholen volledig (dus veldcontrole, input opschonen etc.)

PHP Les 6: Gegevens opslaan in een database II

Doelstelling les:

  • Veilig verwerken van gegevens via php middels prepared statements

Voorbereiding

  • Huiswerk vorige les natuurlijk

Lesprogramma

  • Bespreken huiswerk
  • Kort het "recept" uit vorige les herhalen.
  • Verder werken met foutmeldingen en invoer verwerken.

Stap 5: Waardes opslaan bij foutmeldingen

Bij fouten gaan ingevulde waardes verloren, dat erg frusterend, dus values invullen in formulier.

  1. maak een fout (bijv. geen componistid) en laat zien dat na submit je een foutmelding krijgt, maar dat ondertussen de andere velden weer leeg zijn. Dat is hier niet zo erg, maar als je een groot formulier hebt ingevuld, dan zou het fijn zijn als eerder ingevulde gegevens bewaard worden.

  2. breid de inputs uit met een value-attribute waar je de bijbehorende variabele weer in zet. Hier komt dus die naamgeving (kolomnaam == variabelenaam == inputnaam) goed van pas

    bijv. bij naam componist. Verander

<input type="text" id="componistId" name="componistId">

naar

<input type="text" id="componistId" name="componistId" value="<?= $componistId ?>">
  1. Maak een fout in de invoer, laat zien dat de eerder ingevulde gegevens blijven bestaan.

  2. Vul nu correcte gegevens in, die worden opgeslagen, maar ook weer bewaard. Dus bij succes, die variabelen leeggooien.

// Check results
if ($succes) {
    $melding = 'Gegevens zijn opgeslagen in de database.';
    // maak de 4 variabelen weer leeg
    $componistId = '';
    $naam = '';
    $geboortedatum = '';
    $schoolId='';
} else {
    $melding = 'Er ging iets fout bij het opslaan.';
}

Stap 6: Invoervelden niet klakkeloos vertrouwen

Input opschonen om hackers het moeilijk te maken om cross-site scripting, code-injection e.d. te doen.

We nemen nu gegevens één op één over vanuit de $_POST, dat geeft hackers de mogelijkheid tot code injectie (javascript, etc.). We gaan de input opschonen.

Lees de $_POST uit met htmlspecialchars(...). Eventueel is ook nog de strip_tags te gebruiken.

$componistId    = htmlspecialchars(trim($_POST['componistId']));
$naam           = htmlspecialchars(trim($_POST['naam']));
$geboortedatum  = htmlspecialchars(trim($_POST['geboortedatum']));
$schoolId       = htmlspecialchars(trim($_POST['schoolId']));

Stap 7 (Extra): Dropdown voor schoolid

Maak een dropdown van de schoolid, vooral moeilijk als de eerder geselecteerde waarde bewaard moet blijven.

Huiswerk

  1. Maak formulier voor scholen volledig (dus veldcontrole, input opschonen etc.)
  2. Voorbereiding volgende les

PHP Les 7: Registreren en Inloggen

Doelstelling les:

  • Registeren van gebruikers
  • Inloggen van gebruikers

In deze les wordt uitgewerkt hoe een gebruiker zich kan registreren en daarna kan inloggen. In de muziekschool-database is een tabel Gebruiker met twee kolommen (naam en passwordhash). In de casus heeft deze tabel meer kolommen, namelijk emailadres en gebruikersrol. Met die laatste kolom kan er onderscheid gemaakt worden tussen bijv. klanten en beheerders. Deze kolommen zijn niet in de muziekschool-database, zodat er nog wat te programmeren overblijft voor de studenten (en niet alleen copy/paste werk overblijft).

Voorbereiding

Lesprogramma

  • Bespreken huiswerk
  • Registreren van gebruikers
  • Inloggen van gebruikers

Registreren van gebruikers

Voor de muziekdatabase gebruiken we maar twee kolommen, namelijk naam en passwordhash. Met deze twee kolommen kun je ook al inloggen implementeren. Voor de casus wordt dat wat complexer met twee extra kolommen, emailadres en gebruikersrol.

Van elke stap is een voorbeeld gemaakt (zie de map ./files/), maar probeer zoveel mogelijk alles "live" voor te doen en bespreek het geheel (in de WTIS-omgeving is de volledige uitwerking al opgenomen).

  1. Eerst maken we een HTML-pagina waarmee gebruikers zich kunnen registeren. Dit formulier bevat maar twee inputs de gegevens op te geven. In de praktijk laat je gebruikers twee keer het wachtwoord invoeren om typfouten te ondervangen (huiswerk is om dat extra veld toe te voegen).

    voorbeeld van de HTML-pagina ./files/01-registratie.html

  2. Volgende stap is om er een PHP-pagina van te maken

    zie ./files/02-registratie.php

  3. Om te controleren of er op de knop registeren is geklikt, voegen we een controle in en geven een melding.

    zie ./files/03-registratie.php

  4. Nu halen we de gegevens uit het formulier en checken of de ingevulde waarden correct zijn.

    ./files/04-registratie.php

  5. Password hash, eerst afdrukken naar het scherm

    $passwordhash = password_hash($wachtwoord, PASSWORD_DEFAULT);
    $melding = "password hash: $passwordhash";
    
  6. nu naar de database toe

    bovenaan aan de pagina

    require_once 'db_connectie.php';
    

    En het opslaan van de gevens (zie ./files/06-registratie.php)

    // Hash the password
    $passwordhash = password_hash($wachtwoord, PASSWORD_DEFAULT);
    
    // database
    $db = maakVerbinding();
    // Insert query (prepared statement)
    $sql = 'INSERT INTO Gebruikers(naam, passwordhash)
            values (:naam, :passwordhash)';
    $query = $db->prepare($sql);
    
    // Send data to database
    $data_array = [
        'naam' => $naam,
        'passwordhash' => $passwordhash
    ];
    $succes = $query->execute($data_array);
    
    // Check results
    if($succes)
    {
        $melding = 'Gebruiker is geregistreerd.';
    }
    else
    {
        $melding = 'Registratie is mislukt.';
    }
    
    • Doe een demo en laat zien dat de gevens in de database zitten.
    • In de WTIS-omgeving (de docker-omgeving) is dit eindresultaat ook opgenomen, te vinden in ./applicatie/registreren.php.

Inloggen door gebruiker

Eigenlijk dezelfde aanpak als hierboven, zie ./files/07-inloggen.php. Dit bestand staat ook in de WTIS-omgeving (docker).

Bouw het bestand zoveel mogelijk samen op en bespreek de inhoud.

Huiswerk

  1. Breid het registratieformulier uit met een extra input voor wachtwoordcontrole. De gebruiker moet dus twee keer zijn wachtwoord opgeven, in de PHP controleer je of die twee gelijk zijn. Dit om te voorkomen dat gebruikers per ongeluk een typefout maken bij het invoeren van het wachtwoord.

  2. Maak een pagina gebruiker.php waarin je afdrukt of een gebruiker wel of niet is ingelogd. Bij het inloggen wordt er een session-variabele gevuld, die zul je dus moeten controleren of een gebruiker al dan niet is ingelogd.

Analyse Security

Leerdoelen

  • Herkennen van OWASP security risico's
  • Analyseren van gevaren binnen een applicatie
  • Bewustwording van consequenties van een lek
  • Beheersen van beveiligingskwetsbaarheid

Voorbereiding

Lees en bekijk video's

Extra:

Beantwoord de volgende vragen:

  1. Leg in eigen woorden uit wat Broken Access Control is.
  2. Leg in eigen woorden wat het gevaar is van Injection.
  3. Leg in eigen woorden wat het verschil is tussen Injection en Cryptographic failures.
  4. Zoek 3 bedrijven op die security leak hebben gehad in de afgelopen 5 jaar. Hoe werden deze bedrijven gehackt?

Lesprogramma

Analyse van veiligheid van applicatie

Tijdens de les gaan we aan de slag met het analyseren van beveiligingsrisico's.

In eerste instantie ga je dit toepassen op de code die je tot nu toe gemaakt hebt voor de muziekschool-database en webpagina's. Tijdens het beroepsproduct voer je dit uit voor je beroepsproduct.

Opdracht 1: analyseren van door OWASP aangemerkte beveiligingsrisico's (~30 m)

Ga in kleine groepjes aan de slag met een door de docent toegewezen beveiligingsrisico uit de OWASP top 10. Het is de bedoeling om 'expert' te worden van kwetsbaarheden gerelateerd aan dit beveiligingsrisico en elkaar daarover te gaan informeren als input voor het werken aan het beroepsproduct. Denk hierbij aan het inventariseren van mogelijke aanvalstechnieken die gebruikt kunnen worden om de kwetsbaarheden uit te buiten, maar ook suggesties voor maatregelen die genomen zouden kunnen worden in een applicatie zoals jouw applicatie.

Deze analyse is een goede bron van informatie voor maatregelen die genomen moeten worden tegen beveiligingsrisico's in je beroepsproduct (WTIS-2). Bereidt je dus goed voor om als expert-groep de opgedane kennis te kunnen delen met klasgenoten die weer een ander risico hebben geanalyseerd waar jij graag kennis over wil hebben. Een goed uitgangspunt hierbij is dus om te bedenken welke informatie je zelf graag zou willen horen en dat samen te vatten in een presentatie van maximaal 5 minuten, denk hierbij minimaal aan:

  1. een risicotabel om duidelijk te maken wat het risico is (kans x gevolg) zoals in het voorbeeld
  2. geef concrete voorbeelden van te nemen maatregelen in een applicatie

Opdracht 2: presenteren van onderzoeksresultaten (~30 m)

Presenteer de gevonden resultaten van opdracht 1 aan je peers (andere groepjes) en stel aansluitend kritische vragen aan elkaar.

Deze informatie heb je nodig om je beroepsproduct te kunnen beveiligen.

👷🏼 Opdracht 3: Voer de maatregelen door (~30 m)

Pas de verkregen kennis uit de presentaties en bronnen in de lesvoorbereiding toe op jou beroepsproduct. Dit is ook een goede gelegenheid om waar nodig aanvullende kennis op te halen bij de andere expert groepen.

De grootste risico's en de meest voordehandliggende oplossingen in PHP kun je vinden in het onderdeel met voorbeelden