5, Relaties 1:1 en 1:N
In deze les gaan we bekijken hoe je relaties legt tussen tabellen en hoe je informatie uit andere tabellen kan afdrukken in jouw view.
Een relatie in de view wordt altijd gelegd tussen één regel (row/record) en een andere tabel. Omdat de relatie vanuit de regel wordt gelegd, is er dus altijd sprake van een 1:1 of 1:N relatie. We gaan beide relaties apart bespreken en uittesten met voorbeelden.
1:1 relatie
In de SQL file is de beschrijving van de foreign key in een tabel mogelijk weggelaten. Hierdoor heeft Yii niet alle relaties goed gegeneerd. Dit kun je op twee manieren oplossen:
Bij een 1:1 relatie verwijst de regel naar een andere regel in een andere tabel. We hebben eigenlijk al gezien hoe dat werkt in de vorige les. Daar gebruikten we:
$country->capital->Name
Hiermee verwijs je vanuit de country tabel, naar de capital tabel en dan naar de Name (naam) van de hoofdstad.
Maar kijk eens naar de database, wat valt je op? Juist er is helemaal geen Capital tabel, de tabel heet City.
Dat komt, omdat capital verwijst naar een relatie en niet naar een tabel. Deze relatie is al gemaakt door de CRUD-builder en hij staat in het model Coutry in de file models/Country.php staat:
public function getCapital()
{
return $this->hasOne(City::className(), ['ID' => 'Capital']);
}
Controleer of deze relatie in jouw Country model staat!
Mocht de functie getCapital ontbreken voeg deze dan zelf toe in de file models/Country.php. Als je wilt weten waarom je dat moet doen kijk dan hier.
Alle functies (methods) die met get beginnen in een model, zijn een beschrijving van een relatie, de naam van de relatie is de naam van deze method zonder get ervoor en zonder eerste hoofdletter. Als de functie bijvoorbeeld getBigCity zou heten dan is de naam van de relatie bigCity
In de relatie wordt bepaald met welke tabel de relatie wordt gemaakt, in dit geval City en de primary- en foreig key worden benoemd, zodat Yii weet hoe de relatie wordt gelegd. In dit geval is 'ID' de primary key van City en Capital de foreign key van City.
Opdracht 1Maak een tekening van de tabellen en teken uit hoe de relatie er uit ziet (in een ERD, compleet met keys) en kijk dan naar de code en probeer te begrijpen hoe de relatie die jij getekend hebt in de code staat. |
Een 1:1 relatie wordt dus op de volgende manier gemaakt:
public function get<RELATIE NAAM>()
{
return $this-><RELATIE SOORT>(<RIGHT TABEL>::className(),
['<PRIMARY KEY RIGHT TABLE>' => '<FOREIGN KEY LEFT TABLE>']);
}
In een query zou de relatie er als volgt uit zien:
SELECT *
FROM Country
LEFT JOIN City
ON Country.Capital = City.ID
Nogmaals dit wordt allemaal voor je ingevuld als je een goede database definitie hebt waarin ook de relaties zijn gedefinieerd.
De relatie is gemaakt in het Model van Country en is dan ook alleen vanuit Country Beschikbaar. Je kunt in een view van Country dan gegevens uit de relatie afdrukken door:
$country-><relatie_naam>-><kolom naam>
In de vorige les hebben we dit gebruikt; echo $country->capital->Name;
Dus hier staat; ga naar de country tabel, neem dan de relatie die capital heet en neem daarvan de kolom Name.
Opdracht 2
Verander jouw World database met de volgende queries. Hiervoor open je de database in PHPMySQL, selecteer je de juiste database en copy/paste je het script.
Je hebt nu een extra tabel werelddeel met twee kolommen, id en naam. Vanuit de country table wordt er via de foreign key verwezen naar de tabel werelddeel. Deze structuur met een N:1 relatie zul je vaak tegenkomen.
Maak zonder Gii opnieuw te gebruiken de relatie aan in het country model en pas daarna de view overzicht aan, zodat je het werelddeel van elk land in een kolom ziet. |
1:N relatie
In de World database staat een tabel countrylanguage. In deze tabel staan talen per land welke talen er worden gesproken en per taal wordt ook nog aangegeven welk percentage van de bevolking de taal spreekt. De relatie tussen country en countrylanguage is 1:N. Namelijk in een land worden 1 of meer talen gesproken.
We willen een overzicht maken dat er als volgt uitgaat zien:
In dit voorbeeld zien we dat in Indonesië 8 talen worden gesproken. We gaan ons overzicht aanpassen, zodat we per land alle talen die er worden gesproken netjes in de tabel worden weergegeven zoals hierboven in het voorbeeld is te zien.
Hoe werkt dit?
Bij de 1:1 relatie hierboven konden we via $country->capital->Name
de naam van de hoofdstad uit de gelinkte tabel opvragen. Als dit niet duidelijk is, lees dan het stuk over 1:1 relatie hierboven nog een keer goed door!
Bij een 1:N relatie werkt het hetzelfde als bij de 1:1 relatie. Dus in het model (models/Country.php) s de relatie beschreven:
public function getCountrylanguages()
{
return $this->hasMany(Countrylanguage::className(), ['CountryCode' => 'Code']);
}
We kunnen nu vanuit de view (views/country/overzicht.php) via $country->countryLanguages->Language
de taal opvragen.
Er is alleen één belangrijk verschil, omdat we geen 1:1 relatie hebben, maar een 1:N, krijgen we geen variabele terug maar een array. We moeten het dus met een loop door het array heen lopen.
<?php foreach ($country->language as $language): ?>
<div class="row">
<div class="col-md-8"><?= $language->Language ?></div>
<div class="col-md-4"><?= $language->Percentage ?></div>
</div>
<?php endforeach; ?>
De variabele $language is willekeurig gekozen, dit is de variabele die $country->language[0], $country->language[1], $country->language[2], etc. etc.
Opdracht 3pas de view (views/country/overzicht.php) aan, zodat de talen die in een land worden gesproken worden afgedrukt zoals in het voorbeeld hierboven is aangegeven. |
Opdracht 4
Pas de tabelnaam van de tabel country aan met het volgende statement in phpmyadmin
De country table heet nu land. Gebruik het overzicht hieronder en pas de Yii code aan, zodat je nu verwijst naar de tabel land in plaats van naar de tabel country. Als het werkt, kun je de database en Yii weer terug veranderen.
|
Hieronder een schematisch overzicht van de belangrijkste onderdelen hoe je van een URL via de controller en model de juisten informatie in de view plaatst.
- Dit is de URL die de gebruiker invoert. Via de routing worden twee items uit de URL gehaald: (a) country en (b) overzicht. Item (a) is de controller-naam en item (b) is functie-naam (methodenaam). De controller zelf is te vinden in de file controllers/Country.php deze file-naam komt ook overeen met de item (a).
- In de methode actionOverzicht, waarvan de naam dus uit item (b) van de URL komt, wordt een $country object gemaakt. Dit object is verbonden met de methode Country en deze is weer beschreven in de models/Country.php en wijst vanuit daar naar de juiste database tabel.
- Vanuit de controller wordt er verwezen naar het model. In het model is beschreven waar de data werkelijk kan worden gevonden. In de methode tableNaam() wordt beschreven in welke tabel de informatie staat. In de methodes getCapital en getCountrylanguages wordt beschreven hoe een andere tabel is gekoppeld aan deze country tabel.
- In de view kunnen we het object $country dat is gemaakt in de controller gebruiken om gegevens uit de tabel af te drukken. Via de methodes getCapital en getCountrylanguages kunnen gegevens uit de andere tabellen die zijn gekoppeld aan $country ook worden afgedrukt. Afhankelijk van de soort relatie 1:1 of 1:N krijg je een waarde of een array met waardes terug.
- De view maakt (rendert) de hele pagina en stuurt deze in zijn geheel naar de browser.
Opmerking: Een methode (NL) of method (EN) is een functie in een class. Zodra je leest public function Abc() {
dan is Abs eigenlijk een methode.
Toelichting Stap 3a
public function getRelatie() {
return $this->hasMany(Relatie::className(), ['key.relatie' => 'key.thismodel']);
}
--
Question: waarom moet de functie getCapital met de hand worden toegevoegd?
De model generator van Yii kijkt naar de database relaties aan de hand van de foreign key contraints die je in phpMyAdmin kunt instellen. Je kunt de relaties ook tekenen door de juiste lijntjes te trekken in de designer module van phpMyAdmin. Als de relaties niet goed zijn gedefineerd dan zul je de realaties tussen de foreign key en de primairy key zelf moeten toevoegen in de model file.