Programmeren in PHP/mysql-functies
Vooraf
De originele versie van de MySQL API (= deze versie) wordt afgeraden te gebruiken vanaf PHP versie 5.5.
Onderstaande commando's zullen in toekomstige PHP-versies verwijderd worden.
Het is beter om mysqli_*-functies te gebruiken, of een connectie te maken met behulp van de PDO-extensie.
Verbinding maken
Om verbinding te maken met een database hoor je drie dingen te weten. De gebruikersnaam, het wachtwoord en de plaats waar je database zich bevindt, de host.
PHP-code:
<?php
$_CONFIG["Username"] = 'root'; // Gebruikersnaam
$_CONFIG["Password"] = ''; // Wachtwoord
$_CONFIG["Host"] = 'localhost'; // Host
?>
De conventie is om deze in een configuratiebestand te zetten. We hebben nog geen verbinding gemaakt, alleen de gegevens in een associatieve array gezet, zodat we ze makkelijk terug kunnen vinden. Om verbinding te maken met een database heeft PHP voor iedere database een hoop functies aan boord. Wij gaan die voor MySQL gebruiken, deze beginnen met "mysql_" gevolgd door het doel, bijvoorbeeld mysql_connect(). Omdat er natuurlijk het een en ander fout kan gaan met het maken van de verbinding moeten we ook zorgen voor een goede foutafhandeling, dat doen we met trigger_error(). Omdat die functie makkelijk aan te passen is en standaard een hoop extra informatie kan leveren over de foutmelding. Als je klaar bent met het gebruik van de database is het netjes om de verbinding te sluiten, PHP doet dat, als het niet door jou gebeurt, zelf. Toch is het niet netjes hier op te vertrouwen. Een verbinding sluiten doen we met mysql_close().
PHP-code:
<?php
$_CONFIG["Username"] = 'root';
$_CONFIG["Password"] = '';
$_CONFIG["Host"] = 'localhost';
$Verbinding = mysql_connect($_CONFIG["Host"], $_CONFIG["Username"], $_CONFIG["Password"]);
if($Verbinding == false) // Verbinding is mislukt!
{
trigger_error("Kan geen verbinding maken met de database");
}
mysql_close($Verbinding);
?>
je kan ook dit gebruiken:
PHP-code:
<?php
$_CONFIG["Username"] = 'root';
$_CONFIG["Password"] = '';
$_CONFIG["Host"] = 'localhost';
$Verbinding = mysql_connect($_CONFIG["Host"], $_CONFIG["Username"], $_CONFIG["Password"]) or die("Kan geen verbinding maken met de database");
mysql_close($Verbinding);
?>
Als het goed is levert het vorige programma geen tekst op als je het uitvoert, dat betekent dat de verbinding gelukt is. Als het niet lukt kan dit een aantal oorzaken hebben:
- De gegevens in $_CONFIG zijn fout, probeer dat eerst goed te krijgen.
- Je gebruikt een verouderde manier om verbinding met MySQL te maken, vraag aan de administrator van de database om een wachtwoord in te stellen met OLD_PASSWORD.
Database aanmaken
Als je met een database wilt werken, is het handig als je die hebt en als daar tabellen met gegevens in staan. Gelukkig kan je met SQL ook een database aanmaken. De syntax voor die query is een van de eenvoudigste. Namelijk
CREATE DATABASE db_name
Waarbij db_name de uiteindelijke naam voor de database is. Ook gaan we daarvoor een regel aan de $_CONFIG array toevoegen met daarin de te gebruiken database. Ook wordt dit onze eerste echte query in MySQL.
PHP-code:
<?php
$_CONFIG["Username"] = 'root';
$_CONFIG["Password"] = '';
$_CONFIG["Host"] = 'localhost';
$_CONFIG["Database"] = 'gastenboek';
$Verbinding = mysql_connect($_CONFIG["Host"], $_CONFIG["Username"], $_CONFIG["Password"]);
if($Verbinding == false) // Verbinding is mislukt!
{
trigger_error("Kan geen verbinding maken met de database");
}
$Result = mysql_query("CREATE DATABASE " .
mysql_escape_string($_CONFIG["Database"]), $Verbinding);
if($Result == false)
{
trigger_error("Kan de database niet aanmaken!");
}
else
{
echo "De database is succesvol aangemaakt";
}
mysql_close($Verbinding);
?>
Als het goed is, komt er nu "De database is succesvol aangemaakt" op je scherm te staan. Om de SQL-query te versturen gebruiken we mysql_query, die als eerste argument de query wil en als tweede argument wil mysql_query de verbinding, zodat hij weet waar de query heen gestuurd moet worden. mysql_query geeft, als de query gelukt is een "identifier" terug, waar de opgehaalde rows in staan. Of false als de query mislukt is.
Zoals je wellicht gezien hebt, gebruiken we ook een andere functie. Namelijk mysql_escape_string, deze zorgt er voor dat er geen "foute" tekens in je query terechtkomen en dat de query daardoor niet uitgevoerd wil worden. Ook lost deze functie in een later stadium een groot deel van de database beveiligingsproblemen op.
De tabel aanmaken
Nu we een database hebben aangemaakt, zullen we na moeten gaan denken over de informatie die we op willen slaan. We gaan nu kijken wat we op willen slaan.
- De naam van de gast
- Het e-mailadres van de gast
- Of we het e-mailadres al dan niet mogen weergeven
- De website van de gast
- Het bericht zelf
- De datum waarop het bericht gepost is
Als we dat bedacht hebben, zullen we een tabel moeten bedenken. Zoals er ook te zien is de relatie constant een-op-een. Dit houdt in dat we de alle gegevens mooi in één tabel kunnen zetten. Nu zullen we namen moeten bedenken voor de velden en aan moeten geven welk veld de Primary Key zal worden. De tabel zelf zal de velden "naam", "e-mail", "show_e-mail", "website", "datum" en "bericht" krijgen. Met "id" als Primary Key, zodat we daar op kunnen zoeken, later. Het SQL statement om een tabel te maken ziet er als volgt uit;
CREATE TABLE berichten
(
`id` INT NOT NULL AUTO_INCREMENT,
`naam` VARCHAR(50),
`e-mail` VARCHAR(80),
`show_e-mail` TINYINT(1),
`website` VARCHAR(50),
`bericht` TINYTEXT,
`datum` DATETIME,
PRIMARY KEY (`id`)
)
We zullen deze query op dezelfde manier uit voeren als bij het CREATE DATABASE statement, dus de code zal je bekend voorkomen.
Tabel.php
PHP-code:
<?php
$_CONFIG["Username"] = 'root';
$_CONFIG["Password"] = '';
$_CONFIG["Host"] = 'localhost';
$_CONFIG["Database"] = 'gastenboek';
$Verbinding = mysql_connect($_CONFIG["Host"], $_CONFIG["Username"], $_CONFIG["Password"]);
if($Verbinding == false) // Verbinding is mislukt!
{
trigger_error("Kan geen verbinding maken met de database");
}
$DbSelect = mysql_select_db($_CONFIG["Database"], $Verbinding);
if ($DbSelect == false)
{
trigger_error("Kan de database niet selecteren");
}
$Result = mysql_query('
CREATE TABLE berichten
(
`id` INT NOT NULL AUTO_INCREMENT,
`naam` VARCHAR(50),
`e-mail` VARCHAR(80),
`show_e-mail` TINYINT(1),
`website` VARCHAR(50),
`bericht` TINYTEXT,
`datum` DATETIME,
PRIMARY KEY (`id`)
)
', $Verbinding);
if($Result == false)
{
trigger_error("Kan de tabel niet aanmaken! Waarschijnlijk bestaat de tabel al.");
}
else
{
echo "De tabel is succesvol aangemaakt";
}
mysql_close($Verbinding);
?>
Een functie is er nog bij gekomen, en dat is mysql_select_db, om aan te geven welke database we willen gebruiken om de tabel in aan te maken. mysql_select_db geeft true terug als het selecteren van de database gelukt is, als het mislukt krijg je false terug. Dus daar kun je mooi op testen met een if-statement.
De tabel vullen met test data
Omdat we nog geen gasten hebben die ons gastenboek schrijven zullen we zelf gegevens in de tabel moeten plaatsen om dadelijk de queries te kunnen testen om het gastenboek op te halen. Als we klaar zijn kunnen we deze gegevens er weer uit halen door middel van een TRUNCATE TABLE-query. Een query die ook in het administrator paneel komt. De query die we gaan gebruiken voor het vullen wordt een insert query. Zoals die hieronder. INSERT INTO `berichten` SET `naam` = !, `e-mail` = !, `show_email` = !, `website` = !, `bericht` = !, `datum` = NOW() Waarbij notie gemaakt moet worden van de ! tekens, die in dit voorbeeld als "place holder" dienen, daar komt later nog data te staan. Dan hebben we nog de NOW() functie, deze genereert aan het begin van de query een keer de datum en tijd en gebruikt die dan over de hele query, dus als je je query uitvoert op 23 maart, en het duurt 3 dagen om de query uit te voeren is het resultaat van NOW() nog steeds 23 maart. Nou is dat meestal niet relevant omdat het uitvoeren van de query meestal maar een tiende of een honderdste van een seconde duurt. Dus kunnen wij dat mooi gebruiken in ons gastenboek. Om de test gegevens makkelijk in te kunnen voeren gaan we eerst een array maken met de test data er in. Let wel op dat straks de gegevens andersom komen te staan als je het gastenboek opent. Dat komt omdat we dan de nieuwste gegevens bovenaan willen laten zien.
TestData.php:
PHP-code:
<?php
include_once('verbinding.php');
$TestData = array();
$TestData[] = array('Henk', 'henk@example.com', false, 'www.example.com',
"He! Leuk idee zo''n gastenboek op je website!");
$TestData[] = array('Xavier', 'xavier@example.net', true, '', "Test bericht");
$TestData[] = array('Peter Gulutzan', 'peter@mysql.com', false, "http://mysql.com",
"Ik kom van de mysql-foundation even een kijkje nemen in je gastenboek, leuk gedaan");
$Query = "
INSERT INTO
`berichten`
SET
`naam` = '%s',
`e-mail` = '%s',
`show_e-mail` = %u,
`website` = '%s',
`bericht` = '%s',
`datum` = NOW()
";
foreach ($TestData as $Data)
{
mysql_query(vsprintf($Query, $Data), $Verbinding)
or trigger_error('Kan de query niet uitvoeren!');
}
mysql_close($Verbinding);
?>
Verbinding.php:
PHP-code:
<?php
$_CONFIG["Username"] = 'root';
$_CONFIG["Password"] = '';
$_CONFIG["Host"] = 'localhost';
$_CONFIG["Database"] = 'gastenboek';
$Verbinding = mysql_connect($_CONFIG["Host"], $_CONFIG["Username"], $_CONFIG["Password"]);
if($Verbinding == false) // Verbinding is mislukt!
{
trigger_error("Kan geen verbinding maken met de database");
}
$DbSelect = mysql_select_db($_CONFIG["Database"], $Verbinding);
if ($DbSelect == false)
{
trigger_error("Kan de database niet selecteren");
}
?>
Deze pagina slaan we zo op omdat we hem nog vaak nodig zullen hebben. Sla het op als Verbinding.php.
Gebruikersinvoer
Voor het invoeren van de berichten gebruiken we een HTML pagina met een formuliertje waar de gebruiker een aantal waarden in kan vullen. Deze gegevens komen dus uiteindelijk in het gastenboek te staan. Het formulier is opgemaakt met behulp van CSS. Om het formulier te verwerken en in de database te zetten versturen we het bericht naar verwerk_bericht.php.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Bericht invoeren</title>
<style type="text/css">
<!--
label { float: left; clear: left; text-align: right; width: 140px; }
input, textarea { float: right; clear: right; }
form { width: 340px; margin-left: auto; margin-right: auto;
border: 1px solid black; padding: 10px; }
h1 { text-align: center; font-size: 24px; }
input, textarea { width: 175px; }
input { height: 15px; }
label { height: 20px; }
textarea { height: 150px; margin-top: 5px; }
#submit { height: 22px; }
* { font-family: sans-serif; font-size: 12px; margin-top: 2px; }
-->
</style>
</head>
<body>
<h1>Gastenboek</h1>
<form method="post" action="verwerk_bericht.php">
<label for="naam">Naam</label>
<input type="text" id="naam" name="Naam"/>
<label for="e-mail">e-mailadres</label>
<input type="text" id="e-mail" name="email"/>
<label for="display_e-mail">e-mailadres weergeven</label>
<input type="checkbox" name="d_email" id="display_e-mail"/>
<label for="website">Website</label>
<input type="text" value="http://" name="website" id="website"/>
<label for="bericht">Bericht</label>
<textarea id="bericht" name="bericht"></textarea>
<label></label>
<input type="submit" id="submit" value="Verstuur bericht"/>
</form>
</body>
</html>
Er zijn een aantal zaken waar we rekening mee moeten houden tijdens het verwerken van het gastenboek, namelijk beveiliging en of de gegevens valide zijn (het e-mailadres moet een echt e-mailadres zijn en de website een echte website) Om dat te kunnen gaan we gebruik maken van reguliere expressies. Ik ga hier niet uitleggen hoe die werken, maar ik geef je wel twee reguliere expressies om de twee verplichte gegevens te valideren. Ook moeten we rekening hou met 'magic quotes' en met SQL injection.
#email adressen valideren
;^([a-z0-9-_]+)(.[a-z0-9-_]+)*@([a-z0-9-]+)(.[a-z0-9-]+)*.[a-z]{2,4}$;i
Een email-adres moet dus bestaan uit 1 of 2 groepjes alfanumerieke waarden (A tot Z, 0 tot 9 en _) met een punt tussenin, gevolgd door een apestaartje en 1 of 2 groepjes alfanumerieken, afgesloten door 2 tot 4 letters.
En om URL's (adressen van websites) te valideren:
;^http\:\/\/[a-z0-9-]+.([a-z0-9-]+.)?[a-z]+;i
Omdat we die reguliere expressies niet zomaar in PHP kunnen gebruiken, op een manier als we in perl zouden doen, maken we gebruik van de preg_match() functie die standaard in PHP zit. Daar schrijven we een mooie functie omheen zodat we de acties vaker kunnen gebruiken.
PHP-code:
function is_email($email)
{
return (bool)preg_match(';^([a-z0-9-_]+)(.[a-z0-9-_]+)*@([a-z0-9-]+)(.[a-z0-9-]+)*.[a-z]{2,4}$;i', $email);
}
function is_url($url)
{
return (bool)preg_match(';^http\:\/\/[a-z0-9-]+.([a-z0-9-]+.)?[a-z]+;i', $url);
}
SQL Injection
Aangezien we niet willen dat er database-aanvallen worden uitgevoerd zullen we dit eerst moeten voorkomen, dat zien we dadelijk. Het idee achter een database-aanval is dat er malafide data in de echte SQL query wordt geïnjecteerd. Neem bijvoorbeeld de volgende query:
$query = "SELECT * FROM `table` WHERE `x` = " . $waarde
Hier kun je alles in de variabele $waarde zetten, ook bijvoorbeeld de string " 1 OR 1=1" die er voor zorgt dat alle gegevens uit de hele tabel worden opgevraagd en niet alleen de gegevens die voldoen aan de voorwaarde die wij in gedachten hadden. Nou is dat niet zo'n ramp in dit geval, omdat het geen gegevens verwijdert uit onze query. Daar steekt MySQL zelf namelijk een stokje voor. Normaal zou je namelijk een query 'koppelen' door het ; teken, als je $waarde dan in "1; DELETE * FROM table" verandert, zie je de bui al hangen? Maar, omdat MySQL niet twee query's kan verwerken die gescheiden zijn door een ; werkt dit niet. Om toch een groot deel van de fouten te voorkomen, meest voorkomende is het injecteren van een apostrof (') in de query, om simpelweg te kijken of MySQL over z'n nek gaat, gebruiken we de functie mysql_real_escape_string, in combinatie met de functie array_walk omdat we alle gegevens uit $_POST in de SQL query gaan gebruiken. De aanroep wordt dan dus:
array_walk($_POST, 'mysql_real_escape_string');
Nu is de post data klaar om in de query te stoppen.
Gegevens in de tabel stoppen
Om de gevevens uit het formulier in de tabel te stoppen moet je de volgende code uitvoeren:
PHP-code:
<?PHP
function is_email($email)
{ return((bool)preg_match(';^([a-z0-9-_]+)(.[a-z0-9-_]+)*@([a-z0-9-]+)(.[a-z0-9-]+)*.[a-z]{2,4}$;i', $email));
}
function is_url($url)
{ return((bool)preg_match(';^http\:\/\/[a-z0-9-]+.([a-z0-9-]+.)?[a-z]+;i', $url));
}
$Query = "
INSERT INTO
`berichten`
SET
`naam` = '%s',
`e-mail` = '%s',
`show_e-mail` = %u,
`website` = '%s',
`bericht` = '%s',
`datum` = NOW()
";
if(is_email($_POST['email']))
{ // Verwijder de url als hij niet bestaat.
if(!is_url($_POST['website']))
{ $_POST['website'] = '';
}
// Dit moet eerst, mysql_real_escape_string() geeft anders een foutmelding.
include_once('verbinding.php');
$data = array_map($_POST, 'mysql_real_escape_string');
if(mysql_query(vsprintf($Query,$data),$Verbinding))
{ // Het is gelukt.
echo('Gegevens zijn toegevoegd.');
}
else
{ // Het is mislukt.
echo('Er is een error opgetreden.');
}
mysql_close($Verbinding);
}
?>
Gegevens uit de tabel halen
Je hebt niks aan gegevens in een database als je ze er niet uit kan halen. Je gebruik de sql 'SELECT * FROM `table`' als je gegevens wilt opvragen. Hier een voorbeeld als je gegevens uit een gastenboek wilt halen. (Deze gebruikt het document 'verbinding.php' om te verbinden met de database.)
PHP-code: Voorbeeld: gegevens uit een tabel halen
<html>
<head>
<title>gegevens uit een tabel halen</title>
</head>
<body>
<?php
include_once('verbinding.php');
$data = array();
$i = 0;
$result = mysql_query("SELECT * FROM `berichten`");
while ($lijst = mysql_fetch_array($result,MYSQL_ASSOC))
{ foreach ($lijst as $sleutel => $waarde)
{ // Met htmlentities() voorkom je dat html wordt uitgevoerd.
// Dus gebruiken we het op alle gegevens behalve op 'show_e-mail'.
if($sleutel != 'show_e-mail')
{ $waarde = htmlentities($waarde);
}
/*-------------------------------*
* Zet alle berichten gesorteerd *
* op id in $data *
*-------------------------------*/
$data[$i][$sleutel] = $waarde;
}
$i++;
}
mysql_free_result($result);
mysql_close($Verbinding); // Sluit de verbinding.
// We hebben hem nu niet meer nodig.
$data = array_reverse($data); // Zet de nieuwste berichten bovenaan
// in plaats van onderaan.
$cnt = count($data);
for($i = 0; $i < $cnt; ++$i)
{ $bericht = $data[$i];
echo('<table>'); // Zet de gegevens netjes in een tabel.
echo('<tr>');
echo('<th>');
echo('naam: ');
echo('</th><td>');
// Lees de gegevens uit de associatieve array.
echo($bericht['naam']); // Zo makkelijk is het vanaf hier.
echo('</td></tr>');
if($bericht['show_e-mail']) // controleer of de e-mail weergegeven
{ echo('<tr><th>'); // mag worden
echo('e-mail: ');
echo('</th><td>');
echo($bericht['e-mail']);
echo('</td></tr>');
}
echo('<tr><th>');
echo('website: ');
echo('</th><td>');
echo('<a href="' . $bericht['website'] . '">' . $bericht['website'] . '</a>');
echo('</td></tr><tr><th colspan="2">');
echo('Bericht:');
echo('</th></tr><tr><td colspan="2">');
echo(nl2br($bericht['bericht'])); // Met nl2br() worden alle enters in het
// bericht omgezet naar <br/>.
echo('</td></tr><tr><td colspan="2">');
echo('<b>Gepost op: </b>');
echo($bericht['datum']);
echo('</td></tr></table>');
}
?>
</body>
</html>