Hoofdstuk 5
Een website maken met databasetoegang.
In dit hoofdstuk gaan we het weblog dat je in hoofdstuk 4 hebt gemaakt verder uitbreiden. Een belangrijk onderdeel wordt toegevoegd aan de applicatie, namelijk: databasetoegang. Verder gaan we wat aan validatie van ingevoerde gegevens doen. Leeropdracht 5.1 In deze leeropdracht ga je de gegevens die je hebt ingevoerd in het formulier voor de registratie van een nieuwe user (figuur 1), opslaan in een database.
Figuur 1
a. Maak een MySQL database met een tabel met drie velden. In deze leeropdracht heet de database blog. In die database zit een tabel users. In figuur 2 zie je de velden in de tabel en de veldeigenschappen. Het is verstandig dit ook in jouw applicatie te gebruiken.
Figuur 2 Om gemakkelijk een verbinding te maken met de database is gaan we de verbindingsparameters naar de database vastleggen in het bestand application.ini . Dit bestand vind je in de map …/application/configs/ . In het bestand application.ini staan een aantal settings die de applicatie nodig heeft om te werken. Aan dit bestand gaan we nu de volgende regels toevoegen: resources.db.adapter=PDO_MYSQL resources.db.params.host="localhost" resources.db.params.username="root" resources.db.params.password="" resources.db.params.dbname="blog" In figuur 3 zie je waar deze regels in het bestand application.ini moeten staan.
1
Figuur 3 In deze verbindingsparameters worden de volgende zaken gedefinieerd:
De databaseadapter die je gaat toepassen (in dit geval de PDO-adapter). De host (in dit geval is dat localhost). De username voor het inloggen op de database server (root). Het wachtwoord op de database server (geen wachtwoord). De database die gebruikt gaat worden (blog).
b. Zet de verbindingsparameters voor het verbinden met de server en het selecteren van de database in het bestand application.ini. In hoofdstuk 2 is het Model-View-Controller pattern ter sprake gekomen. De onderdelen View en Controller heb je toegepast. Het onderdeel Model ga je nu toepassen. Een Model is een klasse waarin je allerlei databasehandelingen definieert. Door toepassing van methodes in een model kun je bijvoorbeeld gegevens in database oproepen, toevoegen, veranderen en verwijderen.
2
Een voorbeeld van een model is een DbTable klasse. Dit is een klasse die model staat voor een tabel in een database. Met zf kun je een DbTable model maken met het volgende commando: zf create DbTable users users In dit geval maak je een klasse die model staat voor de tabel users . c. Maak het een DbTable model voor de tabel users in je database. Je hebt nu een model gemaakt van de tabel users in de database blog. Dit model vind je in de map …/models/DbTable/
(zie figuur 4).
Figuur 4 De map DbTable wordt door zf gemaakt als je een model van een databasetabel maakt. In het bestand Users.php is de klasse Users gedefinieerd (zie figuur 5).
Figuur 5 In de naam van de klasse vind je weer de locatie binnen het project terug, door middel van de notatie met "_", zodat je met het auto-load mechanisme van ZF de klasse eenvoudig ergens kunt gebruiken. Deze klasse is een subklasse van de klasse Zend_Db_Table_Abstract, wat weer een klasse uit ZF is. Verder bevat deze klasse een protected attribuut $_name. De waarde van dit attribuut is de naam van de databasetabel waarvan de klasse het model is. Je kunt vervolgens in deze klasse methodes definiëren die verschillende acties op de databasetabel uitvoeren, zoals gegevens toevoegen of gegevens oproepen. Dit gaan we in dit hoofdstuk doen.
3
In het vervolg van deze opdracht gaan we gebruik maken van:
De database blog. De controller UserController. De view add.phtml
We gaan eerst controleren of onze applicatie uit hoofdstuk 4 nog naar behoren werkt. d. Start de applicatie met http://localhost/blog-zend/public/user/add . Je moet het formulier van figuur 6 te zien krijgen.
Figuur 6 e. Voer een aantal waarden in de tekstboxen in en klik op de knop "verzend" . Je moet de geposte waarden dan zien in de view saveadd.phtml die bij de action saveaddAction() behoort, omdat in het form Addfrm het attribuut action van het form met behulp van de methode setAction() de waarde saveadd heeft gekregen. Na de druk op de knop gaat het programma dus verder met de action saveaddAction(). In deze action worden de geposte waarden naar het form verzonden. We gaan nu de applicatie aanpassen zodat de ingevoerde waarden in het form, worden opgeslagen in de databasetabel users. We gaan eerst het model Application_Model_DbTable_Users() aanpassen. In dit model ga je een methode maken waarmee je een nieuw record in de databasetabel kunt toevoegen. Deze methode noemen we insertUser() en heeft een parameter $userData (zie figuur 7).
Figuur 6
4
Je ziet dat in de body van de methode insertUser() de volgende code staat: $this->insert($userData); staat. De methode insert() erft de het model uit de klasse Zend_Db_Table_Abstract . De methode insert() voegt een nieuw record toe aan de tabel user (de tabel die gedefinieerd is in $_name). De parameter van de methode insert() moet een associatieve array zijn, waarbij de key's moeten overeenstemmen met de veldnamen en de bij de key's behorende waarden de ingevoerde waarden moeten zijn.. In dit geval moet de array dus de volgende structuur hebben: array('username'=> …, 'nickname'=> …, 'password'=> …);
Op de puntjes moeten de geposte waarden uit het formulier komen te staan. Dit alles wordt gedaan in de action saveaddAction() van de UserController . Eerst moeten we in de UserController het model Application_Model_DbTable_Users() kunnen gebruiken. In figuur 7 zie je hoe dit gaat.
Figuur 7 In de methode init() van de controller instantieer je het model Application_Model_DbTable_Users() . De methode init() wordt altijd als eerste uitgevoerd bij het aanspreken van een controller. Daarom wordt, in dit geval, bij het aanspreken van de controller een instantie van het model Application_model_DbTable_Users() in het private attribuut userModel gezet. f.
Pas de UserController aan zoals in figuur 7.
In de action saveaddAction() moeten de geposte waarden in een array worden gezet. Dit heb je in hoofdstuk 4 al eens gedaan. In figuur 8 zie je nogmaals hoe dit gaat.
5
Figuur 8 Je ziet hier dat de geposte waarde van het wachtwoord versleuteld wordt en daarna in de array wordt geplaatst. In regel 31 zie je dat de geposte waarden iet naar een view wordt verzonden, maar naar de methode insertUser() van het model Application_Model_DbTable_Users() . Deze methode slaat de ingevoerde waarden in de tabel users op. g. Pas de action saveaddAction() aan, zodat de geposte waarden van het formulier worden opgeslagen in de tabel users() . h. Test je applicatie. Voer een usernaam, een nickname en een wachtwoord in. Klik op de knop en kijk of de nieuwe gegevens in de tabel user zijn opgeslagen. Als de applicatie goed werkt, dan zie je dat na het invoeren van de gegevens in de tabel users ook het view saveadd zichtbaar wordt. i.
Pas de applicatie (eventueel) aan zodat je een melding krijgt dat je gegevens zijn opgeslagen en welke gegevens (zie figuur 9).
Figuur 9 In leeropdracht 5.1 heb je geleerd vanuit een formulier gegevens in een databasetabel weg te schrijven. Je hebt hier een model van de databasetabel users gebruikt. Het gebruikte model is een instantie van de klasse Application_Model_DbTable_Users() die we in deze leeropdracht userModel noemde. Het model werd gemaakt in de methode init() van de UserController (zie figuur 7) en is een attribuut van deze controller. Dit model gebruikt de default databaseadapter, waarvan je de parameters in het bestand application.ini hebt opgeslagen (zie figuur 3). Met dit model kun je allerlei handelingen uitvoeren op de databasetabel, zoals iets toevoegen (insert), iets oproepen (select), iets wijzigen (update) of iets verwijderen (delete). Let wel: deze handelingen hebben alleen betrekking op de databasetabel waarvoor het model is gemaakt (in dit geval de tabel users).
6
Het is dus een model voor 1 tabel. Wil je bijvoorbeeld een query uitvoeren op meer dan 1 tabellen, dan moet je dit anders aanpakken. Hierover straks meer.
Leeropdracht 5.2 In deze leeropdracht leer je een select query uitvoeren op de databasetabel users met behulp van het model Application_Model_DbTable_Users() . Je kunt in de UserController dus nos steeds het model userModel gebruiken. Je moet in dit model wel een aanpassing maken, zodat de select query kan worden uitgevoerd. Het is de bedoeling dat je in deze leeropdracht een lijst maakt van de username en de nickname van iedereen in de tabel users. Normaal gesproken kun je gebruik maken van de volgende query: SELECT username,nickname FROM users Dat zou in dit geval ook kunnen, maar je kunt dat ook doen door gebruik te maken van de faciliteiten van ZF. a. Maak in het model Application_Model_DbTable_Users() een methode allUsers(). De methode allUsers() moet het resultaat van de select query returnen. In deze methode gaan we eerst de query opbouwen. Hiervoor gebruiken we de select() methode. Dit is een methode die het model overerft uit Zend_Db_Table_Abstract. Deze methode returnt een queryobject. Zo'n queryobject maak je als volgt: $statement=$this->select(); Het queryobject wordt in de variabele $statement gezet. Daarna kun je aan dit queryobject nog een aantal clauses (zoals FROM, WHERE, JOIN, enz.) toevoegen. Zo kun je een FROM clause als volg aan het queryobject toevoegen: $statement=$this->select() ->from( tabel , array(veldlijst)); In figuur 10 zie je hoe je het queryobject opbouwd in de methode allUsers().
Figuur 10
7
b. Definieer het queryobject in de methode allUsers(). Daarna moet de query worden uitgevoerd en alle resultaten in een array gezet. In figuur 11 zie je hoe dit in z'n werk gaat.
Figuur 11 De methode fetchAll() overerft het model uit de ZF klasse Zend_Db_Table_Abstract . Deze methode zet het volledige resultaat van de query in de array $result welke weer ge-returnd wordt. c. Maak de methode allUsers() af volgens figuur 11. d. Zorg er nu voor dat het resultaat via een action showallusersAction() van de controller UserController in een view zichtbaar wordt. De opmaak van deze view mag je zelf bepalen.
In de volgende leeropdracht ga je een query maken waarmee je gegevens uit twee tabellen kunt ophalen. In dit geval kun je geen gebruik meer maken van het model Application_Model_DbTable_Users() , omdat je in dit model alleen query's kunt uitvoeren op 1 tabel. Een query op meer dan 1 tabel, moet je in een niet aan een tabel gerelateerde model uitvoeren.
8
Leeropdracht 5.3 In leeropdracht 5.1 heb je een model gemaakt van de tabel users. Dit deeed je met het volgende commando met Zend Tool: zf create DbTable users Het was een model dat bruikbaar is voor 1 tabel. Voor die ene tabel kun je eigenlijk alleen wat standaardquery's in dit model uitvoeren, zoals: INSERT, UPDATE, DELETE, SELECT. Wil je een query met een JOIN uitvoeren, dan lukt dit niet meer in dit model. Daarom maken we een nieuw model, die niet gerelateerd is aan een tabel. Je kunt dit als volgt doen met behulp van Zend Tool: zf create model UsersBerichten a. Maak met Zend Tool het model UsersBerichten. Je ziet nu dat in de map models een bestand met de naam UsersBerichten.php is gemaakt (zie figuur 12). Let op: dit bestand staat niet in de map …/models/DbTable want hier staan alleen de tabelmodels.
Figuur 12 Als je dit bestand opent, dan zie je dat de klasse Application_Model_UsersBerichten geen superklasse heeft. Omdat je straks wel een aantal methodes uit de ZF klasse Zend_Db_Table nodig hebt, moet deze klasse superklasse worden van de klasse Application_Model_UsersBerichten. b. Pas de klasse Application_Model_UsersBerichten aan volgens figuur 13.
Figuur 13
9
Het model van de tabel users maakt standaard gebruik van de default databaseadapter waarvan de opties in het bestand application.ini staan. Het model Application_Model_UsersBerichten heeft nog helemaal geen adapter. Metbehulp van de superklasse Zend_Db_Table kunnen we de default adapter in de klasse Application_Model_UsersBerichten binnenhalen. Hoe dit gaat zie je in figuur 14.
Figuur 14 De methode getDefaultAdapter() overerft de klasse uit Zend_Db_Table. Deze methode returnt de default adapter en plaats deze in het attribuut $dbAdapter van de klasse. Verder staat de regel $this->dbAdapter=$this->getDefaultAdapter(); in de methode init() die als eerste wordt aangeroepen na het instantiëren van de klasse. c. Pas de klasse Application_Model_UsersBerichten aan volgens figuur 14. Een instantie van deze klasse heeft dan een databaseadapter.
d. Maak vervolgens een methode allMessages() in de klasse Application_Model_UsersBerichten In de methode ga je (net als in leeropdracht 5.2) een query opbouwen en het resultaat returnen. De query die we hier gaan gebruiken is: SELECT nickname, berichtdatum,berichttekst FROM users,berichten WHERE users.username=berichten.username Zoals je hier ziet is er een tweede tabel gebruikt. Deze tabel (berichten) heb je nog niet gemaakt. In figuur 15 zie je hoe deze tabel is opgebouwd.
Figuur 15
10
e. Maak de tabel berichten volgens figuur 15. f. Plaats enige berichten in deze tabel. Let op dat je hier berichten plaatst van bestaande gebruikers. Gebruik ook verschillende gebruikers. De eerdergenoemde query kun je ook als volgt formuleren: SELECT nickname, berichtdatum,berichttekst FROM users INNER JOIN berichten ON (users.username=berichten.username) Deze query bouw je in de methode allMessages() op, zoals je in figuur 16 ziet.
Figuur 16 Het queryobject (een selectobject) wordt nu door de methode select() van de databaseadapter dbAdapter gegenereerd. Dit queryobject wordt in de variabele $statement gezet.
Verder wordt aan het queryobject een FROM clause en een JOIN clause toegevoegd met behulp van de methodes from() en join(). De methode from() behoeft geen uitleg meer. De methode join() in de query heeft dezelfde functie als de INNER JOIN clause. De methode join() heeft 3 parameters: join (tabel, relatie: tabel1.primaire_sleutel = tabel2.vreemde sleutel, array(veldlijst)) g. Wijzig de methode allMessages() volgens figuur 16.
11
Tot slot moet de query nog worden uitgevoerd en het resultaat gereturnd worden door de methode allMessages(). In figuur 17 zie je hoe dit gaat.
Figuur 17 In tegenstelling je ziet in figuur 17 dat de methode fetchAll() een methode van de databaseadapter is. Dat was in leeropdracht 5.2 ook het geval, echter in dat geval maakte de databaseadapter al onderdeel uit van tabel model. Daarom kon je in dat geval volstaan met: $result = $this->fetchAll($statement); h. Maak de methode allMessages() af volgens figuur 17. i. Zorg er nu voor dat het resultaat via een action showallmessagesAction() van de controller UserController in een view zichtbaar wordt. De opmaak van deze view mag je zelf bepalen.
Tot slot gaan we het resultaat een beetje stylen. We doen dit met een layout. Een layout is een soort grafische schil over je webpagina. Wanneer je een layout maakt voor je project, dan zullen alle webpagina's dezelfde grafische opmaak krijgen. De content van de pagina's kan verschillen. In de volgende leeropdracht maken we een layout met een header, een menubalk en een contentvenster. Elk onderdeel in de layout (de header, de menubalk en het contentvenster) is een div . In figuur 18 zie je een voorbeeld van een layout (de aparte onderdelen zijn daarin aangegeven). De onderdelen hebben verschillende kleuren en kunnen verder naar eigen inzicht worden opgemaakt. Leeropdracht 5.3 Je gaat eerst een layout maken met zf. Dit doe je met de volgende opdracht: zf enable layout a. Maak een layout met zf voor het project blog_zend. Er is nu een map layouts in je project gemaakt, met daarin een map scripts en daar weer een bastand layout.phtml (zie figuur 19).
12
header
menubalk
contentvenster
Figuur 18
Figuur 19
13
In het bestand layout.phtml maak je de layout voor je project. b. Verwijder de tekst in het bestand layout.phtml . c. Voer nu de code van figuur 20 in het bestand layout.phtml in.
Figuur 20 Hierin zijn de drie onderdelen van het layout te zien (de div's met de id's head, menu en content. Met de opdracht layout()->content; ?> Zorg je ervoor dat de content van je webpagina in de div met het id content terechtkomt. De methode layout() is een helper binnen ZF die dit bewerkstelligd. Verder zie je dat het layout aan het stylesheet style.css is gekoppeld. Hierin kun je de opmaak van de div's instellen. d. Pas het bestand style.css aan, zodat je een indeling als in figuur 18 krijgt. De opmaak mag je zelf bepalen. e. Controleer of de layout ook voor de andere pagina's in het project geldt.
14
Opdracht 5.4 In deze opdracht moet je de applicatie uitbreiden met een mogelijkheid om de berichten van een bepaalde user te zoeken. In de volgende figuren zie je het scherm dat bij die uitbreiding behoort.
Figuur 21 In figuur 21 zie je het beginscherm dat van de nieuwe functionaliteit. In de combobox kun je een keuze maken uit een username. De usernames in de combobox moeten uit de database worden gehaald. Nadat je een username hebt geselecteerd krijg je van de geselecteerde user alle berichten te zien, inclusief de username, de nickname en de berichtdatum (zie figuur 22). Heeft de user geen berichten verzonden, dan verschijnt de tekst "Geen berichten".
Figuur 22 Om dit te realiseren volgen nu een paar hints.
15
Voor de combobox in het formulier is de code van figuur 23 gebruikt.
Figuur 23 Het attribuut multiOptions voorziet de combobox van de optiewaarden. Hierin is $u een array met de optiewaarden. Deze optiewaarden komen uit de database (de usernames). De optie onChange in combinatie met this.form.submit() maakt het mogelijk het form te verzenden als de optiewaarde verandert. Verder wordt het formulier naar zichzelf gepost. In figuur 24 zie je hoe dit in z'n werk gaat.
Figuur 24
16