TNMK30 Elektronisk publicering HT 2014 Laboration 5: PHP och MySQL På webservern www.student.itn.liu.se finns stöd för PHP, och PHP har inbyggda funktioner för att kontakta och ställa SQL-frågor till en rad olika databashanterare. En MySQLserver finns på adressen mysql.itn.liu.se. Exempeldata från föreläsningen ligger där i en databas med namnet demo för att ni skall kunna testa enkla frågor på en mycket liten databas, och bekanta er med SQL. Den egentliga laborationen kommer dock att göras på en betydligt större databas med tusentals poster i vissa tabeller. Strukturen för den databasen är dock väldigt lik det lilla exemplet, det finns bara mycket mer data i den. Nedan hittar du ett enkelt och utförligt kommenterat PHP-script som ställer en fråga till en MySQL-databas och visar resultatet som en HTML-tabell. Exakt denna kod kan du kopiera och klistra in i en egen.php fil, lägga den på W: och titta på den via webservern i valfri webläsare. Kolla en extra gång att radbrytningarna har följt med när du klistrar in. Koden kan också laddas ner från http://webstaff.itn.liu.se/~katvr62/tnmk30/2014/la5_kod.zip och packas up på W: (Följande kod finns i fil testdb.php). <meta charset="utf-8" /> <title>svar på SQL-fråga till databasen 'demo'</title> <link rel="stylesheet" href="style.css"/> // Koppla upp mot databasservern med användarnamn och ev. lösenord. // mysql_connect("url till databasserver", "användarnamn", "lösenord"); // (Denna användare behöver inte ange lösenord för denna databas.) mysql_connect("mysql.itn.liu.se", "demo", ""); // Välj den databas som denna användare har rättigheter på. // (Servern har hundratals små databaser, du kan få en egen om du vill.) mysql_select_db("demo"); <h2>svar på SQL-frågan</h2> <pre> // Ange SQL-frågan som en strängvariabel, vi skall ju skicka den från PHP. // stäng jämförelser i SQL kan göras med LIKE operatorn. När du använder // % i din jämförelse agerar som en wildcard, dvs den matchar ett arbiträrt antal tecken // lastname LIKE '%so%' säger att efternamnet ska innehålla 'so' och vad som helst innan och efter $query = "SELECT firstname, lastname FROM staff WHERE lastname LIKE '%so%'"; // Skriv ut själva frågan innan vi skriver ut svaret. print $query; </pre> // Ställ frågan till MySQL-servern, spara en // referens till resultatet i variabeln $contents
$contents = mysql_query($query); // Skriv ut svaret som en tabell för tydlighets skull print("<table>\n"); // \n skriver en radbrytning i själva HTML-koden print("<tr>\n"); // \n skriver en radbrytning i själva HTML-koden // Skriv ut kolumnrubriker med fältnamnen. Vi vet inte exakt // vilka fält som kommer tillbaka, det beror på frågan, så vi // läser antalet fält och deras namn från resultatvariabeln // genom att använda några praktiska inbyggda funktioner i PHP. // mysql_num_fields returnerar antal columner/fält i resultatet for ($i = 0; $i < mysql_num_fields($contents); $i++) { $fieldname = mysql_field_name($contents, $i); print("<th>$fieldname</th>"); print "</tr>\n"; // Skriv ut varje post i svaret som en rad i tabellen while ($row = mysql_fetch_row($contents)) { // $row blir FALSE när raderna är slut print("<tr>"); for ($i = 0; $i < mysql_num_fields($contents); $i++) { // for-loop i PHP, liknar C++ och Java print("<td>$row[$i]</td>"); // end for print("</tr>\n"); //end while print("</table>\n"); </body> </html> Kopiera och klistra in också följande CSS regler (för att styla tabellen) i en fil med namn style.css och spara den i samma map på W:. table, td, th { border: 1pt solid black; En statisk fråga kanske inte är så spännande, men redan detta är faktiskt ändå en väsentlig förbättring jämfört med en färdig HTML-sida. Man kan nämligen uppdatera innehållet i databasen utan att behöva röra HTML-koden, och databasen kan användas även från andra program för andra syften. Att dela data mellan flera tillämpningar och att bara behöva uppdatera data på ett enda ställe är viktiga grundtankar med att använda databaser. Frågan ställs inifrån PHP, och eftersom PHP enkelt kan ta emot och behandla formulärdata så ligger det nära till hands att göra en SQL-fråga som är delvis dynamisk. Om man låter databasfrågan i sin helhet vara en helt valfri sträng så kan användare få för sig att göra en massa dumheter, så endast vissa delar av frågan bör tas från formulärdata. Detta är ett mycket vanligt sätt att använda databaser på WWW. Ett exempel på hur man kan ställa en fråga som i sin helhet tas från formulärdata ser du nedan. Som sagt är detta olämpligt för verkliga tillämpningar, men tag detta enkla exempel som utgångspunkt för egna experiment utifrån exemplen från föreläsningen. Fortsätt sedan med labuppgiften på nästa sida.
Enkel sida med ett formulär som skickar en valfri SQL-fråga (i filen generalquery.html ): <meta charset="utf-8"/> <title> Ställ SQL-fråga </title> <h1> SQL exempel </h1> <form action="generalquery.php" method="post"> <p> Ställ valfri SQL-fråga:<br /> <textarea name="query" rows="8" cols="80"> </textarea><br/> <input type="submit" value="skicka fråga"> <input type="reset" value="rensa fråga"> </p> </form> </body> </html> PHP-script (i filen generalquery.php ) som tar emot frågan och visar svaret: <meta charset="utf-8"/> <title>svar på SQL-fråga till databasen 'demo'</title> <link rel="stylesheet" href="style.css"/> mysql_connect("mysql.itn.liu.se","demo",""); mysql_select_db("demo"); // Från formuläret, om man trycker på "submit"-knappen, kommer // variablen $POST["query"] som innehåller texten i fältet "query". $query_temp = $_POST["query"]; // Enkla citationstecken blir tyvärr sönderkörda när de gått // genom en webservers behandling av formulärdata. För att SQL-frågor // med strängar i skall kunna skickas med så måste vi därför först // ersätta alla förekomster av \' med endast '. // Det finns en färdig funktion i PHP för detta. $query = stripslashes($query_temp); // Nu har vi en fråga i $query som vi kan skicka till MySQL! <h2>svar på SQL-fråga till databasen 'demo'</h2> <p> print "<code>$query</code>"; </p> // Ställ frågan till MySQL-servern, spara en // referens till resultatet i variabeln $contents $contents = mysql_query("$query"); // Skriv ut svaret som en tabell print("<table>\n");
Labuppgift print("<tr>\n"); // härifrån är det samma som förra exempel //... Det finns ett verkligt exempel i form av en mycket större databas på samma server som du nyss varit inne på. Den heter lego och innehåller information om en stor del av alla de tusentals byggsatser som Lego tillverkat. Strukturen för databasen ser du genom att köra följande PHPscript, som visar samtliga tabeller i databasen. Alla data visas inte, det skulle bli väldigt mycket text i så fall, men du kan själv titta på data i enstaka tabeller genom att skicka andra frågor till databasen, på samma sätt som du nyss gjort med den lilla databasen. Kom ihåg att ändra uppgifterna om databasnamn från demo till lego och användarnamn från demo till lego. Det behövs inget lösenord för denna databas heller. Användaren lego får bara göra SELECT, inte ändra i några data, och databasens innehåll är inte av känslig karaktär - alla data finns att hämta öppet och gratis på nätet från www.bricklink.com. Om du kopierar och sparar följande kod i en.php fil på W: (eller öppna filen legodb_contents.php från den nedladdade koden) kan du se vilka tabeller som lego databasen innehåller, samt hur många rader och vilka fält (kolumner) varje tabell har. <meta charset="utf-8"/> <title>visa alla tabeller i databasen 'lego'</title> <link rel="stylesheet" href="style.css"/> <h1>tabeller i databasen 'lego'</h1> mysql_connect("mysql.itn.liu.se", "lego", ""); // Logga in på databasservern mysql_select_db("lego"); // Välj databas // Hämta info om alla tabellnamn i databasen $tables = mysql_list_tables("lego"); // Loopa över alla tabeller while ($table = mysql_fetch_row($tables)) { $tablename = $table[0]; // Tabellnamnet är det första (och enda) fältet // Kolla antalet poster i tabellen $res = mysql_query("select count(*) FROM $tablename"); $numrows = mysql_fetch_row($res); //spara resultatet från count(*) $numrows = $numrows[0]; // antal poster (count) är det första (och enda) fältet print("<h2>tabellen '$tablename' innehåller $numrows poster</h2>\n"); // Hämta data ur tabellen begränsa till max 10 rader, // vi vill inte visa alla utan bara få en känsla av hur datan ser ut $contents = mysql_query("select * FROM $tablename Limit 10"); $fieldnum = mysql_num_fields($contents); print("<table>\n"); print("<tr>\n"); for ($i = 0; $i < $fieldnum; $i++) { $fieldname = mysql_field_name($contents, $i);
print("<th>$fieldname</th>"); print "</tr>\n"; // Skriv ut varje post i svaret som en rad i tabellen // $row blir FALSE när raderna är slut while ($row = mysql_fetch_row($contents)) { print("<tr>"); for ($i = 0; $i < mysql_num_fields($contents); $i++) { // for-loop i PHP, liknar C++ och Java print("<td>$row[$i]</td>"); // end for print("</tr>\n"); // end while // om tabellen har fler än 10 rader if ($numrows > 10) { // Visa med "..." att det finns mer data print("<tr><td colspan=". $fieldnum. ">...</td></tr>\n"); print("</table>\n"); </body> </html> De tre väsentliga tabellerna du skall koncentrera dig på är parts, sets och inventory. Precis som för det enkla exemplet är det frågan om två listor: sets innehåller (nästan) alla Lego-satser sedan 1950-talet och framåt, och parts innehåller (nästan) alla olika sorters Legobitar som tillverkats genom åren. Tabellen inventory är en n-till-m koppling mellan en viss sats och de bitar den innehåller, och även omvänt, mellan en viss bit och de satser denna bit ingår i. Tabellen inventory innehåller som sagt endast ett urval av alla Lego-satser, men den innehåller ändå mycket data. Övriga tabeller innehåller kompletterande information som behövs om man skall få ut fullständiga och helt korrekta svar från sökningen, men de behöver du inte använda för att bli godkänd. Ibland dyker det upp delar i en sats som egentligen inte är bitar, utan hela figurer, klistermärken, andra delsatser eller objekt av andra typer. Du kan för denna uppgift strunta helt i att visa sådana rader, de utmärks av att fältet inventory.itemtypeid inte är lika med P. Din labuppgift är nu att göra en enkel webtjänst med följande funktion: I ett formulär skall man kunna skriva in numret på en Lego-sats (SetID). Detta nummer skall matchas mot det nummer som står i databasen, men man skall inte behöva veta vilket undernummer den har, det skall räcka med att ange det som står före bindestrecket. Om man är ute efter satsen som har nummer 6080 skall man alltså inte behöva veta att den råkar kallas 6080-1 i databasen, utan kunna skriva in 6080. (Vissa satsnummer blir tyvärr inte unika då, men det kan du strunta i för denna första uppgift.) När man postar formuläret skall man få information om: vad satsen heter och vilket år den tillverkades. Vidare: Om satsen finns i tabellen inventory som listar alla satsers innehåll så skall dessutom
alla enstaka bitar visas i en separat tabell på samma sida. Endast de delar av satserna som är faktiskt bitar ska listas (du kan kolla upp detta via inventory.itemtypeid som beskrivet tidigare) Om satsen inte finns i tabellen inventory så skall en text tala om att det inte finns detaljerad information om just denna sats. Informationen på Bricklink.com är helt och hållet skapade av frivilliga som råkat äga en viss sats och räknat vad den innehåller, så ibland saknas det data för särskilt gamla eller ovanliga satser. Ställ en enkel SQL-fråga för att se vilka satser som finns inventerade för tillfället, och jämför det med antalet satser totalt i databasen! Om satsen inte ens finns i listan över satser (sets) så bör sidan även upplysa om det. (Du blir godkänd även om sidan går sönder när man matar in ett felaktigt satsnummer, men det är inte så snyggt.) Det som står i punkterna ska vara uppfyllt för godkänt. Frågor går naturligtvis utmärkt att ställa. Om du tycker uppgiften är för svår och kör fast så hjälper vi dig förstå hur du skall göra, men ni skall lösa det givna problemet själva, det är det den här laborationen går ut på. Lycka till!