REST API
De vragen van de quiz staan als een constante in de code. In deze les gaan we een database maken en we gaan onze app aanpassen zodat je de vragen via een REST API uit de database kan halen.
Het meeste werk is om de database en REST API te maken. Dat doen we in Yii zodat we heel weinig PHP-code hoeven te gebruiken.
Database
Om data op te slaan met Yii maken we een SQL database.
Hieronder staan twee opties voor het opzetten van een database.
Bepaal voor elke optie de voor- en nadelen. Welke optie is genormaliseerd volgens de regels (van het examen)?
Optie 1
Optie 2
Omdat het in deze les niet gaat om het maken van een mooie en goede Yii app kiezen we voor optie 2. Dit zou in de praktijk niet de beste keuze zijn omdat je hierbij te veel gelimiteerd bent. Je kunt bijvoorbeeld geen 5 antwoorden per vraag vastleggen.
Maak een sql database "quiz" met één tabel, noem die "quiz" en maak de velden zoals beschreven in Optie 2.
Yii
We maken nu een standaard Yii project en maken een CRUD van onze ene tabel.
-
Maak een nieuw Yii project met de naam "quiz".
composer create-project --prefer-dist yiisoft/yii2-app-basic
quiz
Voer dit commando uit in je CMD-window in de folder waar je het project wilt aanmaken.
Open daarna de nieuwe folder in je VSC-editor. -
Volg de stappen uit de Yii les; vergeet niet om de:
-
web.php en de
-
db.php aan te passen (kijk naar les 1 Yii).
-
- Start je development server:
php yii serve
, ga naar localhost:8080/gii en maak het model en de CRUD voor de tabel quiz.
Je hebt nu een standaard CRUD app voor één tabel. Goed, laten dan beginnen met het maken van de REST API.
Ga naar localhost:8080/quiz en gebruik de create om een paar vragen aan te maken. Je kunt de 5 vragen gebruiken die we hadden, maar je kunt ook andere vragen bedenken.
Toevoegen REST API
Het opvragen van data via een REST API is niet veel meer dan het presenteren van de gegevens in JSON. We moeten er dus voor zorgen dat onze Yii app de vragen als een JSON-bestand laten zien.
Stap 1 Security
Je kunt niet 'zomaar' vanuit een Applicatie ene andere website aanroepen en data opvragen omdat je daarmee zou kunne hacken. We moeten Yii dus vertellen dat alle API verzoeken vanaf een bepaalde server zijn toegestaan. Dat doen we door de volgende code toe te voegen aan de QuizControllers.php
return array_merge(
parent::behaviors(),
[
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
'class' => '\yii\filters\Cors',
'cors' => [
'Origin' => ['*'],
'Access-Control-Request-Method' => ['GET', 'POST'],
'Access-Control-Request-Headers' => ['*'],
],
],
]
);
}
public static function allowedDomains()
{
return [
// '*', // star allows all domains
'http://localhost:3000',
'http://test2.example.com',
];
}
Regel 9 t/m 13 geven aan dat er van uit elke website een API verzoek wordt geaccepteerd als dat een GET of POST verzoek is. Wij zullen alleen GET gebruiken.
Regel 24 zorgt er voor dat we vanuit onze development omgeving toegang hebben. Regel 25 is nodig als we later de applicatie in productie zouden moeten zetten. Voor nu zou regel 25 ook weggelaten kunnen worden.
Stap 2a SQL query and JSON bestand genereren.
Voeg deze method (functie) toe in jouw QuizController.php
public function actionApiGet() {
$sql = "select vraag, antwoord1, antwoord2, antwoord3, antwoord4, juiste_antwoord from quiz";
$result = Yii::$app->db->createCommand($sql)->queryAll();
return json_encode($result);
}
Deze method voert een query uit en vertaald de output naar JSON. Probeer maar:
localhost:8080/quiz/api-get
Je ziet zoiets als dit (ik heb het even netjes onder elkaar gezet).
[
{"vraag":"What is the capital of France","antwoord1":"New York","antwoord2":"London","antwoord3":"Paris","antwoord4":"Dublin","juiste_antwoord":"3"},
{"vraag":"Who is CEO of Tesla?","antwoord1":"Jeff Bezos","antwoord2":"Elon Musk","antwoord3":"Bill Gates","antwoord4":"Tony Stark","juiste_antwoord":"2"}
]
Stap 2b Output formaat veranderen
De output uit stap 2a ziet er best goed uit, maar het is wat anders als de JSON die we onze REACT Quiz App gebruiken.
{
questionText: 'What is the capital of France?',
answerOptions: [
{ answerText: 'New York', isCorrect: false },
{ answerText: 'London', isCorrect: false },
{ answerText: 'Paris', isCorrect: true },
{ answerText: 'Dublin', isCorrect: false },
],
},
We gaan het formaat zoals we dat in stap 2a hebben gemaakt omzetten. We lopen vraag-voor-vraag door de vragen heen en zetten welk vraag in het juiste formaat.
Met de volgende aangepaste functie zetten we het JSON-formaat om.
public function actionApiGet() {
$sql = "select vraag, antwoord1, antwoord2, antwoord3, antwoord4, juiste_antwoord from quiz";
$result = Yii::$app->db->createCommand($sql)->queryAll();
$output = [];
foreach ($result as $elem) {
array_push( $output,
[ 'questionText' => $elem['vraag'],
'answerOptions' =>
[
['answerText'=>$elem['antwoord1'],'isCorrect'=>( $elem['juiste_antwoord'] == 1 ? 'true' : 'false') ],
['answerText'=>$elem['antwoord2'],'isCorrect'=>( $elem['juiste_antwoord'] == 2 ? 'true' : 'false') ],
['answerText'=>$elem['antwoord3'],'isCorrect'=>( $elem['juiste_antwoord'] == 3 ? 'true' : 'false') ],
['answerText'=>$elem['antwoord4'],'isCorrect'=>( $elem['juiste_antwoord'] == 4 ? 'true' : 'false') ],
]
] );
}
return json_encode($output);
}
Dit is een stukje lastige code. Allereerst gebruiken we de PHP-data structuren voor een array. Het array wordt dan later omgezet in JSON.
Zet deze regels (tijdelijk) vlak voor de return zodat je beter kan zien wat er gebeurt.
echo "<pre>";
var_dump($output);
Dit is het array wat we in de loop (regel 7) opbouwen. Dit array wordt in regel 20 vertaald in een JSON structuur.
Stap 3a aanpassen REACT code
We passen de REACT code in twee kleine stapjes aan. We zetten eerst onze bestaande JSON in een file en laten onze code een file lezen. Als dat werkt dan hoeven we alleen nog maar de verwijzing van de file te veranderen in een verwijzing naar de REST API (de URL dus).
In REACT zetten we in onze public folder een nieuwe file met de naam 'data.json'.
data.json inhoud:
[
{
"questionText": "Wat is de hoofdstad van Frankrijk?",
"answerOptions": [
{ "answerText": "New York", "isCorrect": "false" },
{ "answerText": "London", "isCorrect": "false" },
{ "answerText": "Paris", "isCorrect": "true" },
{ "answerText": "Dublin", "isCorrect": "false" }
]
},
{
"questionText": "Wie is de oprichter van Tesla?",
"answerOptions": [
{ "answerText": "Jeff Bezos", "isCorrect": "false" },
{ "answerText": "Elon Musk", "isCorrect": "true" },
{ "answerText": "Bill Gates", "isCorrect": "false" },
{ "answerText": "Tony Stark", "isCorrect": "false" }
]
},
{
"questionText": "Welk bedrij was eind jaren 90 bijna failliet?",
"answerOptions": [
{ "answerText": "Apple", "isCorrect": "true" },
{ "answerText": "Intel", "isCorrect": "false" },
{ "answerText": "Amazon", "isCorrect": "false" },
{ "answerText": "Microsoft", "isCorrect": "false" }
]
}
]
Test door via de ur:
localhost:3000/data.json
de json data op te vragen.
Nu moeten we de file inlezen, dat doen we door eerst de volgende twee methods (functies) toe te voegen aan de class App.
componentDidMount() {
this.getData();
}
getData() {
fetch('./data.json')
.then(res => res.json())
.then((data) => {
this.setState({
jsonLoaded: true,
jsonData: data
});
})
.catch(console.log);
}
De method compnentDidMount is een stnadaard React functie die wordt uitgevoerd als een component (class) wordt geladen.
De getdata() method hebben we zelf gemaakt en die leest de file asynchroon im. En zet de data (de vragen dus) in een state variabele; de variabale jsonData bevat de vragen en de jsonLoaded wordt op true gezet om te laten zien dat alle data ingeladen is.
In de render method moeten we eerst wachten tot de data is ingeladen. Op de eerste reden van de render methode zetten we daarom de volgende code:
if ( ! this.state.jsonLoaded ) {
return ( <div className="app">loading...</div>)
}
In de praktijk gaat het laden erg snel maar als het laden te lang duurt wil je niet dat je App een foutmelding geeft. We wachten dus totdat de data is ingelezen. Als dat gedaan is dan doen we:
var questions=this.state.jsonData;
Hiermee is de variabele question gelijk aan de state variabele en werkt de rest van de code weer.
We kunnen ook overal waar in de code questions wordt gebruikt dat vervangen door this.state.jsonData.
Test je code en oops.... er gaat waarschijnlijk nog iets fout.
In handleAnswerOtionClicked hebben we een if-statement waarmee we bepalen of we moeten stoppen. Daarin gebruiken we de constante questions nog. Deze moeten we vervangen in this.state.jsonData
Het is if statement wordt dus:
if ( this.state.vraagNr < this.state.jsonData.length-1) {
Tenslotte kunnen we bovenaan in onze code de constante questions weghalen.
Test nog een keer goed of alles werkt!
Stap 3b aanpassen verwijzing naar file in verwijzing naar web (Yii) web site.
Als de vorige stap goed is uitgevoerd en alles werkt dan hoeven alleen nog maar de verwijzing in de fetch naar de './json' te vervangen in de url van onze Yii app.
getData() {
fetch('http://localhost:8080/quiz/api-get')
...
Je zult nu waarschijnlijk ook zien dat de data even moet laden (afhankelijk van hoe snel alles is
Nu kan je in jouw Yii app vragen aanpassen en/of toevoegen.
--