Yii - 2021

Yes It It! An easy PHP framework for rapid development of a CRUD system

Inleiding

In deze les gaan we leren wat een framework is en waarom we Yii gaan leren.
We leren verder wat MVC en CRUD is en wat heeft dat te maken heeft met een framework.

Op het examen, kerntaak 2 moet je een webapplicatie bouwen.

Je moet zelf weten hoe je dat doet. De meeste studenten kiezen voor PHP. Waarom? Het is relatief eenvoudig en de meeste web applicaties zijn ontwikkeld met PHP (bron).

Veel websites lijken op elkaar en doen vaak dezelfde dingen. Ze maken een verbinding naar de database, laten gegevens zien, je kunt aanloggen, je kunt soms gegevens veranderen en ga zo maar door.

Om niet telkens al deze 'standaard' zaken van 'scratch' af aan te moeten bouwen zijn er zogenaamde frameworks ontwikkeld. Elk framework is eigenlijk veen verzamelingen bouwblokken die je telkens op nieuw kunt gebruiken voor jouw website. Alle frameworks zijn net een beetje anders, maar er zijn ook veel overeenkomsten.

Wat zijn belangrijke zaken als je voor een framework kiest:

  1. Wordt het framework actief onderhouden; bestaat het al wat langer? Je wilt niet in iets gaan investeren dat volgend jaar misschien niet meer bestaat.
  2. Hoe moeilijk is het om een framework te leren; wat is de complexiteit?
  3. Hoe snel kun je iets ontwikkelen met het framework?
  4. Hoe flexibel is een framework; kun je er echt alles mee maken?

Bij punt 3 en 4 is het vaak het een of het ander. Of je kunt heel snel iets bouwen en je bent niet zo flexibel, of je kunt er eigenlijk alles mee ontwikkelen maar het kost veel tijd om dat te doen.

Bijvoorbeeld: PHP is super flexibel je kunt er echt alle mee ontwikkelen, het Laravel framework is ook redelijk flexibel, het Yii framework is iets minder flexibel en als je naar bijvoorbeeld WordPress kijkt (wat je ook als een soort framework zou kunnen zien), dan is dat het minst flexibel. Aan de andere kant kun je met WordPress heel snel een site maken, je bent alleen een beetje beperkt in de functionaliteiten.

Waarom Yii?

Laravel is het meest gebruikte PHP-framework. Yii staat in de meeste lijstjes ongeveer op plaats 7. Dat komt, omdat je weliswaar "snel even wat kan bouwen", maar je kunt het vrij lastig alles precies zo maken als jij het wilt. Het is minder flexibel, maar biedt wel veel structuur. Als je straks je examen maakt dan kun je waarschijnlijk ongeveer 70%-80% van je code maken met Yii, daarvoor hoef je heel weinig te programmeren en heb je tijd om je te storten op de overige 20%-30%. De examens die jullie gaan doen lenen zich goed om met Yii te maken. Je bent namelijk redelijk vrij in het ontwerp en kunt dus de 'Yii-manier' aanhouden.

Maar wat dan als ik later verder wil met (bijvoorbeeld) Laravel? Dat kan en Yii en Laravel hebben dezelfde opzet, MVC. Je zult in Laravel dus veel dingen terugvinden die je in Yii hebt geleerd.

MVC?

MVC, wat is dat? MVC staat voor Model, View en Control. Deze drie-deling wordt gemaakt in je code, zodat je eenvoudiger code kunt terugvinden.

Model Hierin staat waar de verbinding tussen jouw code en de database beschreven
View Dit is de front-end en is vooral HTML/CSS (bootstrap) met heir een daar een 'vleugje' PHP
Control Dit is waar je bepaald wat er gebeurt, als je op een knop drukt ergens dan wordt de code uit de bijbehorende control uitgevoerd.

In een plaatje ziet dat er als volgt uit. De gebruiker doet wat en stuurt daarmee een verzoek (request) naar de juiste controller. De controller 'overlegt' met het model als er iets met data moet gebeuren en het resultaat wordt via de view aan de gebruiker getoond.

image-1594413597727.png

In de komende lessen komen deze begrippen terug. Als het nu allemaal ngo wat onduidelijk is, geen probleem we gaan er mee aan de slag en zullen snel een gevoel krijgen wat MVC precies is en hoe het werkt.

CRUD?

Nog even....waar staat CRUD ook alweer voor? CRUD staat voor Create, Read, Update en Deleten. In een  CRUD applicatie kun je dus gegevens aanmaken (create), lezen (read), bewerken (update) en verwijderen (delete). 

In het examen (Kerntaak 2) moet je minimaal een CRUD kunnen maken.

Yii heeft een CRUD generator en je kan zonder te coderen een standaard CRUD applicatie maken. Super handig dus voor het examen. In de volgende les gaan we hier gelijk mee aan de slag.

Opdrachten

Opdracht 1

Leg uit wat een ontwikkel framework is. Je mag hierbij gebruik maken van internet, maar beschrijf wel in je eigen woorden wat jij denkt dat een framework is.

Bespreek met elkaar waarom jij een framework zou willen leren?

Welke PHP frameworks ken je?

Opdracht 2

Als jij een framework kiest , wat is dan belangrijk voor jou?

Zet de volgende punten op volgorde van belangrijkheid (van meest belangrijk naar minder).

 

 

--

1, De eerste web app

In deze les gaan we alles klaarmaken en installeren voor het werken met Yii. Aan het eind van de les heb je werkende Yii CRUD-applicatie.

Een CRUD-applicatie is een applicatie waarmee je records kunt aanmaken (Create), records kunt lezen (Read), records kunt aanpassen (Update) en records kunt verwijderen (Delete).

We gaan drie stappen doen om een werkende web app te maken:

  1. installeren van alle software
  2. maken van een eenvoudige database
  3. generen van code

In drie stappen hebben we straks een werkende web app in PHP gemaakt.

Voordat je met deze les begint weet je hoe je met phpMyAdmin (of ander tool) een database kunt maken en hoe je een .sql file kan importeren. Ook heb je XAMPP geïnstalleerd.

Installatie

XAMPP

Je hebt XAMPP geïnstalleerd. Voor Yii hebben dit nodig, omdat we een database gaan gebruiken.

Heb je nog geen XAMPP of werkt die niet meer dan kun je hier lezen hoe je XAMPP kan installeren.

Composer

Zorg er voor dat je composer hebt geïnstalleerd. Composer is de installer van de WEB tools.

Als we een framework als Yii of Laravel gaan gebruiken dan installeren we eigenlijk een hele grote doos met allemaal bouwblokken. Deze blokken moeten allemaal samen werken en zijn vaak afhankelijk van elkaar. De blokken worden door verschillende mensen ontwikkeld en op één of andere manier moet er voor worden gezorgd dat de juisten blokken zijn geïnstalleerd. Hiervoor dient Composer. Composer is als het ware de installer van PHP tools, deze worden vaak libraries of packages genoemd. Librabries zijn dan de blokken functionaliteit die je kunt gebruiken om een programma te maken.

https://getcomposer.org/

Tip: Zorg dat XAMPP al in geïnstalleerd voordat je composer installeert; composer heeft namelijk de locatie van php.exe nodig.

In deze korte video wordt getoond hoe je Composer kunt installeren:

Create new yii Project

We maken het Yii project world aan.

Je hoeft niet zelf een map/folder aan te maken. Dit doet composer voor jou. Zorg ervoor dat je in de terminal in de parent-folder bent.

Vul de volgende code in de terminal:

composer create-project --prefer-dist yiisoft/yii2-app-basic world

Let op dat het nieuwe Yii project wordt gemaakt in de folder waarin je de cmd box opent. Stel je wilt je jouw nieuwe project op je Desktop maken dan open je een cmd box en je tikt het commando cd Desktop in. Voer dan het composer commando uit (zoals hierboven aangegeven) en het nieuwe Yii project zal op je Desktop worden gemaakt.

Start Development Server

yii heeft een ingebouwde php server als je die opstart dan draait jouw website op http://localhost:8080/

Let wel dat je XAMPP ook blijft draaien voor je database.

In VCS open je jouw Yii project en daarna open je een nieuwe terminal.

image-1618344180761.png

In de terminal type je dan het commando in:

php yii serve

De Yii webserver draait nu. Ga naar je browser en controleer of op je via localhost:8080 jouw nieuwe Yii project ziet.

Wil je cmd box gebruiken of lukt dit niet. Kijk dan naar de instructiefilm.

Web.php

We moeten nu nog het een en ander configureren in Yii. We willen dat Yii zoveel mogelijk voor ons regelt. Om te beginnen mag Yii de routing regelen (dit wordt uitgelegd in de volgende les).

Open config/web.php - en zet het gedeelte dat urlmanager heet, uit commentaar (uncomment prettyURL). Het moet er als voglt uitzien (ongeveer op regel 50 van config/web.php).

        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
            ],
        ],

Dit is een instelling die de routing (waarover later meer) eenvoudiger maakt.

Database

Let er  op dat je database goed is gedefinieerd en dat je ook de relaties heb vastgelegd. Dat kun je doen met phpMyAdmin. De ingebouwde Yii CRUD-generator werkt alleen goed als de database en de relaties goed zijn vastgelegd.

Voor onze eerste Yii web-app gaan we gebruik maken van de world database. In deze les staat beschreven hoe je deze database kunt installeren. In deze les staat de SQL-file world.sql waarmee je de database World kan maken (klik op de link of kijk aan de linkerkant van deze pagina).

We gaan Yii vertellen welke database er moet worden gebruikt.

We openen de file config/db.php en zetten daar het volgende in:

return [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=127.0.0.1;dbname=world',
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
];

Let op dat je geen localhost gebruikt om naar je database te verwijzen. Gebruik 127.0.0.1.

Easy Debugging (optioneel)

Om straks eenvoudig te kunnen debuggen maken we een paar aanpassingen. Voor nu mag je dit ook even overslaan, we kunnen dit later ook nog doen.

Als je dit uitvoert dan kun je later in de code de volgende twee functies gebruiken:

d($objectnaam) bekijk $object de code loopt gewoon door.
dd($objectnaam) bekijk $object de uitvoering van de code stopt.

Hoe krijg je dit? Eenvoudig twee stappen.

Stap 1, New file config/functions.php

<?php
/**
 * Debug function
 * d($var);
 */
function d($var,$caller=null)
{
    if(!isset($caller)){
        $caller = debug_backtrace(1)[0];
    }
    echo '<code>Line: '.$caller['line'].'<br>';
    echo 'File: '.$caller['file'].'</code>';
    echo '<pre>';
    yii\helpers\VarDumper::dump($var, 10, true);
    echo '</pre>';
}

/**
 * Debug function with die() after
 * dd($var);
 */
function dd($var)
{
    $caller = debug_backtrace(1)[0];
    d($var,$caller);
    die();
}

Stap 2, Edit config/web.php - voeg regel 3 en 4 toe.

<?php

/* Include debug functions */
require_once(__DIR__.'/functions.php');

Eerste CRUD

1. Model maken

We gaan opnieuw naar de component builder, open http://localhost:8080/gii/model

image-1594285274135.png

De naam van het model wordt automatisch aangemaakt Country. Let op dat de 'Model Class Name' met een hoofdletter begint; kijk goed naar het voorbeeld. De naam van een class (oop) wordt altijd met een hoofdletter geschreven.

Druk onderaan op de pagina op preview, Yii laat nu zien welke file hij gaan aanmaken voor je. Druk op generate om de file te maken.

De file model/country is nu aangemaakt, we gaan later meer in op de inhoud van deze file. Wat voor nu belangrijk is om te onthouden is dat de model/country.php file de verbinding is tussen de tabel country en Yii. Elke keer als Yii informatie uit de country tabel nodig heeft dan gebruikt Yii de model/country.php file

Omdat er vanuit de country tabel wordt verwezen naar de andere twee tabellen, City en Countrylanguage maak je nog twee modellen van deze twee tabellen.

In je model directory (folder) heb je nu dus de volgende files staan:

City.php
ContactForm.php
Country.php
Countrylanguage.php
LoginForm.php
User.php
2. CRUD compleet maken met controllers en views

Ga terug naar de component builder, open http://localhost:8080/gii/model

image-1618227641051.png

Let goed op de hoofdletters, deze zijn belangrijk. Het View path wordt leeg gelaten, daar kan Yii zelf iets voor kiezen.

Druk op preview, je ziet 8 files die klaar staan om te worden aangemaakt.

image-1618227971123.png

Druk op generate om de files aan te maken.

Controller

In de directory controllers staat een file CountryController.php. Dit is de controller van de country CRUD. Al het denkwerk dat nodig is om de country CRUD aan te sturen zit in de CountryController.php file.

View

In de views/country directory staan de views. De index.php is de standaard view en deze file bevat de (voornamelijk HTML code) om de pagina weer te geven.

Weten we nog waar de controller en view ook alweer vandaan komen? Het Framework is gebouwd volgens de MVC-architectuur.  In stap 1 hebben we het model gemaakt en in deze 2de stap hebben we de view en controller gemaakt.

image-1612557541726.png

(deze sheet is uit de vorige les)

De controller is waar alle verzoeken van de gebruiker naar toe gaan. De view is de presentatielaag met voornamelijk HTML, CSS, en JavaScript. En in het model wordt de verbinding met de database gemaakt.

Klaar

Ga naar http://localhost:8080/country

image-1594288457454.png

Je hebt een CRUD van Country gemaakt, compleet met  search en sort-functionaliteiten. Je hebt ook een mooie pagina selector waarmee je daar de verschillende pagina's kan navigeren. Je kunt country's lezen/tonen, aanpassen, verwijderen en aanpassen. Probeer het maar! Wees niet bang om de database 'kapot' te maken want je kunt in een paar tellen de database opnieuw importeren.

In de volgende lessen gaan we de automatisch gebouwde applicatie stap-voor-stap aanpassen.

Voor nu kan je trots zijn op je eerste (?) web CRUD applicatie.

Extra hulp met Filmpje

Niet gelukt? Kijk in dit filmpje nog een keer alle stappen door: Instructie Yii - les1

Opdracht

Maak voor de tabellen:

ook een CRUD met de Crud Generator

Controleer op http://localhost:8080/city en http://localhost:8080/country-language of de CRUD werkt.

Plus opdrachten

  1. Ga in de code-editor naar de map views en probeer te vinden waar het overzicht Countries is gemaakt. Haal code weg zodat de kolom Surface Area niet meer wordt getoond.

    image-1612558210976.png

  2. Kun je de in dezelfde view de groene knop 'Create Country' verplaatsen en onderaan de pagina plaatsen?
    Dus het moet er zo uit te komen zien:

    image-1612558884647.png

--

 

 

 

2, Routing

In deze les leer je wat routing is en hoe je binnen Yii de routing kunt instellen.

Wat is routing? (Algemeen)

Routing is de manier waarop de webserver en jouw programma weet  welke pagina er moet worden getoond. Kijk maar eens goed naar de URL in je browser. Je kunt de URL opdelen in 5 stukjes:

image-1594292626981.png

Onderdeel Wat is het? Uitleg
Scheme protocol hoe wordt de informatie verzonden (http, https, ftp, smb, file, ...)
domain name domein domein verwijst naar een server of een groep servers.
file path path verijst naar de file die de webserver moet 'uitvoeren'
parameters variabelen hiermee kun je informatie naar de webserver sturen.
anchor verwijzing binnen de pagina hiermee kun je naar een onderdeel van de pagina springen.
Voorbeeld: https://www.roc.ovh/link/302#bkmrk-nog-een-keer-in-sche

Het schema bepaald hoe de informatie wordt opgevraagd (bij een webserver is dit meestal http en https). De domain name is de naam van de server(s). Het file path verteld de server welke informatie er moet worden getoond. De parameters zijn variabele die mee kunnen worden gegeven bijvoorbeeld om waarden die een gebruiker heeft ingevoerd naar de webserver te sturen. Tot slotte is het anchor (weinig gebruikt) een verwijzing naar een stukje binnen een pagina.

Normaal gesproken komt het file path overeen met de locatie van de bestanden op de server. Het file path is het path vanaf de document root. Stel de document root staat op jouw laptop naar:

c:\www

Je hebt XAMPP opgestart en je gaat naar:

http://localhost/opgave1/uitslag/index.php

Dan wordt de file

c:\www\opgave1\uitslag\index.php

door de webserver uitgevoerd. Het file path wordt dus achter de document root geplaatst

Hoe kom je van de URL in je browser naar de file op jouw laptop?

image-1613336806883.png

Met routing zorg je ervoor dat je ergens een vertaling wordt gemaakt tussen het URL en de file PHP die moet worden uitgevoerd.

Alles wat hierboven is beschreven is de standaard routing. Zo werkt het op jouw laptop als je plain PHP gebruikt (dat is PHP zonder framework).

Routing zorgt ervoor dat het path en eventueel de parameters de juiste code uitvoeren. Bij eenvoudige web applicaties is het path gewoon de locatie van de file op de harddisk, maar voor meer ingewikkelde programma's kan dat al snel onoverzichtelijk worden.

Met een framework werkt het daarom anders.

Routing in Yii

Standaard hoef je niets te doen voor routing. Er zit al heel veel voorgebakken in Yii.

Stel je typt in je browser in

localhost:8080/country/index

Dit path bestaat uit twee delen; country en  index.
country verwijst naar controllers/CountryController en
index verwijst naar de public function actionIndex in de class CountryController.

Het path bestaat in Yii uit twee delen, de verwijzing naar de controller en de verwijzing naar de functie/method.

De vertaling van path naar controller en function/method gaat als volgt:

  1. het eerste deel van het path is de controller-naam, maar
    1. de controller-naam begint altijd met een hoofdletter, en;
    2. als er een streepje (-) in het path staat dan is dat een nieuw woord en in de controller begint dit nieuwe woord met een hoofdletter. Voorbeeld: dit-is-het-eerste-deel wordt DitIsHetEersteDeel
  2. Het tweede deel van het path wordt opdezelfde manier vertaald maar er wordt ook nog eens het woord action voorgezet. Voorbeeld: dit-is-het-tweede-deel wordt actionDitIsHetTweedeDeel
Een paar voorbeelden:
path file public function
/kaart/overzicht KaartController actionOverzicht
/klas-lokaal/overzicht klasLokaalController actionOverzicht
/klas-lokaal/stoel-maat klasLokaalController actionStoelMaat
/klas-lokaal/index klasLokaalController actionIndex
/klas-lokaal klasLokaalController actionIndex

Zoals is te zien in de laatste regel, wordt de actionIndex uitgevoerd als het tweede deel van het path ontbreekt.

Oefening

We gaan in onze World-applicatie oefenen met routing

(1) Zet in de CountryController in de CountryController class een nieuwe public function:

public function actionHello() {
    echo "Hello World!";
    exit;
}

Opdracht: Save de aangepaste CountryController en bedenk met welke url je nu deze nieuwe functie (method) moet aanroepen.

Controleer dat, zodat je in jouw browser het volgende ziet:

image-1594296881002.png

 

(2) Maak een nieuwe file controllers/ExampleController.php en zet daar het volgende in.

<?php

namespace app\controllers;

use Yii;
use yii\web\Controller;


/**
 * CountryController implements the CRUD actions for country model.
 */
class ExampleController extends Controller
{
  public function actionSay($message = 'empty')
  {
    echo "Hello $message";
    exit;
  }
}

Opdracht: Ga nu naar http://localhost:8080/..............?message=friend maar vervang de puntjes zodat je de functie (method) actionSay van de ExampleController uit laat voeren.

Opdracht: Pas de parameter in de url aan zodat er op het scherm komt "Hello" gevolgd door jouw voornaam. Gebruik voor jouw naam één woord.

Extra

In de vorige les hebben een instelling in de config/web.php aangepast. Hierdoor hebben we eenvoudige routing aan gezet. Als je dit uit zet dan werkt de routing iets anders. Voor nu niet belangrijk maar als je wilt weten hoe het precies zit, kijk dan naar dit Engelse Youtube filmpje: https://www.youtube.com/watch?v=QcZiS43iVxU

Verder is er nog meer informatie te vinden op de officiële Yii documentatie pagina's: https://www.yiiframework.com/doc/guide/2.0/en/runtime-routing
--

 

 

3, Menu

In deze les gaan we routing zoals we dat in de vorige les hebben geleerd toepassen. We gaan een menu maken en we gaan de menu's koppelen aan de routing

Views

In de folder views zien we drie folder staan:

  1. country
    In de country folder staan de views die we met de CRUD-generator hebben gegeneerd.
  2. lay-outs,
    hierin staat de standaard lay-out van elke Yii pagina.
  3. site
    hierin staan een aantal standaard Yii pagina's zoals de about en de contact page.

Elke pagina die je laadt met Yii wordt gevormd door main.php in de lay-outs view.

In de main.php staat <?= $content ?> dit is kort voor<?php echo $content ?> . Het toont jouw pagina die in de variabele $content staat:

image-1614021559589.png

Nav Widget in main.php

In de main.php in de folder lay-outs staat de standaard Yii pagina structuur. Het menu is hier ook een onderdeel van.

In de main.php staat een stuk PHP-code die gebruik maakt van een Yii widget. Een Yii widget is eigenlijk een plug-in zoals we die ook bij WordPress kennen.

De code voor de menu bar heeft de volgende structuur:

<?php
	// plaats deze code in de main.php en vervang daarmee de standaard menu

    NavBar::begin([
      
     // hier wordt het type en de stijl van de menu bepaald
       'brandLabel' => Yii::$app->name,  // de naam van het menu
       'brandUrl' => Yii::$app->homeUrl, // de home page waar je naar toe gaat als je op de naam klikt
       'options' => [
          'class' => 'navbar navbar-expand-md navbar-dark bg-dark fixed-top',
        ],
      
   ]);
                  
                  
    echo Nav::widget([
      
      // hier worden de menu's en menu items bepaald
        'options' => ['class' => 'navbar-nav navbar-right'],
        'items' => [
            [ 'label' => 'Country',
                'items' => [
                    ['label' => 'Overzicht', 'url' => ['/country/index', ''] ],
                    ['label' => 'Voeg toe', 'url' => ['/country/index', ''] ],
                    ['label' => 'Europa', 'url' => ['/country/index', ''] ],
                ],
            ],
        ],
    ]);
                  
   NavBar::end();
 ?>                   

In Yii worden parameters voor Widget via assiociative arrays mee gegeven. Deze hebben de structuur:

[ 'key1' => 'waarde1', 'key2' => 'waarde2' ]

de waarde kan dan ook weer een assiociative array zijn:

[ 'key1' => 'waarde1', 'key2' => [ 'andere_key1'=> 'andere_waarde1', ........ ]  ]

Opgaven

  1. Pas het menu aan en plaats de code zoals hierboven beschreven:

    image-1613993167595.png

     

  2. Om een country toe te voegen moet in de controller CountryController de method/function actionCreate() worden uitgevoerd.
    Bedenk hoe je de routing naar deze pagina opzet en pas het menu aan 'voeg toe' zodat deze is gekoppeld aan actionCreate() van de CountryController.

  3. In elk menu-item staat de route, bijvoorbeeld 'country/index' en daarna staat een ''. Deze laatste lege string zijn de parameters. Als we alle landen uit Europa willen filteren dan kunnen we de volgende URL gebruiken:

    image-1613993783698.png


    Het gedeelte na de ? is de parameter. In Yii zet je een paramter als volgt in een assiociatieve array (element): 'countrySearch[Continent]'=>'Europe'
    Het hele menu-item ziet er dus als volgt uit:

    image-1615820099350.pngPas je menu aan en test uit of alle drie de menu-items goed werken.

  4.  
  5.  Maak nu een extra menu-item en noem dat Asia. Dit menu-item laat alle landen van Asia zien.

  6. Maak nu een menu-item voor alle werelddelen: North America, South America, Asia, Europe, Africa, Antarctica en Oceania.

  7. Maak een extra menu city met als items 'Overzicht' en 'Voeg toe'.

    image-1614009669414.png

--

 

4, Gridview Widget (view)

Dit In deze les leer je de basis aanpassingen in de Gridview maken.

Je leert hoe je kolommen wel of niet zichtbaar kan maken, hoe je de kolom headers kan aanpassen, hoe je de kolom breedte kan aanpassen, hoe je de inhoud van de kolom zelf kan aanpassen en als laatste leren hoe we een link kunnen  maken van een kolom.

Country overzicht aanpassen

Onze CRUD-generator (Gii) heeft voor ons een country overzicht gemaakt. Als je de CountryController.php opent dan zie je dat function (method) actionIndex de view index aanroept. Open deze index file. Deze staat in views/country.

Gridview Widget

Je ziet dat er op regel 23 een zogenaamde widget wordt aangeroepen.

Een Widget is een plug-in waarmee je functionaliteit aan Yii toevoegt. De Gridview Widget is misschien wel de meest uitgebreide widget en het zorgt ervoor dat we een mooi overzicht van onze data krijgen.

1. Kolommen aanpassen

In de parameters van deze widget zie je dat de kolommen Code, Name, Continent, Region en SurfaceArea worden afgedrukt. Je ziet ook dat bepaalde kolommen niet worden afgedrukt deze staan in commentaar.

Opdracht 1

Verander de lay-out in het country overzicht en laat de volgende kolommen in deze volgorde zien:

 

Code, Name, Population, SurfaceArea en Capital.

 

Population laat inwoneraantal zien en SurfaceArea laat oppervlakte zien.

 

2. Kopjes aanpassen

Stel we willen het kopje boven de kolom veranderen, dan kan dan als volgt. Vervang bijvoorbeeld 'SurfaceArea' in de GridView door het volgende array.

[ 'label' => 'Oppervlakte',
  'attribute' => 'SurfaceArea',
],

Je ziet dat we de surface area nu hebben vervangen door een assiociative array. Dus waar in de code het woord 'SurfaceArea' staat plaats je het assiociative array zoals hierboven is aangegeven.

Dit verteld de GridView snippet dat het label Oppervlakte moet zijn (dit wordt boven aan de pagina gezet) en dat de waarde in de kolom moet worden gevuld met 'SurfaceArea'.

Opdracht 2

Pas nu de kolom Population aan; en zet in de kolom header 'Inwoners'.

 

3. Kolom breedte aanpassen

We gaan de kolombreedte  aanpassen. Vervang het blok van net door het volgende:

[ 'label' => 'Oppervlakte',
  'attribute' => 'SurfaceArea',
  'contentOptions' => ['style' => 'width:30px; white-space: normal;'],
],
Opdracht 3

Pas de breedte van de kolom Inwoners (Population) ook aan; zet de breedte op 30 pixels.

 

4. Waarde van kolom aanpassen

Stel we willen km2 achter de oppervlakte plaatsen, dan kunnen we de waarde van de kolom als volgt aanpassen:

[
	'label' => 'Oppervlakte',
    'attribute' => 'SurfaceArea',
	'format' => 'raw',
    'value' => function($data) {
      							  return sprintf("%8d k&#13217", $data->SurfaceArea);
                               }
]

Regel 4 t/m 7 in deze code verandert de waarde van de kolom.

Opdracht 4

 

Test uit wat de code doet, haal 'format'=> 'raw' weg en kijk wat er gebeurt.
Waarom is deze regel nodig?

Vervang regel 6 door

 

return 'hallo';

en kijk wat er gebeurt.

 

5. Link maken van een kolom

In de Capital kolom zien we geen plaatsnaam, maar een nummer. Dit is het ID van een plaats in de city tabel.

Dus de Capital kolom uit country is de foreign key die wijst naar de primary key ID in city.

In de city view kun je een plaats naam zoeken door bijvoorbeeld de volgende URL te gebruiken:

http://localhost:8080/city/index?CitySearch[ID]=179

179 is het ID van Brussels, de hoofdstad van België.

We gaan nu de value van de kolom Capital vervangen door de link.

Laten we om te beginnen de kolom Capital een beetje gaan aanpassen:

 

Opdracht 5

Vervang de kolomnaam van de kolom Capital door 'Hoofdstad'.

 

Verander de breedte van deze kolom naar 30px.

 

Vervang de value van de kolom in

 

'value' => function($data) {
return "/city/index?CitySearch[ID]=179";
}

(alle kolommen krijgen dus dezelfde waarde).

 

Je ziet nu:

image-1614025244735.png

Dat is nog geen link. Dat komt omdat we met <a href="">...</a> een link moeten maken. Dat kan maar dat is lastig omdat we met " in " quotes zitten. Dus dubbele quotes in dubbele quotes. Dat kan, maar het is een gepriegel.

Met de Yii module Html kunnen we dit eenvoudiger doen.

Zet eerst boven aan in de code:

use yii\helpers\Html;

En we vervangen nu de regel die de waarde returned in:

image-1614026085274.png

We zijn er bijna, we moeten nu alleen de 179 nog variabel maken.

image-1614026443516.png

Opdracht 6

 

Maak je code werkend met bovenstaande code.

Als je nu op het nummer klikt ga je naar de hoofdstad van het betreffende land.

 

Verander nu de tekst van de link zodat je in de kolom Hoofdstad, in 'naar hoofdstad'.

 

Als je er op klikt dan ga je dus naar de index view van de hoofdstad.

 

Verander ook de breedte van de kolom naar 120px zodat de kolom netjes past.

 

De kolom ziet er dus als volgt uit:

image-1614026822965.png

Opdracht 7

 

(a) Zet de kolommen in de volgende volgorde:

 

Code -  Name - Hoofdstad - Population - Oppervlakte

 

(b) Verander de kolomnamen als volgt:

 

Code - Naam - Hoofdstad - Inwoners - Oppervlakte

 

(c) Zorg dat de naam van het land vet gedrukt wordt (bold).

 

(d) Haal vervolgens de volgnummers (1,2,3,4....) weg en verplaats de edit kolom (de laatste) naar voren.

Het hele overzicht ziet er uit zoals te zien is op het volgende plaatje:

 

image-1617127616569.png

Dit is het eindresultaat.

Plus opdracht **

De bevolkingsdichtheid zegt iets over de hoeveelheid inwoners in een land. Natuurlijk heeft Amerika meer inwoners dan Nederland, maar het land is ook vele groter. Om te vergelijken hoe dichtbevolkt een land is, kun je uitrekenen hoeveel mensen er gemiddeld per km2 wonen.

bevolkingsdichtheid = aantal inwoners / oppervlakte.

Stel een land heeft een oppervlakte van 100 km2 en je er wonen 200 mensen. Dan heb je in dit land  dus gemiddeld 2 inwoners per km2, want:

2 = 200 / 100

Maak nu een nieuwe kolom in het country overzicht en bereken daarin de bevolkingsdichtheid.

--

 

5, Menu - Controller - View

We gaan een eigen overzicht maken. We beginnen met het aanmaken van een menu, dan maken we een controller, en van de controller gaan we naar de view. We leren data op te halen met de controller en deze te tonen in onze eigen view.

Het ophalen van data doen we met een eenvoudige query die we in de Controller plaatsen. Vanuit de Controller gaan we via het Model naar de database. De volgorde wordt:

Gebruiker -> (kiest) menu -> controller -> model -> database -> (resultaat terug naar) controller -> view -> gebruiker.

In  deze les maken we dus het hele 'rondje'. Dit is een standaard 'rondje' dat in elke applicatie vaak terug zal komen.

Menu - Controller - View

Om te beginnen maken we een nieuwe method (=functie) in de controllers/CountryController class.

Deze controller noemen we actionOverzicht. Volgens de routing regels wordt de route wordt dus /country/overzicht.

Opdracht 1

Maak een menu item overzicht onder country dat naar /country/overzicht gaat.

image-1614620329168.png

Probeer het menu uit. Wat zie je en kun je verklaren wat je ziet?

Juist.... je krijgt een foutmelding want de routering verwijst naar de method/function actionOverzicht in de controller CountryController en die bestaat niet!

We gaan dus een stukje code toevoegen aan onze CountryController.

use yii\data\Pagination;

..
..
  
public function actionOverzicht()
{
    // dit is de query, dit is te vergelijken met select * from Country
    $countries=Country::find()->all();
  
	// de view wordt aangeroepen en het object $countries en $pagination wordt meegegeven.
    return $this->render('overzicht', [
        'countries' => $countries,
    ]);
}
Opdracht 2

Maak de function actionOverzicht in de CountryController zoals hierboven is aangegeven.

 

Test opnieuw wat er gebeurt als je het menu overzicht selecteert

image-1614623472969.png What? Weer een fout? Waarom nu?

Juist het menu gaat naar de actionOverzicht en de actionOverzicht voert een query uit en roept de view overzicht op. En die view bestaat (nog) niet.

We gaan dus in de folder views/country een nieuwe file overzicht.php aanmaken en plaatsen daar de volgende code in.

<?php
    foreach ($countries as $country) {
        echo $country->Name;
        echo " - ";
        echo $country->Code;
        echo " - ";
        echo number_format($country->Population, 0, ',', ' ');
        echo "<br>";
    }
?>
Opdracht 3

Maak de view overzicht zoals hierboven is aangegeven.

 

Het overzicht is bijna niet geformatteerd.

image-1614627165703.png

Weet je nog hoe een HTML table er uit ziet?

<table>

	<tr>
    	<td> .. </td>
        <td> .. </td>
        <td> .. </td>
     </tr>
     
     <tr>
    	<td> .. </td>
        <td> .. </td>
        <td> .. </td>
     </tr>
     
</table>

Hierboven staat een skelet van een table met twee rijen en drie kolommen.

Vind je dit lastig? Kijk dan de video.

Opdracht 4

Verander de /view/country/overzicht nu zo dat het netjes in een table wordt gezet. De output komt er als volgt uit te zien:

 

image-1614627534845.png

De kolommen staan nu dus netjes onder elkaar.

 

Je hebt nu zelf een view opgebouwd zonder gebruik te maken van de Gridview widget. Het kost meer tijd en moeite om zelf een overzicht zonder Gridview widget te maken, maar je hebt wel veel meer controle over hoe jouw overzicht er uit komt te zien.

Sorteren en selecteren

Via de Yii Gridview widget hebben we geleerd dat we eenvoudig kunnen sorteren en selecteren. De Yii Gridview heeft ook beperkingen. Je kunt bijvoorbeeld niet alle landen zoeken met meer dan X inwoners. Soms wil je ook dat de gebruiker zelf niet kan selecteren. Hij krijgt dan niet alle regels uit de tabel te zien, maar bijvoorbeeld alleen de regels met de datum van vandaag.

We gaan het sorteren en selecteren via de controller coderen.

Kijk nog eens naar de CountryController bij de actionOverzicht, daar staat:

$countries=Country::find()->all();

Dit statement kijkt naar het object Country dat in het model is gemaakt en zoekt dan naar alle regels uit dit object. Dit object wordt in het model gekoppeld aan de tabel country.

Yii vertaald dit naar de query

SELECT * FROM country

We kunnen nog veel meer, kijk maar eens naar het volgende uitgebreide voorbeeld:

$countries = country::find()->where(['Continent' => 'Europe'])->orderBy(['Name' => SORT_ASC])->all();

Hier worden alle countries geselecteerd met Continent=Europe, gesorteerd op Name.

Dit is een soort Yii query; een query in Yii-formaat.

Het SQL-statement zou er zo uit zien:

SELECT * FROM country WHERE Continent = 'Europe' ORDER BY Name ASC

Op deze manier kun je dus via de code in de controller een selectie maken en de sortering aanpassen. In de volgende opdracht komt dit terug.

Opdracht 5
    • Maar een nieuw menu-item en noem dit Overzicht Europe.

    • Maak een nieuwe function actionOverzichtEurope, zet deze in de juiste controller en koppel deze aan het menu-item.

      (let op de vertaling naar de routering; in de routering staat nooit een hoofdletter en woorden worden gescheiden door een streepje "-" , zie les 2 over routing).

    • Laat in dit overzicht alle landen van Europa zien. Gebruik hiervoor het stukje code dat hierboven staat ($countries = country::find()->where.........).

    • Druk de volgende kolommen af:

Name en Surface Area

 

    • Zet de twee kolommen netjes onder elkaar

    • Formateer de Surface Aera netjes met spatie tussen de duizendtallen en lijn ze recht uit.

    • Maak een header die bold is.

    • Sorteer het overzicht op Surface Area van grootste land naar kleinste.

      Het overzicht komt er dus zo uit te zien:

image-1614629726011.png

Je hebt geleerd dat je een overzicht kan maken via een (Yii-)query. Met de query kan je de selectie en sortering aanpassingen. De query wordt geplaatst in de controller en maakt gebruik van het model. In dit voorbeeld het model van country.

Dit komt terug in het examen, daarom nog een opgave over dit onderwerp.

Opdracht 6

Pas het overzicht van Opdracht 5 aan zodat je alleen alle kleine Europese landen ziet.

 

Een land is klein als SurfaceArea < 100000


Pas hiervoor de controller aan.

 

Je hebt dus te maken met twee zoekcriteria.

Zoek zelf op internet uit hoe je in een 'Yii-query' twee zoekcriteria kan combineren met andWhere().

 

--

6, 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) van één tabel naar één of meer rijen in een andere tabel.

Omdat de relatie vanuit één 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.

Als uitgangspunt nemen onze eigen view die we in de vorigen les hebben gemaakt. We gaan een kolom toevoegen waarin de hoofdstad van el land wordt getoond.

image-1615887892142.png

1:1 relatie

Bekijk de World database. In de country tabel staat de kolom Capital, dit is een foreign key die verwijst naar de primary key ID in de city table.

image-1615888192933.png

Vanuit de country table kun je via de foreign key Capital verwijzen naar de Name in de gekoppelde tabel city.

$country->hoofdstad->Name;

Met dit statement verwijs je vanuit de country tabel, via de relatie hoofdstad naar de Name (naam) van de hoofdstad.

Nu moet de relatie hoofdstad alleen nog worden geprogrammeerd. Dit doen we in de model file van Country.

// models/Country.php

public function getHoofdstad()
{
	return $this->hasOne(City::className(), ['ID' => 'Capital']);
}

De functie getHoofdstad vertel jij Yii hoe de relatie tussen de tabel Country en City in elkaar zit.

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

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']

Opdracht 1

Lees de uitleg hierboven goed door en plaats de kolom hoofdstad in het overzicht.

 

image-1615887892142.png

Maak hiervoor twee aanpassingen:

    1. maak de relatie in het model van Country, en
    2. pas de view overzicht aan door daar de kolom Hoofdstad aan toe te voegen.

 

We gaan de Hoofdstad clickable maken. Als je naar http://localhost:8080/city gaat dan zie je het overzicht van steden. Klik op een oogje op een van de steden. Let op de URL. Je ziet http://localhost:8080/city/view?id=<NR> als url en <NR> is het ID van de stad.

Dus als je bijvoorbeeld klikt op http://localhost:8080/city/view?id=5 dan zie je informatie over Amsterdam.

Opdracht 2

Maak elke hoofdstad in het overzicht datje bij  opdracht 1 hebt gemaakt clickable. Als je op de link klikt dan open je het overzicht van de stad, bijvoorbeeld http://localhost:8080/city/view?id=5.

 

Tip: je kunt Html:a() gebruiken om een link te maken. Dit is in de vorige les uitgelegd.

image-1615896595590.png

1:N relatie

In de World database staat een tabel countrylanguage. In deze tabel staat per land welke talen er worden gesproken. Per taal wordt ook aangegeven welk percentage van de bevolking deze 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:

image-1594550267859.png

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->hoofdstad['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']);
}

Controleer of je het model hebt aangemaakt van Countrylanguage tabel.

Let even op de hoofd en kleine letters!

tabel naam in database Countrylanguage
model naam CountryLanguage
relatienaam in Country.php Countrylanguages

We kunnen nu vanuit de view (views/country/overzicht.php) via $country->countryLanguages de taal opvragen.

Als je de Gridview widget gebruikt dan noem je het veld (in de Widget) country.countryLanguages

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.

Dit gaan we demonstreren met behulp van de debug functie die we in les 1 (optioneel) hadden toegevoegd. als je dit nog niet hebt gedaan dan moet je dat nu doen.

Debugging

Ga nu naar je view en voeg bovenaan in de loop waarmee je het overzicht afdrukt, de regel dd($countries) toe. dd staat voor dump & die. Dus dd() laat de variabele zien en stopt met het uitvoeren van het programma.

<?php
	....
	echo "<table border=1>";
	....
	foreach ($countries as $country) {
		...
 		// hier staat al code voor eerste kolommen
 		...
    	// voeg hier deze kolom toe
    	echo "<td>";
    	d($country->countrylanguages);
    	echo "</td>";   
 		...
}
...

Als we even de border van de tabel aanzetten (regel 3) dan is het iets makkelijker om de output te lezen.

Als je goed kijkt dan zie je in de rechter kolom een array van objecten. In deze Yii-objecten staat telkens een 'Language'.

image-1616504143444.png

Om de taal te af te drukken moeten we eerst het element array aanwijzen:

$country->countrylanguages[0]
$country->countrylanguages[1]
$country->countrylanguages[2]
$country->countrylanguages[3]

En van elk element moeten we de 'Language' hebben. Dat kun je in de dump (plaatje hierboven) goed zien.

$country->countrylanguages[0]->Language
$country->countrylanguages[1]->Language
$country->countrylanguages[2]->Language
$country->countrylanguages[3]->Language

Je weet natuurlijk niet hoeveel talen er in elk land worden gesproken. De lengte van het array is in dit voorbeeld 4, maar het kunnen er natuurlijk meer of minder zijn.

Opdracht 3

Lees de uitleg hierboven goed door. Druk in de laatst kolom één taal af.

 

image-1616504705123.png

 

Nu hebben we één taal per land, maar in de meeste landen wordt meer dan één taal gesproken. We gaan  nu met een loop door het array $country->countrylanguages heen. Gebruik hiervoor de volgende loop.

foreach( $country->countrylanguages as $taal) {
	// vul zelf de jusite variable in om de taal af te drukken
    echo .....;
	echo " <br/>";
}
Opdracht 4

Gebruik de loop zoals hierboven is aangegeven en maak het volgende overzicht.

 

image-1616505080100.png

 

Zorg ervoor (met CSS) dat de tabel er netjes uitgelijnd uit ziet zoals hierboven in het voorbeeld.

 

Je hebt nu een 1:N (één op meer) relatie gemaakt. In volgende lessen gaan we hier opnieuw mee aan de slag.

Opdracht 5

In de Countrylanguages tabel staat naast de taal ook het percentage. Dit is het percentage van de bevolking dat deze taal spreekt. Zet dit percentage tussen haakjes achter elke taal. Voorbeeld:

 

image-1616505413868.png

 

Jammer dat de percentage niet gesorteerd zijn!

Pas de relatie aan  in het Country model

    public function getCountrylanguages()
    {
        return $this->hasMany(CountryLanguage::className(), ['CountryCode' => 'Code'])
        ->orderBy(['Countrylanguage.Percentage' => SORT_ASC]);
    }
Regel 4 is erbij gekomen. let op de ; !
Als je nu het overzicht opnieuw toont dan zie je dat de talen nu oplopend (van laag naar hoog) zijn gesorteerd.
Opdracht 6

Sorteer de talen op percentage aflopend van hoog naar laag. Op die manier komt de meest gesproken van een land bovenaan.

 

image-1616506111435.png

Tip: gebruik Google om te zoeken hoe je de relatie in het model verandert zodat je aflopend kunt sorteren.

 

 

Als je in de database de relaties (via SQL) goed definieert dan worden de relaties in Yii door de model generator automatisch gemaakt. Het aanmaken van relaties in de databases valt buiten deze lessen en kan lastig zijn. Het is dus belangrijk dat je snapt hoe de relaties tussen tabellen in Yii worden gemaakt.


 

7, en nu koffie!

In deze les gaan we alles wat we tot nu toe hebben geleerd herhalen. We bouwen een nieuwe applicatie.

In de volgende les gaan we nieuwe dingen leren met deze applicatie.

Wat gaan we maken?

We gaan een applicatie maken waarin medewerkers van een coffee-to-go een bestelling kunnen opnemen. De medewerker neemt een bestelling op, vraagt de naam van de klant. De bestelling krijgt een status besteld.

De bestelling wordt klaar gemaakt en de barista (koffiemaker) zet de status van de bestelling op klaar. De medewerker ziet dat zijn bestelling klaar is, roept de naam van de klant en rekent af. De status wordt op geleverd gezet.

Opdracht 1

Opdracht 1

Maak de coffee database met de drie tabellen, menu, medewerker en bestelling. Maak voor elke tabel een CRUD.

 

Volg de stappen hieronder om dit uit te voeren.


In stap 1 maak je een nieuwe database

In stap 2-6 maak je een nieuw Yii project aan. De uitgebreide instructie met voorbeelden kan je vinden in les 1.

 

In stap 7-12 maken we de tabellen en de CRUD pagina's.

 

Voer stap 1 tot en met 13 uit.

 

Stap 1, maak een database coffee.

Stap 2, maak een nieuw Yii project coffee. Gebruik de onderstaande regel voor in de Terminal.

Let op dat je <naam van de map hier!!> vervangt voor de naam van dit project

composer create-project --prefer-dist yiisoft/yii2-app-basic <naam van de map hier!!>

Stap 3, pas de database configuratie file in de Yii aan en laat deze verwijzen naar de coffee database.

Stap 4, pas de web.php in de Yii config aan en zet smart routing aan (uncomment het gedeelte bij 'urlManager').

Stap 5, maak de file functions.php met daarin de functies dd() en d(). Dit is beschreven in les 1.

Stap 6, roep functions.php aan vanuit de web.php configuratie file. Ook dit staat in les 1 beschreven.

Stap 7, maak een tabel medewerker.

image-1616594908052.png

Stap 8, Genereer het model en de CRUD voor medewerkers en zet de volgende namen in de database.

image-1616594707813.png

Stap 9, maak een tabel menu.

image-1616595011664.png

Stap 10, Genereer het model en de CRUD voor menu en zet de volgende namen en prijzen in de database.

image-1616594739223.png

Stap 11, Maak een tabel bestelling

image-1616595477669.png

Stap 12, Genereer het model en de CRUD voor bestelling. (er hoeft geen data in de database gezet te worden voor Bestelling)

We hebben nu een tweede standaard Yii applicatie en we gaan nu aanpassingen maken in het invoerformulier 'Create Bestelling'.

Als we nu een nieuwe bestelling maken dan zien we het volgende scherm:

image-1616595571596.png

Stap 13, timestamp halen we weg, want die wordt automatisch ingevoerd (default). Die hoeven we dus niet in te voeren. Open de _form.php in de view van bestelling en haal de timestamp regel er uit.

(als id er ook nog in staat dan kun je deze ook verwijderen uit het formulier)

Opdracht 2

ERD (Database Model)

(optioneel alleen als je nog examen KT1 moet doen).

Opdracht 2

Maak de coffee database met de drie tabellen, menu, medewerker en bestelling een ERD met behulp van Lucichart.

We zijn nu klaar en gaan in de volgende les onze eigen drop down menu's maken.

--

8, Form en drop down

We gaan onze koffie applicatie uitbreiden.

Om het invoeren van gegevens eenvoudiger te maken gaan we drop down menu's toevoegen.

We gaan naar twee soorten drop-downs kijken; die met vaste waarden en die met dynamische waarden (uit de database).

Bij het invoeren van een bestelling gaan we allereerst de status via een drop down invoeren.

Daarna gaan we waarde voor de medewerker ook uit een drop down halen. Deze waarde moet uit de database komen.

Drop down in Bestelling

We gaan kijken naar de bestelling tabel en CRUD.

Als we de status in de database als een enum hebben aangemaakt dan weet Yii dat de status uit beperkt aantal waarden kan hebben en als het goed is maakt Yii dan vanzelf een een drop down in het form.

Klopt dat heb jij een drop down?

Indien niet, dan kun je de code aanpassen in het form aanpassen.

In de view _form.php van Bestellingen moet de volgende regel staan:

<?= $form->field($model, 'status')->dropDownList([ 'besteld' => 'Besteld', 'klaar' => 'Klaar', 'betaald' => 'Betaald', '' => '', ], ['prompt' => '']) ?>

Deze regel laat een drop down menu in het formulier zien. Kijk eens goed naar de parameter van dropDownList.

[ 'besteld' => 'Besteld', 'klaar' => 'Klaar', 'betaald' => 'Betaald', '' => '', ], ['prompt' => '']

De eerste parameter is een associative array waarin elk element bestaat uit een key en een value.

De key is de waarde die het element uit het form krijgt (de value) en deze waarde wordt door Yii in de database gezet. De key komt dus overeen met de waarde in de database. De value van het associatieve array is wat de gebruiker op het scherm ziet.

Deze waarde kan je dus veranderen. Je kunt dus bijvoorbeeld 'betaald' veranderen in 'afgerekend'. Het enige dat dan gebeurt is dat je iets ander op het scherm ziet.

In dit voorbeeld is de key en de value gelijk (de waarde op het scherm is hetzelfde als de waarde in de database).

Dus samengevat, de key is de waarde in de database en de value is wat de gebruiker op het scherm ziet.

Stel we willen in het form van bestelling de medewerker veranderen. Dan moeten we dus de foreign key die naar medewerker verwijst veranderen. Het juiste id van de medewerker moet in de tabel bestelling worden ingevuld.

De waarde wordt dus het id, maar dat is niet wat je de gebruiker wilt laten zien.

Dus als we voor de medewerkers een drop down willen maken dan hebben we een assiociative array nodig dat er zo uit ziet:

[ '1'=> 'Ayoub', '2'=> 'Brahim','3'=> 'Carla','4'=> 'Diego','5'=> 'Eisa' ]

De keys zijn de id's die als foreign keys in de bestelling tabel staan en de namen zijn de namen van de medewerkers.

 

Opdracht 8a

Maak nu een statische drop down met de waarden zoals in het voorbeeld (Ayoub, Brahim, Carla, ....).

De waarden worden dus (nog) niet uit de database gehaald.

Inleveren
  1. Schermafdruk yii-08a-jouw-naam met het form waarin je de drop down (opengeklapt) laat zien. Maak een schermafdruk van je gehele browser.

 

Opdracht 8b, Dynamische drop down

In opdracht 8a hebben we een drop down van medewerkers gemaakt, maar we willen de lijst van medewerkers natuurlijk uit de database halen.

In deze opdracht leg ik stap-voor-stap uit wat je moet doen omdat oor elkaar te krijgen. Lees alles aandachtig door en sla geen stappen over!

Data uit de database halen doen we in de controller.

Open de BestellingController. Vanuit deze code wordt de create view aangeroepen en vanuit de create view wordt de _form.php aangeroepen. Waarom dit in twee stappen gaat leggen we later uit.

We veranderen de code in Bestelling controller:

use app\models\Medewerker;

..
..

public function actionCreate()
{
    $model = new Bestelling();
    $medewerkers = Medewerker::find()->all();

    if ($model->load(Yii::$app->request->post()) && $model->save()) {
        return $this->redirect(['view', 'id' => $model->id]);
    }

    return $this->render('create', [
        'model' => $model,
        'medewerkers' => $medewerkers
    ]);
}

De eerste regel use app\models\Medewerker; zetten we bovenaan in de BestellingController. Hiermee vertellen we Yii dat vanuit de BestellingController gebruik willen gaan maken van het model Medewerker.

In de function actionCreate die wordt aangeroepen als we een nieuwe bestelling willen maken, voegen we een object toe waarin alle medewerkers zitten. In regel 9 halen we alle medewerkers op en in regel 17 sturen we het resultaat naar de view create.php (van bestelling).

We kunnen de medewerkers ook ophalen via een sql-query. Je mag regel 9 ook vervangen in de volgende twee regels:

// dit is hetzelfde als
// $medewerkers = Medewerker::find()->all();
$sql="select * from medewerker";
$medewerkers = Yii::$app->db->createCommand($sql)->queryAll();

In de view create.php van bestelling passen we het laatste code aan

<?= $this->render('_form', [
    'model' => $model,
    'medewerkers' => $medewerkers
]) ?>

Hiermee geven we het object medewerkers weer door aan de view _form van bestelling.

In het view _form zetten we nu bovenaan in het PHP-gedeelte.

dd($medewerkers);

Dit is de debugfunctie en hiermee controleren we of we inderdaad alle medewerkers naar de view hebben gestuurd.

We hebben nu als het goed is een lijst van medewerkers in de _form maar we moeten het ombouwen naar een assiociative array.

Pas hiervoor de code aan in de view _form van bestelling.

<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\helpers\ArrayHelper;

$medewerkerList = ArrayHelper::map($medewerkers,'id','naam');
dd($medewerkerList);
?>

...
...
...

Deze code laat het eerst stukje van de view_form zien.

Met de functie ArrayHelper::map zetten we het object $medewerkers om in een assiociative array. Met dd() laten we dat zien. Probeer maar! Als het goed is, zie je het volgende:

[
    1 => 'Ayoub'
    2 => 'Brahim'
    3 => 'Carla'
    4 => 'Diego'
    5 => 'Eisa'
]

En dit is precies wat we nodig hebben om de Drop Down te maken.

Pas de regel in de view _form van bestelling waarin de user het id van de medewerker moet intypen aan. Verander deze regel in:

<?= $form->field($model, 'medewerker_id')->dropDownList($medewerkerList, ['prompt' => ''])->label('Medwerker') ?>

Als het goed is heb je hiermee een werkend menu gekregen.

image-1616605065315.png

Gelukt? Wordt de lisjt van medewerkers in de drop down uit de database gehaald?

Inleveren

  1. schermafdruk yii-08b-jouw-naam met het form waarin je de drop down van de medewerkers (opengeklapt) laat zien. Maak een schermafdruk van je gehele browser.

 

Opdracht 8c

Maak nu een drop down voor de bestelling zodat de medewerker
uit een lijstje van koffiesoorten kan kiezen bij het opnemen van
de bestelling.

image-1616605448841.png

Gebruik hiervoor het stappenplan dat hieronder is beschreven.

Het stappenplan voor een drop down

Schrijf eerst het volgende op:

In welk model komt de drop down? Dit noemen we de target.
(in opdracht 2 hierboven is dat Bestelling)

Waar komt de informatie voor de Drop Down uit? Dit noemen we de source.
(in opdracht 2 hierboven is dat Menu)

Voer nu de stappen 1-6 uit

1. Zet in de target controller welke source model we willen gebruiken.

// in controller van target
use app\models\<SourceModelnaam>;

2. Haal in de target controller in de actionCreate() de informatie uit de source op.

// sourceName bedenk jij zelf en daarin staat de informatie die in de drop down moet komen.
$sourceName = source::find()->all();

3. Geef vanuit de target controller in de actionCreate() de informatie door aan de view.

return $this->render('create', [
     'sourceName' => $sourceName,
     ....
]);

4. Open de view create in de target (view) en geef de informatie door aan _form view.

<?= $this->render('_form', [
    'sourceName' => $sourceName,
    ...
]);

5. Open de view _form in de target (view) en zet de informatie om in een assiociative array

// in dit voorbeeld worden id en naam gebruikt:
//   id is het veld dat moet worden opgeslagen
//   naam is het veld dat in de drop te zien is
$sourceNameList = ArrayHelper::map($sourceName,'id','naam');

6. Pas het invoerveld aan in de _form van de target (view).

// veld_in_de_db is het veld van de target dat in dit invoerveld wordt weggeschreven.
<?= $form->field($model, 'veld_in_de_db')->dropDownList($$sourceNameList, ['prompt' => '']) ?>

Als je alles correct gedaan hebt dan moet je nog 1 regel aanpassen in de code om de functionaliteit te gebruiken. Yii controleert automatisch of alle databasekolommen zijn ingevuld in het formulier. Dit controleren wordt validatie genoemd. Deze regels staan in het model. Omdat je geen Bestelling ID opgeeft bij het maken van een bestelling moet je deze requirement verwijderen op regel 33 van model Bestelling.

return [
	[ ['naam', 'medewerker_id', 'menu_id', 'status'], 'required'],
    ...
    ...
]

Inleveren

  1. schermafdruk yii-08c-jouw-naam met het form waarin je de drop down van de producten (opengeklapt) laat zien. Maak een schermafdruk van je gehele browser.
  2. de BestellingController.php en zet jouw naam bij de aanpassingen die je hebt gedaan.

 

Opdracht 8d

De insert en update lijken op elkaar en gebruiken hetzelfde form (_form.php).

We hebben nu de insert aangepast, maar de update nog niet. Controleer maar! We moeten nu de update dus ook nog aanpassen.

aanpassen update view

Weet je nog dat de -update view niet rechtstreeks door de actionCreate van de BestellingController wordt aangeroepen?

Hoe zit dat nu?

image-1616610519616.png

De actionCreate en actionUpdate vanuit de controller gaan bieden naar hun 'eigen' view maar via deze eigen view komen beide op hetzelfde form uit. Dat komt omdat een update en create hetzelfde form gebruiken.

Als we nu dus een update uitvoeren van de bestelling (pennetje vanuit de gridview) dan krijgen we een foutmelding. Dat komt omdat de form verwacht dat er gegevens voor de drop down worden meegestuurd.

Om dit te fixen kopiëren we de code in de controller die we in de actionCreate hebben gemaakt dus naar de actionUpdate.

Deze twee functies zijn bijna hetzelfde. Bij de create wordt alleen een nieuw object gemaakt en bij de update wordt een bestaand object ingeladen.

Nu moeten we de objecten nog doorgeven van de update view naar de _form view. Net zoals we dat ook deden bij  de create. 

Zorg ervoor dat de update weer werkt en dat de objecten op de juiste manier worden doorgegeven. Lees hiervoor de uitleg die hierboven staat.


Pas de actionUpdate in de BestellingController aan.

Pas de update view van de BestellingController aan


Zorg dat de update van een Besteliing goed werkt..

Inleveren

  1. de BestellingController.php en zet jouw naam bij de aanpassingen die je hebt gedaan.

 

Opdracht 8e

De final touch

Laten we nog wat labels aanpassen. Dit zijn de teksten die boven de invoervelden in het form staan.

Pas de labels in het form aan zodat er Medewerker, Klantnaam, Bestelling en Status Bestelling komt te staan:


image-1616611684520.png


Zoek zelf uit hoe dit moet. Tip gebruik de zoektermen:


How to change label text of the ActiveField?


Inleveren

  1. schermafdruk yii-08e-jouw-naam met het bestellingen form waarin je de aangepaste labels laat zien. Maak een schermafdruk van je gehele browser.

--


9, Drop down in de Grid View

In deze les gaan we bepaalde dingen herhalen en op een ander manier gebruiken. We leren hoe we drop downs in een Gridview plaatsen en we leren ook hoe we een relatie kunnen gebruiken in de gridview.

In de gridview van de Bestellingen pas de kolom status aan:

[
    'attribute'=>'status',
    'filter'=>array('besteld'=>'is besteld', 'klaar'=>'is klaar', 'betaald'=>'is betaald'),
],

In de database is de waarde van deze kolom besteld, klaar of betaald. Om duidelijk het verschil tussen de waarde van de kolom in de database en de getoonde waarde te laten zien zijn de getoonde waardes 'is besteld', 'is klaar' en 'ís betaald'.

Let op dat

array('besteld'=>'is besteld', 'klaar'=>'is klaar', 'betaald'=>'is betaald')

hetzelfde is als

['besteld'=>'is besteld', 'klaar'=>'is klaar', 'betaald'=>'is betaald']

Als we nu een drop down willen bij de medewerkers dan moeten we dit hetzelfde aanpakken als we in de vorige les hebben gedaan:

Bovenaan in de view zetten we:

$medewerkerList=['1'=>'test1','2'=>'test2','3'=>'test3'];

Test dit uit. Als je test1 selecteert dan selecteer je dus de medewerkers met id 1.

Vanuit de controller maken we een object dat alle medewerkers bevat. Dit object geven we door aan de grid view (index). Daar maken we van het object een list en de list plaatsen we in de kolom als value van de key 'filter'.

Dus we veranderen de kolom medewerker:

[
    'attribute' => 'medewerker_id',
    'filter'    => $medewerkerList,
],
Opdracht 1

Zorg er nu voor dat je vanuit de controller de medewerkers opvraagt en deze verstuurd naar de index view.

 

Gebruik de

ArrayHelper::map()

functie om je object om te zetten in een list.

 

En gebruik de list dan in de gridview.

In de kolom medewerker zien we nu de id's van de medewerker. Als we de naam willen afdrukken moeten we de relatie maken. Dit hebben we eerder gedaan, zet in het model van Bestelling de volgende code:

public function getMedewerkers()
{
   return $this->hasOne(Medewerker::className(), ['id' => 'medewerker_id']);
}

Plaats nu in de gridview in de index view van de bestelling:

[
    'attribute' => 'medewerker_id',
  	'label'     => 'Medewerker',
    'filter'    => $medewerkerList,
    'value'     => 'medewerkers.naam'
],

Hiermee zetten we de waarde van deze kolom op naam van de relatie medewerkers.

Opdracht 2

Pas tenslotte het label aan en zorg ervoor dat de kolom waarin de medewerker wordt getoond in de gridview (van de index view van bestelling) er als volgt uit ziet:

 

image-1616622153704.png

 

Als er een naam wordt geselecteerd dan worden alle bestellingen die door deze medewerker zijn uitgevoerd geselecteerd.

 

 

Hiermee zetten we de waarde van deze kolom op naam van de relatie medewerkers.

Opdracht 3

In deze opdracht gaan we alle stappen die we hebben uitgevoerd om de kolom Medewerker aan te passen nog een keer uitvoeren maar nu voor de kolom menu_id.

 

Pas de menu_id kolom aan in het bestellingenoverzicht (index view van Bestelling).

 

Zorg ervoor dat je met een drop down alle koffie soorten (menu) kunt selecteren. Je kunt dan dus alle bestellingen 'Americano' opzoeken.

 

image-1616622602808.png

 

Zorg ervoor dat je de juiste relatie legt tussen de Bestelling en Menu.

 

Pas dan de grid view aan zodat je in de bestelling kolom geen id's meer ziet, maar dat je de naam van de bestellingen ziet.

 

image-1616622580235.png

 

Let ook op het label. Dit is veranderd van menu_id naar Bestelling.

 

Succes!

 

--

 

 

 

 

 

 

10. View en details aanpassen

Om alle details netjes te krijgen dien je nog de view.php aan te passen zodat de labels correct staan.

Opdracht 1

Pas de gegevens van de tabel in de view.php zodanig aan dat het resultaat er ongeveer zo uit ziet als onderstaande afbeelding:

 

Let op! Er is een relatie tussen Bestelling en Medewerker. Wil je van de properties van medewerker gebruik maken doe dan dit: $model->menu->naam

 

Tip: er is ook een relatie tussen Bestelling en Menu

Screenshot-2021-05-29-140727.png

 

Opdracht 2

Boven elke pagina kun je breadcrumbs zien die aangeven waar een gebruiker zich bevindt. Als je de opdrachten gemaakt hebt volgens de instructies dan zie je dat er Bestellings staat. Dit is natuurlijk niet correct. Kun je dit aanpassen naar Bestellingen.

Tip: dit dien je te doen op meerdere views.

11. Login

Yii heeft al een (beperkte) ingebouwde login. In deze les wordt besproken hoe je dit systeem kan gebruiken.

Role based security

In role based security heeft elke gebruiker een naam en wachtwoord en heeft elke gebruiker ook een rol (role in het Engels). De role bepaald dan wat de gebruiker kan.  Je kunt de rol 'admin' hebben die bijvoorbeeld alles kan en mag. Daarnaast kun je de rol 'user' hebben die bijvoorbeeld alleen bepaalde overzichten kan maken. Een andere rol zou bijvoorbeeld 'docent' kunnen zijn en de docent kan dan bijvoorbeeld cijfers aanpassen. De rol leerling kan dan bijvoorbeeld alleen de cijfers raadplegen.

Model

De file in model 'User.php' bevat de data waarin de users staan. De users staan dus niet in de database. Dat kan wel maar dat kost iets meer tijd en voor het ontwikkelen is het lekker 'makkelijk' om de gebruikers even lokaal aan te maken.

Standaard staat dit in de User.php file

private static $users = [
      '100' => [
          'id' => '100',
          'username' => 'admin',
          'password' => 'admin',
          'authKey' => 'test100key',
          'accessToken' => '100-token',
          'role' => 'admin',
      ],
      '101' => [
          'id' => '101',
          'username' => 'demo',
          'password' => 'demo',
          'authKey' => 'test101key',
          'accessToken' => '101-token',
          'role' => 'user'
      ],
];

Regel 8 en 16 (de 'role') zijn toegevoegd aan de standaard inhoud van de file. In dit voorbeeld zijn er dus twee rollen.

Dan dien je nog een attribuut/property toe te voegen aan de User Model. Door deze als volgt aan te maken:
Voeg public $role toe bovenaan in de class User

class User extends \yii\base\BaseObject implements \yii\web\IdentityInterface
{
    public $id;
    public $username;
    public $password;
    public $authKey;
    public $accessToken;
    
    public $role; //Deze dien je nog toe te voegen aan de User.php

Dit is alles en hiermee kan kan je aanloggen in het Yii systeem.

Functionaliteiten afschermen

In elke controller staat bovenin een function behaviors(). Deze functie regelt de toegang tot de functies. Bijvoorbeeld:

public function behaviors()
{
    return [
        'verbs' => [
            'class' => VerbFilter::className(),
            'actions' => [
                'delete' => ['POST'],
            ],
        ],
        'access' => [
            'class' => AccessControl::className(),
            'rules' => [
                // when logged in, any user but yo need to be logged in!
                [ 'actions' => ['student','create', 'update-status'],
                    'allow' => true,
                 	'roles' => ['@'],
                ],
              
             	// when logged in and role = user
                [ 'actions' => [ 'rolspeler', 'update', 'call-student' ],
                    'allow' => true,
                    'roles' => ['@'],
                    'matchCallback' => function ($rule, $action) {
                        return (Yii::$app->user->identity->role == 'user');
                    }
                    ],
              	// when logged in and role = admin
                [ 'actions' => [],
                    'allow' => true,
                    'roles' => ['@'],
                        'matchCallback' => function ($rule, $action) {
                        return (Yii::$app->user->identity->role == 'admin');
                    }
                ],
            ],
        ],
        
    ];
}
Toelichting

Het eerste gedeelte is standaard en daarin wordt bepaald dat een delete alleen mag worden aangeroepen via een POST (en dus niet via een GET). Dit is niet van belang voor onze role based security.

'verbs' => [
            'class' => VerbFilter::className(),
            'actions' => [
                'delete' => ['POST'],
            ],
        ],

Het gedeelte daaronder is wel van belang. We zien drie blokjes (elk blokje begint met 'actions').

In het eerste blokje wordt bepaald dat als je bent aangelogd dat je dan de functie student, create en update-status mag gebruiken.

// when logged in, any user but yo need to be logged in!
  [ 'actions' => ['student','create', 'update-status'],
   'allow' => true,
   'roles' => ['@'],
  ],

De regel 'roles'=> ['@'] betekent iedereen die is ingelogd.
Je kunt ook 'roles'=>['?'] gebruiken en dat betekent iedereen die niet is aangelogd (guest user).

Meer uitleg kan je vinden op: https://www.yiiframework.com/doc/guide/2.0/en/security-authorization

Het tweede blokje bepaald dat als je bent aangelogd met de rol user, je de functie rolspeler, update en call-student mag gebruiken.

// when logged in and role = user
  [ 'actions' => [ 'rolspeler', 'update', 'call-student' ],
   'allow' => true,
   'roles' => ['@'],
   'matchCallback' => function ($rule, $action) {
     return (Yii::$app->user->identity->role == 'user');
   }
  ],

Het derde blokje bepaald dat je alle acties (dus alle functies) kan  gebruiken als he de rol admin hebt.

// when logged in and role = admin
  [ 'actions' => [],
   'allow' => true,
   'roles' => ['@'],
   'matchCallback' => function ($rule, $action) {
     return (Yii::$app->user->identity->role == 'admin');
   }
  ],

Opgave 1

Maak allereerst een menu voor jouw coffee app.

image-1621708382447.png

Kijk in les 3 als je niet meer weet hoe je een menu moet maken.

Opgave 2

Maak nu een drie logins: één admin en twee medewerkers Adam en jane.

Gebruik de volgende gegevens.

id username password authKey accesToken role
100 admin admin123 admin-key admin-token admin
200 Adam geheim adam-key adam-token user
201 Jane wachtwoord jane-key jane-token user

Pas de User.php in models aan. Kijk naar het voorbeeld dat in deze les is beschreven.

Je kunt de template die hieronder staat gebruiken.

private static $users = [
      '100' => [
          'id' => '100',
          'username' => '',
          'password' => '',
          'authKey' => '',
          'accessToken' => '',
          'role' => '',
      ],
      '200' => [
          'id' => '200',
          'username' => '',
          'password' => '',
          'authKey' => '',
          'accessToken' => '',
          'role' => ''
      ],
      '201' => [
          'id' => '201',
          'username' => '',
          'password' => '',
          'authKey' => '',
          'accessToken' => '',
          'role' => ''
      ],
];

Opgave 3

Wie mag wat? Pas de rechten aan aan de hand van het volgende schema.

  Create Read (index) Update Delete
Medewerkers admin medewerker/admin2) admin admin
Menus admin iedereen1) admin admin
Bestelling medewerker iedereen1) medewerker/admin2) medewerker/admin2)

1)Iedereen betekent admin, medwerker of iemand die niet is aangelogd.
2)medewerker/admin, kan ook worden gezien als iemand die is aangelogd.

Bovenstaande tabel kun je ook zo vertalen:

Een admin kan 

Medewerker Read (index) Create Update Delete
Menus Read (index) Create Update Delete
Bestelling Read (index) Create Update Delete

Een medewerker/user kan 

Medewerker Read (index)      
Menus Read (index)      
Bestelling Read (index) Create Update Delete

Een niet-ingelogde gebruiker/guest kan 

Medewerker        
Menus Read (index)      
Bestelling Read (index)      

We gaan de toegang regelen voor de create, index, update en delete van de medewerker.

Ga naar de file MedewerkerController.php in de controllers en voeg de volgende code toe:

class MedewerkerController extends Controller

{
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['POST'],
                ],
            ],
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                  
                    // when logged in as admin
                    [ 'actions' => ['create', 'update', 'delete'],
                        'allow' => true,
                        'roles' => ['@'],
                         'matchCallback' => function ($rule, $action) {
                            return (Yii::$app->user->identity->role == 'admin' );
                        }
                    ],
                  
                    // when logged in, any user but you need to be logged in!
                    [ 'actions' => ['index'],
                        'allow' => true,
                     	'roles' => ['@'],
                    ],
                  
                ],
            ],
        ];
    }
  
  ....
  ....

Maak nu zelf de toegangscontrole voor de MenuController.php en de BestellingController af.

More info

In

https://www.roc.ovh/books/yii/page/login-via-db

staat kort beschreven hoe je de gebruikers in de database kan vastleggen. Voor het examen is dit niet noodzakelijk.

--

12. Issues

Deze opdracht maakt gebruik van een bestaand project waarbij sommige zaken ontbreken of kapot zijn. Aan jou de taak om ze op te lossen

Maak gebruik van het project op Github: Garage

  1. Maak een Fork van de repository

Screenshot-2021-06-15-165045.png

  1. Clone (vanuit je eigen Github) het project naar je eigen computer
  2. In de root folder van het project staat een bestand: garage.sql
  3. Maak een database aan: garage
  4. Importeer het garage.sql met phpmyadmin
  5. Open het project in VS Code
  6. Run de volgende code: composer update (hiermee update je de benodigde pakketen voor dit project)
  7. Start de server op met php yii serve
  8. Los de onderstaande issue op: zorg ervoor dat je na elke oplossing een commit maakt
  9. Als je klaar bent push je je werk naar Github

 

Issues
  1. Part pagina is niet zichtbaar
  2. Bij maken Car is het mogelijk om klanten te selecteren maar we zien alleen voornamen, achternamen zou al veel beter zijn
  3. In de appointment index staat onder Customer een getal. Hier moet een achternaam komen.
  4. Bij maken van een reparatie worden de onderdelen (Part) niet goed weergegeven in de dropdown.
  5. Als je een Appointment, Car of Reparatie wilt updaten dan ontstaat er een error.
  6. Inloggen doet het niet vanwege een bug.
  7. Alle pagina's zijn zichtbaar zonder inloggen: klant wil dit
  8.  

 

Index

Create

Update

Delete

Appointment

Alle medewerkers

Alle medewerkers

Admin/Manager

Admin

Car

Alle medewerkers

Alle medewerkers

Admin/Manager

Admin

Repair

Alle medewerkers

Alle medewerkers

Admin/Manager

Admin

Customer

Alle medewerkers

Alle medewerkers

Admin/Manager

Admin

Parts

Iedereen

Alle medewerkers

Admin/Manager

Admin

 

13. PDF genereren (optioneel)

Je kunt data exporteren naar bijvoorbeeld een excel bestand of PDF. Dit kan ook met YII maar daarvoor heb je wel een extentie nodig die dit voor je regelt. Hoe dat in zijn werk gaat dat zie je in dit hoofdstuk.

1. Installeer de volgende extentie: yii2-export door de volgende command in de terminal van je project te zetten:

composer require kartik-v/yii2-export "*"

2. Zorg ervoor dat de extentie in je project geactiveerd is. Open web.php

Voeg de onderstaande code toe achter 'components'

'components' => [
      ],
'modules' => [
  'gridview' => ['class' => 'kartik\grid\Module']
],

3. Kies een Controller uit waar je de PDF-extentie wilt gebruiken. Stel bijvoorbeeld dat je een overzicht wilt printen van alle Projecten of alle Auto's. Je kunt in principe van alle index overzichten een pdf (of excel) maken.

In het voorbeeld hieronder maken we gebruik van een AppointmentController

Bovenin de controller zet je de volgende regel neer

use kartik\export\ExportMenu;

4. We verbouwen de boel een beetje zodat de logica klopt. In de view index.php wordt de gridview aangemaakt. Dit gaan we aanpassen

Van dit:

<!-- dit is de index.php van Appointment -->
<?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],


            'id',
            [
                'attribute' => 'appointment_id',
                'label'     => 'Appointment',
                'value'     => 'appointment.name'
            ],
            [
                'attribute' => 'part_id',
                'label'     => 'Part',
                'value'     => 'part.name'
            ],


            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]);

Naar dit:  Hiermee worden de kolommen van de tabel niet aangemaakt in de view index.php maar in de controller zelf.

<!-- dit is de index.php van Appointment -->     
<?= \kartik\grid\GridView::widget(
            [
                'dataProvider' => $dataProvider,
                'filterModel' => $searchModel,
                'columns' => $gridColumns,
            ],
        );

        // Renders a export dropdown menu
    	echo $menu;

        ?>

5. Omdat we een overzicht willen hebben van alle Afspraken, dus de index, gaan we naar de method actionIndex() van de desbetreffende controller (in dit voorbeeld de AppointmentController.

// AppointmentController.php
$gridColumns = [
            ['class' => 'yii\grid\SerialColumn'],
            'id',
            [
                'attribute' => 'appointment_id',
                'label'     => 'Appointment',
                'value'     => 'appointment.name'
            ],
            [
                'attribute' => 'part_id',
                'label'     => 'Part',
                'value'     => 'part.name'
            ],
            ['class' => 'yii\grid\ActionColumn'],
        ];


        $menu = ExportMenu::widget([
            'dataProvider' => $dataProvider,
            'columns'      => $gridColumns
        ]);

        return $this->render('index', [
            'searchModel'   => $searchModel,
            'dataProvider'  => $dataProvider,
            'gridColumns'   => $gridColumns,
            'menu'          => $menu,
        ]);

FAQ

use yii\helpers\Html;

...

Html::a('Naam van de link', ['/controller/method','id'=>'1','param2'=>'waarde'],['title'=> 'Edit',]);

Dit resulteert in de link met de naam 'Naam van de link' die verwijst naar link /controller/method?id=1&param2=waarde

Verder die je de tekst 'Edit' als je een met de muis over de link gaat (mouse over).

Hoe maak ik een button in Yii?

use yii\helpers\Html;

...

<?= Html::a('Cancel', ['index'], ['class'=>'btn btn-primary', 'title'=> 'Mouse over Text',]) ?>
  
<?= Html::a('Ok', ['create'], ['class' => 'btn btn-success', 'title'=> 'Mouse over Text',]) ?>

Bootstrap button styles: https://getbootstrap.com/docs/4.0/components/buttons/

Hoe verander ik de standaard home page in Yii?

Twee mogelijkheden:

  1. Standaard staat de home page in de site view index, views/site/index.php. Pas deze file aan.
  2. De controller SiteController.php wordt standaard uitgevoerd. Je kan hier dus een verwijzing naar andere home pagina maken. Zie voorbeeld hieronder.
public function actionIndex()
{
	return $this->redirect(['/maaltijd/index']);
}

De home page is nu /maaltijd/index.

Hoe kan ik vanuit Yii naar de vorige pagina?

use yii\helpers\Html;

...
  
<?= Html::a( 'Cancel', Yii::$app->request->referrer , ['class'=>'btn btn-primary']); ?>

Hoe kan ik selecteren en sorteren?

$countries = Country::find()->where(['Continent' => 'Europe'])
->andWhere('SurfaceArea>1000000')->orderBy(['Name' => SORT_ASC])->all();

Hierbij selecteer je alle countries waarbij Continent = 'Europe'  and waar SurfaceArea>1000000, gesorteerd op Name (oplopend, dus a...z).

Hoe kan ik in Yii invoercontrole instellen?

Dit bepaal je in je model, bijvoorbeeld:

 public function rules()
 {
    return [
      [['population', 'average_age'], 'integer'],
      [['children_count'], 'integer', 'min' => 0, 'max' => 5],
      [['first_name', 'last_name'], 'string', 'max' => 35],
      [['from_date', 'to_date'], 'date'],
      [['opmerking', 'extra_opmerking'], 'default', 'value' => null],
      [['email'], 'email'],
    ];
}            

Meer info:

https://www.yiiframework.com/doc/guide/2.0/en/tutorial-core-validators
en
https://www.yiiframework.com/doc/guide/2.0/en/input-validation

 

 

 

 

 

Trouble Shooting Yii Installatie

Eerste hulp bij installatie issues.

Yii is eigenlijk alleen afhankelijk van PHP. Als Yii dus niet werkt dan moet er worden gecontroleerd of de goede versie van XAMPP is geïnstalleerd en of deze versie ook daadwerkelijk wordt gebruikt.

Stap 1, nieuwst XAMPP

Controleer XAMPP, alles is getest met de onderstaande versie, zorg dat je deze versie of nieuwer heb geïnstalleerd en dat oude versie zijn verwijderd.

image-1600615612510.png

Stap2, welke php wordt gebruikt?

Om dit te controleren maken we een nieuwe file test.php en zetten deze in de web directory van het Yii project. In opgave 1 van de lessen, heet het project world dus we plaatsen de file test.php in de directory world/web.

Meestal is het path dus voluit c:\XAMPP\htdocs\world\web, maar dit kan per installatie verschillen.

In de file test.php komt het volgende te staan:

<?php
phpinfo();
?>

Start nu je yii server op (php yii serve).

In de browser ga je naar http://localhost:8080/test.php

Je ziet nu alle instellingen van de PHP server.

image-1600616185913.png

De geel gemarkeerde dingen zijn belangrijk. De eerste laat zien dat PDO is geïnstalleerd:

"--with-pdo-oci=c:\php-snap-build\deps_aux\oracle\x64\instantclient_12_1\sdk,shared"

De tweede laat zien welke PHP wordt gebruikt.

Loaded Configuration File     C:\xampp\php\php.ini 

Ziet dit er anders uit dan heb je mogelijk een andere PHP versie nog geïnstalleerd of die heb je geïnstalleerd gehad. Haal deze weg of de-installeer deze.

Stap 3, path

Ga naat je cmd box en type path.

Zoek in het de output van dit commando naar:

C:\xampp\php

Lastig zoeken? Kopieer de hele output naar notepad en gebruik de zoekfunctie van notepad en zoek naar php. Contoleer ook of er niet twee maal een path naar php bestaat (php komt als het goed is precies één keer voor).

Klopt het path niet dan kan je dat als volgt aanpassen.

  1. ga naar control panel
  2. zoek (rechtsbovenin) naar environment

    image-1600616724024.png

  3. Klik op "Edit the system environment variables"
  4. Klik op knop "Environment Variables"

    image-1600617058469.png

  5. Je hebt nu twee schermen (boven en onder). Zoek waar je path staat en pas het aan.
  6. Had je geen path? Maak dan een nieuwe entry in de system variables (onderste gedeelte van het scherm).

Je moet nu herstarten of uitloggen/opnieuw aanloggen.

Controleer dan opnieuw in een cmd box met het commande path het path goed staat.

--

XAMPP opstarten of afsluiten geeft foutmelding

Bij het afsluiten van XAMPP krijg je een foutmelding die zegt dat het bestand xampp-control.ini niet kan worden overschreven.

Je kunt dit verhelpen door de inhoud van deze file eerst te kopiëren, dan de file weg te gooien en daarna een nieuwe xampp-control.ini aan te maken. Zet daarin de originele inhoud, die je hebt gekopieerd terug.

De CRUD en Model page werkt niet

Je gaar naar http://localhost:8080/gii en er gebeurt niets.

Heb je de urlManager in web.php goed geconfigureerd (zie uitleg in les1)?

Gii werkt niet

Als je gii oproept dan zie je de Yii home page.

Check config/web.php is op regel 47 is dit stukje uncommented?

--

I put this in the page content for our homepage rather than in the custom HTML header where you'd only want:

Bijlagen, Notes en Voorbeelden

Voorbeelden stukjes code

Bijlagen, Notes en Voorbeelden

Unsorted Notes

Voor- nadelen Laravel v. Yii


Laravel YII
MVC
Model Eloquent Active Record
View/templating Blade PHP met 'Yii Snippets'
Routing Expliciet Impliciet / Expliciet
Migrations
(meer als optie)
Model creations command line
GUI, reverse engineered form DB
Controller Creation command line
GUI, create full CRUD
View creation ?
GUI, create full CRUD
Documentation ***
**
Active development
Add Ons / Libraries *** **
Install Base (volgens Google) 484,970 58,800



Grootste voordeel is de GUI waarmee je reverse engineered een CRUD maakt.

Install Datepicker

(in project root)

change in file composer.dev  "minimum-stability": "dev",

composer clear-cache
composer require kartik-v/yii2-widget-datepicker

https://github.com/kartik-v/yii2-widget-datepicker

Create (temporary swap on Ubuntu)
free -m

total used free shared buffers cached
Mem: 2048 357 1690 0 0 237
-/+ buffers/cache: 119 1928
Swap: 0 0 0
/bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
/sbin/mkswap /var/swap.1
/sbin/swapon /var/swap.1

https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors

Install Bootstrap 4

composer require --prefer-dist yiisoft/yii2-bootstrap4

Redirect

return $this->redirect(['view', 'id' => $model->id]);

Post form on dropdown change

<select name="status" id="status" onchange="this.form.submit()">
Bijlagen, Notes en Voorbeelden

Useful Links

Useful extensions

https://wbraganca.com/yii2extensions

Sorting/filtering in model met relaties

https://www.yiiframework.com/wiki/653/displaying-sorting-and-filtering-model-relations-on-a-gridview

Bootstrap (4) Icons

https://www.w3schools.com/icons/bootstrap_icons_glyphicons.asp

Caching example

https://www.digitalocean.com/community/tutorials/how-to-use-caching-with-the-yii-framework-to-improve-performance

Easy Debug - d() en dd()

https://www.yiiframework.com/wiki/793/debuging-variables-in-yii2

Krajee Exention

https://demos.krajee.com

Login from Database

http://code-epicenter.com/how-to-login-user-from-a-database-in-yii-framework-2/

QR Code

https://qrcode-library.readthedocs.io/en/latest/

References

https://www.tutorialspoint.com/yii/index.htm

https://riptutorial.com/yii2

 




Bijlagen, Notes en Voorbeelden

Active Records - Queries naar DB

Active Records

select

$examen = examen::find()->where(['actief' => '1'])->orderBy(['datum_van' => 'SORT_DESC'])->one();

$examen = examen::find()->where(['actief' => '1'])->orderBy(['datum_van' => 'SORT_DESC'])->all();

$gesprekSoort = gesprekSoort::find()->all();

$gesprekSoort = examenGesprekSoort::findAll(['examen_id' => $id]);

$gesprekken = gesprekSoort::find()->where([])->joinWith('examenGesprekSoorts')->all();

// only two columns and return array (instead of object).
$criterium = Criterium::find()->select('id, omschrijving')
     ->where([ 'werkprocesid' => $model->form->werkproces ])->asArray()->all();

// zoek alle distinct studenten op naam en gebruik like %
$resultaat = resultaat::find()->select(['student_nummer','student_naam'])->distinct()
     ->where(['like', 'student_naam', $model->student_naam])->orderBy(['student_naam' => 'SORT_ASC'])->all();

// andWhere (na de where plaatsen)
->andWhere(['<','Bedrag', 100000])->

Insert update delete

// INSERT (table name, column values)
Yii::$app->db->createCommand()->insert('user', [
    'name' => 'Sam',
    'age' => 30,
])->execute();

// INSERT via raw Query
$sql="insert into examen_gesprek_soort (examen_id, gesprek_soort_id) values(:examenid, :gesprekid)";
$params = array(':examenid'=> $id, ':gesprekid' => $value );
$query->createCommand()->sql;
$result = Yii::$app->db->createCommand($sql)->bindValues($params)->execute();

// debug query 
$query = new Books::find()->where('author=2');
 echo $query->createCommand()->sql;

// UPDATE (table name, column values, condition)
Yii::$app->db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute();

// DELETE (table name, condition)
Yii::$app->db->createCommand()->delete('user', 'status = 0')->execute();

$sql="delete from examen_gesprek_soort where examen_id = :examenid";
$params = array(':examenid'=> $id);
$result = Yii::$app->db->createCommand($sql)->bindValues($params)->execute();

$sql="select * from student";
$result = Yii::$app->db->createCommand($sql)->queryAll(); //execute() returned only a count
Bijlagen, Notes en Voorbeelden

GridView Widget

GridView Widget

Make item clickable link
[
    'attribute'=>'naam',
    'label' => 'Studentnaam',
    'contentOptions' => ['style' => 'width:600px; white-space: normal;'],
    'format' => 'raw',
    'value' => function ($data) {
        return Html::a($data->naam, ['/examen/update?id='.$data->id],['title'=> 'Edit',]);
     },
],

// for related table values, use
'value' => 'student.locatie',
Title of column name

Change the model, example:

public function attributeLabels() {
	return [
		'id' => 'ID',
		'naam' => 'examennaam',
		'datum_van' => 'van',
		'datum_tot' => 'tot',
		'actief' => 'actief',
	];
}

The database column 'datum_van' will be showed as 'van'.

View column conditionally
[
    'class' => 'yii\grid\ActionColumn',
    'contentOptions' => ['style' => 'width:400px; white-space: normal;'],
    'header'=>'Actions',
    'template' => '{view} {update} {delete}',
    'visibleButtons'=>[
        'view'=> function($model){
              return $model->status!=1;
         },
    ]
],
Add own controller (copy)
[
    'class' => 'yii\grid\ActionColumn',
    'template' => '{download} {view} {update} {delete}',
    'buttons' => [
        'download' => function ($url) {
            return Html::a(
                '<span class="glyphicon glyphicon-arrow-down"></span>',
              	['another-controller/anotner-action', 'id' => $model->id],   // mag ook alleen url als je geen parameter wilt meegeven, dan hoef je geen array te maken.
                [
                    'title' => 'Download',
                    'data-pjax' => '0',
                ]
            );
        },
    ],
],

// Alternatief, copy button, niet vergeten in template te te zetten
    'buttons'=>[
         'copy' => function ($url, $model, $key) {
             return Html::a('<span class="glyphicon glyphicon-copy"></span>', ['copy', 'id'=>$model->id],['title'=>'Copy']);
         },
    ],   
Boolean (toggle icon)

Show boolean, if clicked, change status.

 [
   'attribute'=>'actief',
   'contentOptions' => ['style' => 'width:10px;'],
   'format' => 'raw',
       'value' => function ($data) {
         	// $status = icon 'ok' or icon 'minus' depending on $data->ctief
            $status = $data->actief ? '<span class="glyphicon glyphicon-ok"></span>' : '<span class="glyphicon glyphicon-minus"></span>';
            return Html::a($status, '/rolspeler/toggle-actief?id='.$data->id);
        }
 ],

// Controller
 public function actionToggleActief($id) {
        // function toggles boolean actief, in this example only one row can be active, so we execute two queries
        $sql="update examen set actief=1 where id = :id; update examen set actief=0 where id != :id;";
        $params = array(':id'=> $id);
        Yii::$app->db->createCommand($sql)->bindValues($params)->execute();
        return $this->redirect(['index']); // go back to view
    }

In this example only one row can be active. If you want a simple toggle use SQL statement:

update <table> set abss(active = active-1)
Drop down in grid select

https://stackoverflow.com/questions/29231013/how-can-i-use-a-simple-dropdown-list-in-the-search-box-of-gridviewwidget-yii2

[
    'attribute'=>'attribute name',
    'filter'=>array("ID1"=>"Name1","ID2"=>"Name2"),
],

// of

[
    'attribute'=>'attribute name',
    'filter'=>ArrayHelper::map(Model::find()->asArray()->all(), 'ID', 'Name'),
],

// of

[
    'attribute' => 'attribute_name',
    'value' => 'attribute_value',
    'filter' => Html::activeDropDownList(
      				$searchModel,
      				'attribute_name',
                    ArrayHelper::map(ModelName::find()->asArray()->all(), 'ID', 'Name'),
                    ['class'=>'form-control','prompt' => 'Select Category']
                ),
],

In dit voorbeeld wordt er data uit een andere table/model gehaald. De foreign  key moet worden ge-update aan de hand van de rolspelers table.

<?php

// ************************
// * in controller        *
// ************************

// pass rolspeler

$rolspeler = Rolspeler::find()->where(['actief' => '1'])->all();
return $this->render('index', [
     'searchModel' => $searchModel,
     'dataProvider' => $dataProvider,
     'rolspeler' => $rolspeler,
]); 

// fucntion for update (ajax) call from form
  
public function actionUpdateStatus($id, $status, $rolspelerid) {
    $model = $this->findModel($id);
    $model->status=$status;
    $model->rolspelerid=$rolspelerid;
    $model->save();
}

// ************************
// * in view (index)      *
// ************************

// ajax call to update record

<script>
    function changeStatus(id, status, rolspelerid) {
        // console.log(val, id);
        $.ajax({
        url: "<?= Url::to(['update-status']) ?>",
            data: {id: id, 'status': status, 'rolspelerid': rolspelerid },
            cache: false
        }).done(function (html) {
            location.reload();
        });
    }
</script>


// prepare ass. array for the drop down.

<?php $rolspelerList = ArrayHelper::map($rolspeler,'id','naam'); ?>
   
...

<?= GridView::widget([
     'dataProvider' => $dataProvider,
     'filterModel' => $searchModel,
     'columns' => [
       
      // show dropdown and call ajax script that calls the methos/function in teh controller
        
     'format' => 'raw',
         'value' => function ($model) {
             return Html::dropDownList('status', $model->status, ['0'=>'Wachten','1'=>'Loopt','2'=>'Klaar'],
             ['onchange' => "changeStatus('$model->id', $(this).val(), '$model->rolspelerid')"]);
            }],
      
...
      

From: https://www.trickyhints.com/how-to-create-dropdown-in-yii2-gridview/

Column customization

In Yii2's GridView, each column in the grid can be customized using a variety of properties. These properties allow you to define how each column behaves, how it's rendered, and how it interacts with the data provided. Here's a list of common properties that you can specify in a column in Yii2 GridView:

**attribute**: The attribute name of the data model. The value of the attribute will be displayed in this column.
**label**: The label of the column in the header.
**value**: A PHP callable that returns the content of the column. It should be used if the column content is not directly tied to a model attribute.
**format**: How the data of this column will be formatted. Common formats include 'text', 'html', 'date', 'number', etc.
**header**: The header cell content. This will override the automatically generated header cell content.
**footer**: The footer cell content. This will override the automatically generated footer cell content.
**headerOptions**: HTML attributes for the header cell tag.
**contentOptions**: HTML attributes for the data cell tag.
**footerOptions**: HTML attributes for the footer cell tag.
**visible**: Whether the column is visible. If set to false, the column will not be displayed.
**filter**: The filter input content. This can be a string or an array to specify the type of filter input.
**filterOptions**: HTML attributes for the filter cell tag.
**headerHtmlOptions**: Deprecated, use `headerOptions` instead.
**footerHtmlOptions**: Deprecated, use `footerOptions` instead.
**class**: The class name of the column. Yii2 provides several built-in column types like `yii\grid\SerialColumn`, `yii\grid\DataColumn`, `yii\grid\CheckboxColumn`, etc.
**enableSorting**: Whether sorting is enabled for this column.
**sortLinkOptions**: HTML attributes for the sorting link.
**filterInputOptions**: HTML attributes for the filter input.
**filterWidgetOptions**: Configuration options for the filter widget.
**noWrap**: Whether to disable text wrapping for this column.



Bijlagen, Notes en Voorbeelden

Filter Grid view en Model Search

In Search Model for filter

add public property
public $locatie;
add to safe in rules
 public function rules() {
    return [
        ....
        [['locatie'], 'safe'],
    ];
}
add in join
// locatie is attribute from student
$query = Gesprek::find()
            ->joinwith(['student'])

note that student is relation

public function getStudent()
{
   return $this->hasOne(Student::className(), ['id' => 'studentid']);
}
add in andFilterWhere
$query->andFilterWhere(['like', 'student.locatie', $this->locatie]);

In Search Model for sort

add sort attributes
$dataProvider->sort->attributes['locatie'] = [
     'asc' => ['student.locatie' => SORT_ASC],
     'desc' => ['student.locatie' => SORT_DESC],
];

In view

add column
[
  'attribute' => 'locatie',
  'contentOptions' => ['style' => 'width:20px;'],
  'format' => 'raw',
  'value' => 'student.locatie',
],
Bijlagen, Notes en Voorbeelden

Buttons Redirect

Example Buttons

//Cancel
<?= Html::a('Cancel', ['index'], ['class'=>'btn btn-primary', 'title'=> 'Mouse over Text',]) ?>

//Delete
<?= Html::a('Delete', ['delete', 'id' => $model->id], [
            'class' => 'btn btn-danger',
            'data' => [
                'confirm' => 'Are you sure you want to delete this item?',
                'method' => 'post',
                'title' => 'Mouse Over Text',
            ],
        ]) ?>

Back Button

 <?= Html::a( 'Cancel', Yii::$app->request->referrer , ['class'=>'btn btn-primary']); ?>

Redirect

return $this->goBack((!empty(Yii::$app->request->referrer) ? Yii::$app->request->referrer : null));
return $this->redirect(Yii::$app->request->referrer);

return $this->redirect(Yii::$app->request->referrer ?: Yii::$app->homeUrl);

return $this->goBack((!empty(Yii::$app->request->referrer) ? Yii::$app->request->referrer : null));
Bijlagen, Notes en Voorbeelden

Overzicht van URL naar view met 1:M relatie

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.

 

  1. 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).

  2. 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.

  3. 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.

  4. 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.

  5. 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']); 
}

 

 

Bijlagen, Notes en Voorbeelden

CRUD maken met Gii

Create CRUD Example

Invullen (in dit voorbeeld is het model examen al gemaakt, examen is een tabel in de database).

Model Class app\models\Examen
Search Model Class app\models\ExamenSearch
Controller Class app\controllers\ExamenController
View path leeg laten (wordt default waaarde)

 

Bijlagen, Notes en Voorbeelden

Forms

 

Active Form en Form mix

Eigen fields toevoegen in Active form, voorbeeld:

<select name="gesprek[status]" id="status">

gesprek in dit voorbeeld de model naam, en status is de naam van het form-veld.

Dit veld wordt in de standaard update automatisch gesaved.

Drop down (look-up in forms)

In controller get data for dop down, stel het model/table heet xxx.

use app\models\xxx;

...

$xxxModel = xxx::find()->all();

...

return $this->render('create', [
  'model' => $model,
  'xxxModel' => $xxxModel,
]);
Dan in form
use yii\helpers\ArrayHelper;

...

<?php
    $itemList=ArrayHelper::map($xxxModel,'id','omschrijving');
    
   	// Het id uit de drop down wordt in het huidige model ingevoerd, hier heet deze foreignKey
    echo $form->field($model, 'foreignKey')->dropDownList($itemList,['prompt'=>'Please select']);
?>
Change label
<?= $form->field($model, 'databasecolumn')->textInput()->label('Your nice label') ?> 
Bijlagen, Notes en Voorbeelden

Helpers

HTML::a
// both the same
return Html::a($data->naam, ['/examen/update?id='.$data->id],['title'=> 'Edit',]);
return Html::a($data->naam, ['/examen/update', 'id'=>$data->id], ['title'=> 'Edit',]);

// back
 <?= Html::a( 'Cancel', Yii::$app->request->referrer , ['class'=>'btn btn-primary']); ?>
Array helper
use yii\helpers\ArrayHelper;

$dropdownlist = Arrayhelper::map($student,'id','naam');

//input [ [ 'id' => '999', 'naam' => 'Mark de Groot'], [ 'id' => ..... ], [ ....], ....]
//result [ '999' => 'Mark de Groot', ...];
echo Html::a("<span style=\"color:#800000\" class=\"glyphicon glyphicon-print\"></span>",
   ['print/index', 'id'=>-99],
     [  'title' => 'Print ALL',
        'data' => [
        'confirm' => 'Are you sure you want to print this item?',
        'method' => 'post',
     ],
   ] );
Html::a("<span class=\"glyphicon glyphicon-trash\"></span>",
	['/uitslag/remove', 'studentid'=>$dataSet[$naam]['studentid'], 'examenid'=>$examenid ],
	['data-confirm' => 'Delete all results from '.$naam.' from this exam?', 'title'=> 'Verwijder uitslag']);

Html::a( [naam], [link, parameter, parameter], [options] )
Options in het bovenstaande voorbeeld: data-confirm en title. Andere option is class

Yii::$app

 Yii::$app->requestedRoute;          // route, example query/help
 Yii::$app->controller->action->id   // action, example help
Bijlagen, Notes en Voorbeelden

Bootstrap 4

Install

composer require --prefer-dist yiisoft/yii2-bootstrap4

In composer.json

...    
    "minimum-stability": "dev",
    "require": {
        ...
        "yiisoft/yii2-bootstrap4": "^1.0@dev"
...        

in site.css (optional)

// Added by MB

.nav {
    font-size: 2.4em;
}

.nav li {
    padding-right:0px;
    font-size: 1.4em;
}


ul.nav a:hover {
    color:#a94442;
    background-color: #E0E0E0;
}

h1 {
    color:#424ca9;
    font-style: italic;
}
Bijlagen, Notes en Voorbeelden

Views

Include HTML

<?= \Yii::$app->view->renderFile('@app/views/site/fileToInclude.php'); ?>

Set Flash

Terugkoppeling vanuit de controller naar de view.

In Controller

Yii::$app->session->setFlash('success', "Exam deleted.");

of

Yii::$app->session->setFlash('error', "Exam cannot be deleten, it has still forms attached to it.");

In View

  <?php if (Yii::$app->session->hasFlash('success')): ?>
    <div class="alert alert-success alert-dismissable">
         <button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
         <?= Yii::$app->session->getFlash('success') ?>
    </div>
  <?php endif; ?>

  <?php if (Yii::$app->session->hasFlash('error')): ?>
    <div class="alert alert-danger alert-dismissable">
      <button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
      <?= Yii::$app->session->getFlash('error') ?>
    </div>
  <?php endif; ?> 

Redirect

return $this->redirect(['index']);

 

Bijlagen, Notes en Voorbeelden

Two database connections

Zie; http://www.bsourcecode.com/yiiframework2/yii2-0-database-connection/

Bijlagen, Notes en Voorbeelden

Nav - Menu's

Example menu.

Menu 1 contain a confirmation dialog, menu 2 contains an external URL/Link.

<?php

echo Nav::widget([
    'encodeLabels' => false,
    'items' => [
        [   'label' => 'Menu1',
            'visible' => (isset(Yii::$app->user->identity->role) && Yii::$app->user->identity->role == 'admin'),
            'items' => [
                 ['label' => 'Item1', 'url' => ['/control/action'] ],
                 ['label' => 'Item2', 'url' => ['/control/action'] ],
                 ['label' => 'Item3', 'url' => ['/control/action'] ],
                 [  'label' => 'Item4',
                     'url' => ['/control/action'],
                      'linkOptions' => array('onclick'=>'return confirm("Are you sure? ")'),
             ],
                 ['label' => '-----------------------------------'],
                 ['label' => 'Item1', 'url' => ['/control/action'] ],
            ],
            'options' => ['class' => 'nav-item']
        ],
      
   		[   'label' => 'Menu2',
            'visible' => (isset(Yii::$app->user->identity->role) && Yii::$app->user->identity->role == 'admin'),
            'items' => [
                 ['label' => 'Item1', 'url' => ['/control/action'] ],
                 ['label' => 'Item2', 'url' => ['/control/action'] ],
                 [  'label' => 'Item3',
                    'url' => 'https://server.com/page1',
                    'template'=> '<a href="{url}" target="_blank">{label}</a>',
                    'linkOptions' => ['target' => '_blank'],
                  ],
            ],
            'options' => ['class' => 'nav-item']
        ],
    ],
]);
Split menu from main.php
// In main.php

// remove the use *navbar* lines (2)

<header>
	// remove menu here and replace it with this line
    <?php echo Yii::$app->view->renderFile('@app/views/layouts/menu.php'); ?>
</header>

// now you can put your menu in the file menu.php (same directory)
// menu.php

<?php

use yii\bootstrap4\Nav;
use yii\bootstrap4\NavBar;
use yii\bootstrap4\Html;

NavBar::begin([
    'brandLabel' => Yii::$app->name,
    'brandUrl' => Yii::$app->homeUrl,
    'options' => [
        'class' => 'navbar navbar-expand-md navbar-dark bg-dark fixed-top',
    ],
]);

echo Nav::widget([
    'options' => ['class' => 'navbar-nav'],
    'items' => [
...
...
Bijlagen, Notes en Voorbeelden

Joined Table Search

Case Gesprek has an N:1 relation with Student. Gesprek has a FK studentid The table student has a naam

Gesprek Model

Create relation

In het Model Gesprek van het object

public function getStudent()
{
    return $this->hasOne(Student::className(), ['id' => 'studentid']);
}

Gesprek View

In de GridView Widget

[
   'attribute' => 'student',
   'value' => 'student.naam',
 ],

GesprekSearch model

  1. line 3 add the public variable
  2. line 9 add student searchbox
  3. line 19 add join
  4. line 22-24 add sorting
  5. line 27 add filter in query
class GesprekSearch extends Gesprek
{
    public $student;  // ADD student!

    public function rules()
    {
        return [
            [['formid', 'rolspelerid', 'status'], 'integer'],
            [['opmerking', 'student'], 'safe'], // ADD student!
        ];
    }
    
    ...
    
    public function search($params)
    {
    
    $query = Gesprek::find()
            ->joinwith(['examen', 'form', 'student']) // ADD student!
     ...
      
     $dataProvider->sort->attributes['student'] = [ // ADD this block to suppoer sorting
            'asc' => ['student.naam' => SORT_ASC],
            'desc' => ['student.naam' => SORT_DESC],
     ];
     ...
     $query->andFilterWhere(['like', 'student.naam', $this->student]); // ADD filter!
      
    

See: https://www.yiiframework.com/wiki/653/displaying-sorting-and-filtering-model-relations-on-a-gridview

 

Bijlagen, Notes en Voorbeelden

Resources

Official docs

https://www.yiiframework.com/doc/api/2.0

Yii2 Quick Start Guide

http://quickstart.yii2tutorial.com/content/ch11#11-1

Yii2 Lessons

https://www.youtube.com/playlist?list=PLRd0zhQj3CBmusDbBzFgg3H20VxLx2mkF

Yii2 Tutorials

https://www.youtube.com/playlist?list=PLK757tmJ34tNGMy5uzD-Gn_wpjavHFbon

Beginning Yii2

https://www.youtube.com/playlist?list=PLMyGpiUTm106xkNQh9WeMsa-LXjanaLUm

Building a Youtube Clone

https://www.youtube.com/watch?v=whuIf33v2Ug

Russian Forum (translated in English)

https://translate.google.com/translate?hl=en&sl=ru&tl=en&u=https%3A%2F%2Fyiiframework.ru%2Fforum%2F\

Examples (web sites build in Yii)

  1. https://yiipowered.com/en
  2. https://monoray.ru/products/6-open-real-estate
  3. https://sakuracommerce.com/
Bijlagen, Notes en Voorbeelden

Login - Copied

Customize Authentication On Yii Framework Using MySQL Database

Just managed to find some time to play around with Yii. Yii is powerful but there is still a long way to go if we are talking about documentation for Yii framework. In Yii framework, we can see that it is very different from CodeIgniter where documentation is really structured and well understood. Nonetheless, i still feel that Yii framework is worth to explore. I managed to get my own customized authentication on Yii by adding some secure feature such as hashing, salt, key and etc. So here i am writing this tutorial to share with you more about Yii framework using MySQL database.

Requirement

Since this is more like a follow up tutorial, there are a few requirements before you start reading this tutorial.

  1. Installed Yii with a MySQL database.
  2. Setup Gii and get the user CRUD completed.

Customize Authentication - Database

Now here comes the tricky part. We need a database that stored our hashed password which is 128 bits since i am using sha512. Our data schema should looks like this,

CREATE TABLE tbl_user (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    username VARCHAR(128) NOT NULL,
    password VARCHAR(128) NOT NULL,
    email VARCHAR(128) NOT NULL
);

Well, it looks the same as the demo one so just ignore me lol. Create this table and we are ready to do some MVC. If you are following the tutorial you would most likely get a CRUD user setup. But we only have 'admin' and 'demo' login account. We would definitely want something better.

Customize Authentication - Model

In order to validate a user, we need to create a few methods in the model folder in order to authenticate and store user password. We will need to create these functions on our User.php file on our model folder.

/**
 * @return boolean validate user
 */
public function validatePassword($password, $username){
    return $this->hashPassword($password, $username) === $this->password;
}
/**
 * @return hashed value
 */
DEFINE('SALT_LENGTH', 10);
public function hashPassword($phrase, $salt = null){
    $key = 'Gf;B&yXL|beJUf-K*PPiU{wf|@9K9j5?d+YW}?VAZOS%e2c -:11ii<}ZM?PO!96';
    if($salt == '')
        $salt = substr(hash('sha512', $key), 0, SALT_LENGTH);
    else
        $salt = substr($salt, 0, SALT_LENGTH);
    return hash('sha512', $salt . $key . $phrase);
}

the two methods above is used to validate the user password during login and the other method returns a hashed password given the original plain password value. Once these two methods are pasted into the user.php file. We are done with our modal!

Customize Authentication - Controller

In controller, we need to modify the create and update handler but i will just demonstrate the create user handler. Go to your controller folder and look for UserController.php. Change the method actionCreate to the following

/**
 * Creates a new model.
 * If creation is successful, the browser will be redirected to the 'view' page.
 */
public function actionCreate()
{
    $model=new User;
 
    // Uncomment the following line if AJAX validation is needed
    // $this->performAjaxValidation($model);
 
    if(isset($_POST['User']))
    {
        $model->attributes=$_POST['User'];
        $model->password = $model->hashPassword($_POST['User']['password'], $_POST['User']['email']);
        if($model->save())
            $this->redirect(array('view','id'=>$model->ID));
        else
            $model->password = $_POST['User']['password'];
    }
 
    $this->render('create',array(
        'model'=>$model,
    ));
}

This way, we can create user with hashed password instead of plain password stored in our database.

Customize Authentication - Component

Next we need to authenticate our users. The original one just defined 'demo' and 'admin' as the only users that we are able to login. But now we have a database and a list of user and password. We should really secure our login. Here we modify our original authentication method to the following one.

public function authenticate()
{
    $username = $this->username;
    $user = User::model()->find('username=?', array($username));
    if($user === NULL)
        $this->errorCode=self::ERROR_USERNAME_INVALID;
    else if(!$user->validatePassword($this->password, $this->username))
        $this->errorCode=self::ERROR_PASSWORD_INVALID;
    else{
        $this->username = $user->username;
        $this->errorCode=self::ERROR_NONE;
 
    }
    return !$this->errorCode;
}

this allowed us to go through the users in our database table instead of the hardcoded one by using the method we wrote previously on the modal folder.

Now, our user will be authenticate using our customized authentication process rather than using the default one!

Bijlagen, Notes en Voorbeelden

Login via DB

Maak een  database tabel

CREATE TABLE `tbl_user` (
  `id` int(11) NOT NULL,
  `username` varchar(128) NOT NULL,
  `password` varchar(128) NOT NULL,
  `authKey` varchar(200) DEFAULT NULL,
  `role` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `tbl_user`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `username` (`username`);
  
ALTER TABLE `tbl_user`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
COMMIT;

Create CRUD voor tbl_user

image-1617048375766.png     

image-1617048411848.png

app\models\TblUser
app\models\TblUserSearch
app\controllers\TblUserController

Pas actionCreate voor TblUserController aan

public function actionCreate()
{
    $model = new tblUser();

    if ($model->load(Yii::$app->request->post()) ) {
        $model->password=sha1($model->password);
        $model->authKey=md5(openssl_random_pseudo_bytes(40));
        if ($model->save() ) {
            return $this->redirect(['view', 'id' => $model->id]);
        }
    }

    return $this->render('create', [
        'model' => $model,
    ]);
}

Dit zorgt ervoor dat er

Pas model User.php aan

<?php

namespace app\models;

class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
{
    public static function tableName()
    {
        return 'tbl_user';
    }

    public function rules()
    {
        return [
            [['username', 'password', 'email'], 'required'],
            [['username', 'password', 'email'], 'string', 'max' => 128],
            [['role'], 'string', 'max' => 20],
        ];
    }

    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'username' => 'Username',
            'password' => 'Password',
            'role' => 'Role',
            'email' => 'Email',
        ];
    }

    public static function findIdentity($id)
    {
        return static::findOne($id);
    }

    public static function findIdentityByAccessToken($token, $type = null)
    {
        return static::findOne(['access_token' => $token]);
    }

    public static function findByUsername($username)
    {
        return static::findOne(['username' => $username]);
    }


    public function getId()
    {
        return $this->id;
    }

    public function getAuthKey()
    {
        return $this->authKey;
    }


    public function validateAuthKey($authKey)
    {
        return $this->authKey === $authKey;
    }

 
    public function validatePassword($password)
    {
        return $this->password === sha1($password);
    }
}

Users aanmaken met CRUD tbl_user

Let op:

Get role

Yii::$app->user->identity->role

ToDo

User can change  his password

-> new form and access based on Yii:$app->user->identity->username


Bijlagen, Notes en Voorbeelden

Set Flash

Set Flash

Terugkoppeling vanuit de controller naar de view.

In Controller

Yii::$app->session->setFlash('success', "Exam deleted.");

of

Yii::$app->session->setFlash('error', "Exam cannot be deleten, it has still forms attached to it.");

In View

  <?php if (Yii::$app->session->hasFlash('success')): ?>
    <div class="alert alert-success alert-dismissable">
         <button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
         <?= Yii::$app->session->getFlash('success') ?>
    </div>
  <?php endif; ?>

  <?php if (Yii::$app->session->hasFlash('error')): ?>
    <div class="alert alert-danger alert-dismissable">
      <button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
      <?= Yii::$app->session->getFlash('error') ?>
    </div>
  <?php endif; ?> 

xxx

Bijlagen, Notes en Voorbeelden

Opdracht - Voetbal Toernooi

1. Bij de onderstaande opdracht ga je een kleine applicatie die een voetbaltournooi probeert na te bootsen. Je kunt wedstrijden bekijken, opslaan, wijzigen en verwijderen. Elke wedstrijd bestaat uit twee teams en elk team bestaat uit meerdere spelers.

2. Jouw opdracht is om de onderstaande ERD om te zetten in een CRUD applicatie. Let daarbij op de relaties tussen Team en Game

3. Maak een nieuw Yii Project aan in

composer create-project --prefer-dist yiisoft/yii2-app-basic Tournament

4. Je mag de database zelf maken (goed om zelf te oefenenen).

 

ERD

image-1624353236435.png

 

Repository: https://github.com/ROC-van-Amsterdam-College-Amstelland/tournament-examen-oefening

 

Bijlagen, Notes en Voorbeelden

RESTfull API

Om van een model een restfull API te maken, voeg de volgende controller toe:

<?php

namespace app\controllers;

use yii\rest\ActiveController;

class ApiController extends ActiveController {
    public $modelClass = 'app\models\<Model_Naam>';
}


?>

In de browser krijg je XML terug, test via Postman en je ziet dat je JSON terug krijgt.

 

Of plaats zelf een controller in
class HiscoresController extends Controller
{
    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [

                ],
                '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',
        ];
    }  

    public function beforeAction($action) 
    { 
        $this->enableCsrfValidation = false; 
        return parent::beforeAction($action); 
    }
    
  	public function actionApiPost()
    { 
        Yii::$app->response->format = \yii\web\Response:: FORMAT_JSON;
        $hiscore = new Hiscores();
        $hiscore->scenario = Hiscores::SCENARIO_CREATE;
        $hiscore->attributes = yii::$app->request->post();
      	
      	if($hiscore->validate()) {
          $hiscore->save();
        } else {
           return array('status'=>false,'data'=>$hiscore->getErrors(), 'post'=>yii::$app->request->post(), 'get'=>yii::$app->request->get() );
        }
    }
  
    public function actionApiGet() {
      $sql = "select id, name, score, datetime, ip from hiscores order by score desc";
      $result = Yii::$app->db->createCommand($sql)->queryAll();
      
      return json_encode(['data'=>$result]); // of json_encode([$result);
    }
  
    public function actionIndex()
      {
      ... // hieronder de standaard code
      ...
      ...
        
      
        
    
Bijlagen, Notes en Voorbeelden

Active - inactive zetten van een object

Je wilt een onbect hidden maken of op een of andere manier uit zetten.

In de table maak je dan een extra kolom, bijvoorbeeld
actief, type boolean, default = 1
Dan pas je het model aan:
public function rules() {
...
[['grade'], 'integer'],
...
public function attributelabels()
...
'actief' => 'Actief'
Code in de standaard grid view index
[
'attribute'=>'actief',
'contentOptions' => ['style' => 'width:10px;'],
'format' => 'raw',
'value' => function ($data) {
$status = $data->actief ? '&#10004' : '10060';
return Html::a($status, ['/student/toggle-actief?id='.$data->id],['title'=> 'Toggle Status',]);
}
],
Functie/method in controller
public function actionToggleActief($id) {
// function toggles boolean actief
$sql="update student set active=abs(active-1) where id = :id;";
$params = array(':id'=> $id);
Yii::$app->db->createCommand($sql)->bindValues($params)->execute();
return $this->redirect(Yii::$app->request->referrer);
}

Laatset regel is een return naar de previous page. Dit zorgt ervoor dat de selectie in de grid view behouden blijft.

Cést tout!

Bijlagen, Notes en Voorbeelden

Dynamic return page

In an update store referrer in Yii session and use it to return to when update is done.

// Store return URL, just before return to update view.
Yii::$app->user->returnUrl = Yii::$app->request->referrer;

// return to saved URL
$returnUrl = Yii::$app->user->returnUrl ?: ['/aaa/bbb', 'id' => $id]; // if not saved go to default       
return $this->redirect($returnUrl);

Bijlagen, Notes en Voorbeelden

Grouping rows in Gridview

Integrating collapsible groups with triangles in a Yii2 GridView requires a creative approach because GridView is primarily designed for flat data presentation. However, you can achieve this by manipulating the GridView and incorporating custom HTML and JavaScript for the collapsible functionality. Here’s a conceptual outline of how you might approach this:

Step 1: Prepare Your Data

Organize your data in a way that it can be grouped logically. Your data query should include the field(s) you intend to group by.

Step 2: Customize Gridview Rows

You'll use the beforeRow and afterRow properties of the GridView to inject custom HTML for group headers and footers. The group header will include the triangle and group title, and the group footer will serve as a marker for the end of a group.

PHP
echo GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'beforeRow' => function ($model, $key, $index, $grid) {
        static $lastGroup = null;
        $currentGroup = $model->groupAttribute; // Replace 'groupAttribute' with your actual attribute used for grouping

        if ($lastGroup !== $currentGroup) {
            $lastGroup = $currentGroup;
            return "<tr class='group-header'><td colspan='5'>
                        <div class='group-title'><span class='triangle'>&#9662;</span> {$currentGroup}
                        </div>
                    </td></tr>";
        }

        return null;
    },
    // Define your columns, and other GridView configurations here...
]);

Step 3: CSS for Styling

Add CSS for the group headers and the triangle icon. You can style the triangle to indicate collapsibility and use CSS transformations to change its orientation based on the group's collapsed state.

CSS
.group-header .triangle {
    cursor: pointer;
    display: inline-block;
    transition: transform 0.3s ease-in-out;
}

.group-header.collapsed .triangle {
    transform: rotate(-90deg); /* Pointing right when collapsed */
}

.group-content {
    display: none; /* Initially hide the content */
}

Step 4: JavaScript for Collapse/Expand Functionality

Use JavaScript to handle the click event on the group header, toggling the visibility of the group content and the orientation of the triangle icon.

JavaScript
$(document).on('click', '.group-header', function() {
    var header = $(this);
    header.toggleClass('collapsed');
    header.nextUntil('.group-header').toggle(); // This will show/hide the rows until the next group header
});

Step 5: Integration with Gridview

Caveats and Considerations

This approach is a bit of a hack around the default GridView behavior, so it requires careful implementation and testing to ensure it works smoothly across different datasets and user interactions.

Archive

Oude lessen

Archive

5, Zelf een view maken

DEZE EN VOLGENDE LESSEN WORDEN NOG AANGEPAST

Tot nu hebben we gebruik gemaakt van de GridView om data te laten zien (localhost:8080/country/index) - in deze les gaan we onze eigen view maken. Het maken van een eigen view kost meer tijd, maar je kunt meer zaken zelf aanpassen zoals de weergave (formattering) van getallen.

Menu - Controller - View

Om te beginnen maken we een nieuwe method in de controllers/CountryController class.

Deze controller noemen we actionOverzicht. Volgens de routing regels wordt de route wordt dus /country/overzicht.

Opdracht 1

Maar een menu item overzicht onder country dat naar /country/overzicht gaat.

image-1614620329168.png

Probeer het menu uit. Wat zie je en kun je verklaren wat je ziet?

Juist.... je krijgt een foutmelding want de routering verwijst naar de method/function actionOverzicht in de controller CountryController en die bestaat niet!

We gaan dus een stukje code toevoegen aan onze CountryController.

use yii\data\Pagination;

..
..
  
public function actionOverzicht()
{
    // dit is de query, dit is te vergelijken met select * from Country
    $countries=Country::find()->all();
  
	// de view wordt aangeroepen en het object $countries en $pagination wordt meegegeven.
    return $this->render('overzicht', [
        'countries' => $countries,
    ]);
}
Opdracht 2

Maak de function actionOverzicht in de CountryController zoals hierboven is aangegeven.

 

Test opnieuw wat er gebeurt als je het menu overzicht selecteert

image-1614623472969.png What? Weer een fout? Waarom nu?

Juist het menu gaat naar de actionOverzicht en de actionOverzicht voert een query uit en roept de view overzicht op. En die view bestaat (nog) niet.

We gaan dus in de folder views/country een nieuwe file overzicht.php aanmaken en plaatsen daar de volgende code in.

<?php
    foreach ($countries as $country) {
        echo $country->Name;
        echo " - ";
        echo $country->Code;
        echo " - ";
        echo number_format($country->Population, 0, ',', ' ');
        echo "<br>";
    }
?>
Opdracht 3

Maak de view overzicht zoals hierboven is aangegeven.

 

Het overzicht is bijna niet geformatteerd.

image-1614627165703.png

Weet je nog hoe een HTML table er uit ziet?

<table>

	<tr>
    	<td> .. </td>
        <td> .. </td>
        <td> .. </td>
     </tr>
     
     <tr>
    	<td> .. </td>
        <td> .. </td>
        <td> .. </td>
     </tr>
     
</table>

Hierboven staat een skelet van een table met twee rijen en drie kolommen.

Opdracht 4

Verander de /view/country/overzicht nu zo dat het netjes in een table wordt gezet. De output komt er als volgt uit te zien:

 

image-1614627534845.png

De kolommen staan nu dus netjes onder elkaar.

 

Je hebt nu zelf een view opgebouwd zonder gebruik te maken van de Gridview widget. Het kost meer tijd en moeite om een overzicht te maken maar je hebt wel veel meer controle over hoe jouw overzicht er uit komt te zien.

Sorteren en selecteren

We gaan nu een paar functies bouwen die in de Gridview widget al ingebouwd zijn. Dit gaat om sorteren en selecteren.

Kijk nog eens naar de CountryController bij de actionOverzicht, daar staat:

$countries=Country::find()->all();

Dit statement zoekt gaat naar het object country, dit staat in het model en zoekt daar alle regels (rows) in op. Yii vertaald dit nar de query

SELECT * FROM country

Maar we kunnen nog veel meer, kijk maar eens naar het volgende uitgebreide voorbeeld:

 $countries = country::find()->where(['Continent' => 'Europe'])->orderBy(['Name' => SORT_ASC])->all();

Hier worden alle countries geslecteerd met Continent=Europe, gesorteerd op Name. Het sql statement zou er zo uit zien:

SELECT * FROM country WHERE Continent = 'Europe' ORDER BY Name ASC

Op deze manier kun je dus via de code in de controller een selectie maken en de sortering aanpassen.

Opdracht 5
    • Maar een nieuw menu item en noem dit Overzicht Europe.

    • Maak een nieuw actionOverzichtEurope en koppel deze aan het menu item.
      (let op de vertaling naar de routering; in de routering staat nooit een hoofdletter en woorden worden gescheiden door - , zie les over routing).

    • Laat in dit overzicht alle landen van Europa zien, en druk de volgende kolommen af:

Name en Surface Area

 

    • Zet de twee kolommen netjes onder elkaar

    • Formateer de Surface Aera netjes met spatie tussen de duizendtallen en lijn ze recht uit.

    • Maak een header die bold is.

    • Sorteer het overzicht op Surface Area vangrootste land naar kleinste.

      Het overzicht kot er dus zo uit te zien:

image-1614629726011.png

 

Lees het commentaar in deze controller, zodat je begrijpt wat de verschillende blokken code doen.

use yii\data\Pagination;

..
..
  
public function actionOverzicht()
{
    $this->view->title = 'Search Countries';

    // dit is de query, dit is te vergelijken met select * from Country
    $query=Country::find();
  
  	// hier worden bepaald hoeveel regels er op één pagina worden getoond en hoeveel regels er in totaal zijn
    $pagination = new Pagination([
        'defaultPageSize' => 5,
        'totalCount' => $query->count(),
    ]);

	// hier wordt de query uitgevoerd, er wordt gesorteerd en er wordt slecht één pagina gelijktijdig binnen gehaald.
    $countries = $query->orderBy('name')
        ->offset($pagination->offset)
        ->limit($pagination->limit)
        ->all();

	// de view wordt aangeroepen en het object $countries en $pagination wordt meegegeven.
    return $this->render('overzicht', [
        'countries' => $countries,
        'pagination' => $pagination,
    ]);
}

View

In Yii zit standaard bootstrap(3) geïnstalleerd. In de volgende view wordt gebruik gemaakt van bootstrap(3) styles.

Dan zetten we in de file views/country/overzicht.php het volgende

<?php
use yii\helpers\Html;
use yii\widgets\LinkPager;
?>

<div class="card">
  <div class="card-body">
  <h3 class="card-title">Countries</h5>

    <table class="table" style="width: 70rem;" border=0>
      <tr>
          <th scope="col" style="width: 10rem;">Land</th>
          <th scope="col" style="width: 5rem;">Code</th>
          <th scope="col" style="width: 10rem;">Hoofdstad</th>
          <th scope="col" style="text-align: right;width: 10rem;">Inwoners</th>

      </tr>
      <?php foreach ($countries as $country): ?>
        <tr>
            <td><?= $country->Name ?></td>
            <td><?= $country->Code ?></td>
            <td><?php
              if ($country->capital) { // note: f.e. Antartica has no Capital
                echo $country->capital->Name;
              } else {
                echo "";
              }
            ?></td>
            <td style="text-align: right"><?= number_format($country->Population, 0, ',', ' '); ?></td>
        </tr>
      <?php endforeach; ?>
    </table>

  </div>
</div>

<?= LinkPager::widget(['pagination' => $pagination]) ?>

In deze HTML-code onderscheiden we enkele zaken:

  1. We hebben een table waarin we alles afdrukken.
  2. We hebben een table header waarin we de kopjes van de verschillende kolommen afdrukken.
  3. We hebben een loop waarmee we door het country object heen itereren en alle countries afdruken.
  4. We hebben een widget die ervoor zorgt dat we naar de volgende pagina kunnen springen.

Opdrachten

  1. schrijf van de vier bovenstaande punten op waar je dit precies vind in de code, noteer de regels waarop het betrekking heeft. Bijvoorbeeld: 1 heeft betrekking op regel 10-32

  2. wat (welk stukje code) zorgt ervoor dat het inwoneraantal netjes geformatteerd wordt afgedrukt?

  3. In deze code wordt de naam van de hoofdstad afgedrukt (en niet meer de foreign key zoals in ons eerdere overzicht). Op welke regel wordt de naam van de hoofdstad afgedrukt?

  4. Er staan 'landen' in de database die geen hoofdstad hebben zoals bijvoorbeeld Antartica. Verander de code, zodat er in deze gevallen een streepje (-) wordt afgedrukt.

Opmerking:

In de view zie je php-code staan tussen <?php ?> en tussen <?= ?> Dit is bijna hetzelfde:
<?= $a ?> is een verkorte notering voor <?php echo $a ?> 

 

--

Archive

6, De tweede web app

In deze les gaan we oefenen met alles wat we tot nu hebben geleerd. We zullen een database met een N:M relatie gebruiken en we zullen zien dat dat niet echt anders is als een 1:N relatie.

Stap 1, Database

In deze stap gaan we een nieuwe database maken.

Maak een nieuwe database; ga naar deze pagina en installeer de student database.

Je hebt nu een database student met drie tabellen; student, vak en een koppeltabel cijfer. Een student heeft meerdere vakken een één vak kan door meerdere studenten worden gevolgd.

Stap 2, Nieuw Yii project

In deze stap gaan we een nieuw Yii project maken.

composer create-project --prefer-dist yiisoft/yii2-app-basic student

Pas de config/web.php en de config/db.php aan (kijk naar naar de eerste les als je niet meer weet hoe dit precies moet).

Tip: vergeet niet de localhost in de d.php te veranderen in 127.0.01

Start de Yii PHP server.

Stap 2, Models

In deze stap gan we de models maken.

Maak met behulp van Gii zoals we dat in de eerste les hebben gedaan een model van alle drie de tabellen. Maak daarna met Gii een CRUD van de student tabel. Kijk nog een keer naar de eerste les als je niet meer weet hoe dit precies moet.

Stap 3, CRUDs

In deze stap gaan we de CRUDs generen

Maak  met Gii een CRUD van de student-tabel, de cijfer-tabel en de vak-tabel. Kijk nog een keer naar de eerste les als je niet meer weet hoe dit precies moet.

Stap 4, View

In deze stap gaan we onze eigen view maken waarin we per student alle (behaalde) cijfers laten zien.

In deze stap zien we een relatie, namelijk een 1:N relatie. Elke student heeft immers 0,1, of meer vakken.

In de vorige lessen hebben we hier mee geoefend, weet je nog? In Eén land werden er 1 of meer talen gesproken. In dat overzicht lieten we alle talen per land zien en nu laten we alle vakken per student zien. Technisch werkt dit op dezelfde manier.

image-1594677185236.png

Stap 5, Update Cijfer

In deze stap gaan we een het automatisch gegenereerde form waarmee we een cijfer kunnen aanpassen veranderen.

In de standaard CRUD van de tabel cijfer kun je cijfers updaten. Ga naar http://localhost:8080/cijfer en druk op edit (rechts). Je komt nu in het update-scherm van de tabel cijfer.

image-1594748309104.png

Nu zie je alleen de gegevens uit de tabel cijfer: je zou liever de naam van de student en de naam van het vak zien. Dat gaan we fixen!

Allereerst kijken we naar de URL: http://localhost:8080/cijfer/update?id=0

Om te bepalen welke view we moeten aanpassen volgend de routeringsregels. We gaan naar de CijferController.php file en zoeken naar de actionUpdate. We zien dat aan het einde van deze function (method) de view update wordt aangeroepen:

 return $this->render('update', [ 'model' => $model, ]); }

De update view staat in views/cijfer/update.php, maar als je die opent, dan zie je heel weinig code. Dat komt, omdat de update en insert heel veel op elkaar lijken. Het invulscherm (form) is namelijk voor beide hetzelfde. En beide views (update.php en insert.php) roepen dus beiden _form.php aan. Als we in _form.php wijzigingen maken, dan gelden die dus voor update en voor insert.

We willen dat het scherm er ongeveer zo gaat uitzien:

image-1594750980851.png

We kunnen in dit form alleen het cijfer aanpassen; de naam van de student en de naam van het vak kunnen hier niet worden aangepast. Het cijfer staat overigens als 100-tal in de database. In dit voorbeeld staat 85 en dat staat voor een 8.5.

<video uitleg - met gebruik van dd>

Nu moeten we een link maken tussen ons overzichtsscherm en de juisten update pagina. Het moet je moet op het vak/cijfer van een student kunnen klikken en dan moet jet juiste update scherm worden getoond.

Ons overzicht overzicht.php staat in de student directory.

Om zeker te weten dat we het juiste ID gebruiken, maken we een tijdelijke aanpassing in het overzicht en drukken het ID van de cijfer tabel af.

Opdracht (1):  druk na het cijfer in student/overzicht.php het id af van de koppeltabel waarin het cijfer voor het vak staat. Zie voorbeeld hieronder.

image-1594752066080.png

We hebben nu alle informatie om van het cijfer een hyperlink te maken naar het juiste update scherm.

Opdracht (2): maak van het cijfer dat wordt afgedrukt in student/overzicht.php een hyperlink (<a href=....), zodat als je op het cijfer klikt, het juiste en zonet gemaakte update scherm verschijnt.

Het skelet van de code om de hyperlink af te drukken:

<?php $url='/cijfer/update?id='.XXXXXX; ?>
<a href=<?=$url?> >
  <?= number_format(($cijfervak->cijfer)/10,1) ?>
</a>

Om het overzichtelijk te houden maak je eerst de variabele $url met de juisten URL en maak je daarna de hyperlink. De XXXXXX moet nog worden vervangen door het juiste id (van de tabel vak). Deze heb je bij de vorige opdracht al laten afdrukken.

--

Archive

7, Sorteren, selectie maken en menu's

In deze les....

Stap 1, aanmaken nieuwe overzicht

We gaan in de cijferController, een nieuwe function (method) actionOverzicht maken. Als basis kun je de actionOverzicht uit studentController kopiëren. Je moet de nieuwe functie natuurlijk wel aanpassen!

We gaan in de view cijfer een nieuw overzicht.php aanmaken. Als basis kun je de file overzicht.php uit student kopiëren. Ook deze nieuw gekopieerde view moet je wel aanpassen!

We drukken de volgende vier kolommen af:

<?= $cijfer->student->voornaam ?>
<?= $cijfer->student->achternaam ?>
<?= $cijfer->vak->naam ?>
<?= $cijfer->cijfer ?>

Stap 2, conditioneel afdrukken

We gaan de cijfers conditioneel anders afdrukken. De conditie is dat we cijfers lager dan 55 in het rood gaan afdrukken. De conditie is dus cijfer<55.

We kunnen hiervoor de volgende code gebruiken.

 <?php if($cijfer->cijfer < 55): ?>
   <td><font color="red"><?= number_format(($cijfer->cijfer)/10,1) ?></td>
 <?php else: ?>
   <td><?= number_format(($cijfer->cijfer)/10,1) ?></td>
 <?php endif; ?>

Op regel 1 staat de conditie, dit is bijna hetzelfde als een PHP if. Het enige verschil is dat de { is vervangen door een :
Op regel 2 wordt het cijfer door 10 gedeeld en met één decimaal in het rood afgedrukt.
Op regel 3 staat de else, hier wordt de } weggelaten en de { is vervangen door :
Op regel 4 wordt het cijfer net zo afgedrukt als op regel 3, alleen nu niet in het rood.
Op regel 5 staat een endif, zonder } en met een ;. De ; geeft aan dat dit het eidne is van de if.

Stap 3, sortering aanpassen

De sortering wordt geregeld vanuit de controller.

De query wordt in de controller gemaakt.

$query=cijfer::find()

Voeg hier een orderBy aan toe.

$query=cijfer::find()
  ->orderBy('cijfer');

Stap 3, selectie aanpassen

We gaan een selectie maken op vak, dit gebeurt ook in de controller.

We voegen een where toe.

$query=cijfer::find()
  ->where( ['vak_id' => $vak_id] )
  ->orderBy('cijfer');

Oops, waar komt $vak_id vandaan? Hoe weet de controller waar hij op moet selecteren?

Dat weet de controller nog niet, we zullen bij het aanroepen van controller een parameter moeten meegeven, verander de definitie van de controller in:

public function actionOverzicht($vak_id=1)

$vak_id wordt nu optioneel meegegeven en is 1 als er geen waarde wordt meegegeven.

Probeer maar, als het goed is, zie je nu alleen de vakken waarvan het vak_id een 1 is (en dat in Nederlands).

Verander de URL naar bijvoorbeeld:

http://localhost:8080/cijfer/overzicht/?vak_id=2

en je ziet alleen het vak met het vak_id 2 (en dat is Engels).

Stap 4a, extra menu in overzicht

image-1594845411296.png

De eenvoudigste methode om via de view een vak te selecteren is met een 'eenvoudig' Bootstrap menu. Dat kan met de volgende code:

<div class="row">
  <div class="col-md-2">
      <ul class="nav nav-tabs">
        <li class="dropdown">
          <a href="#" data-toggle="dropdown">Vak <span class="caret"></span></a>
          <ul class="dropdown-menu" role="menu">
            <li><a href="/cijfer/overzicht/?vak_id=1" >Nederlands</a></li>
            <li><a href="/cijfer/overzicht/?vak_id=2" >Engels</a></li>
            <li><a href="/cijfer/overzicht/?vak_id=3" >Rekenen</a></li>
            <li><a href="/cijfer/overzicht/?vak_id=4" >JavaScript</a></li>
            <li><a href="/cijfer/overzicht/?vak_id=5" >PHP</a></li>
            <li><a href="/cijfer/overzicht/?vak_id=6" >HTML</a></li>
            <li><a href="/cijfer/overzicht/?vak_id=7" >SQL/Databases</a></li>
            <li><a href="/cijfer/overzicht/?vak_id=8" >Burgerschap</a></li>
            <li><a href="/cijfer/overzicht/?vak_id=9" >IT Security</a></li>
          </ul>
        </li>
      </ul>
    </div>
</div>

Dit is de Bootstrap manier om een menu te maken.

Zie: https://getbootstrap.com/2.3.2/components.html#buttonDropdowns voor meer uitleg.

Op zich hoef je niet precies te weten hoe het werkt, als je maar weet hoe je dit kunt aanpassen voor je eigen menu.

Je kunt dit menu ook in de menu bar van de applicatie plaatsen, daarvoor moet je de file views/layouts/mail.php aanpassen.

Stap 4b, tabbed menu in overzicht

image-1594845470106.png

Een tabbed overzicht ziet er fraaier, maar is wat lastiger. Dit kun je op de volgende manier toevoegen in je view.

<ul class="nav nav-tabs">
  <?php $vakArr=['Nederlands','Engels','Rekenen', 'JavaScript',
              'PHP', 'HTML', 'SQL/DB', 'Burgerschap', 'Security'] ?>
  <?php for($i=1; $i<=9; $i++): ?>
    <?php if($i==$vak_id): ?>
      <li class="active">
    <?php else: ?>
      <li>
    <?php endif; ?>
      <a href="/cijfer/overzicht/?vak_id=<?=$i?>" ><?=$vakArr[$i-1]?></a></li>
  <?php endfor; ?>
</ul>

Dit is vrijwel allemaal PHP code. De vakken staan in een array en zijn in de database genummerd met de ID's 1 tot en met 9. In een loop worden alle menu items afgedrukt en wordt de juiste waarde uit het array erbij afgedrukt.

Bestudeer de code en vraag om uitleg als je dit niet begrijpt.

--

 

Archive

8, Login / rollen

In onze tweede web app, de student database gaan we een login maken. Als je aanlogt als beheerder dan mag je de cijfers invoeren, veranderen of deleten.

models/Users.php
private static $users = [
  '100' => [
      'id' => '100',
      'username' => 'admin',
      'password' => 'admin',
       'authKey' => 'test100key',
       'accessToken' => '100-token',
       'role'=> 'admin',
  ],
   '101' => [
      'id' => '101',
      'username' => 'user',
      'password' => 'demo',
      'authKey' => 'test101key',
      'accessToken' => '101-token',
      'role'=> 'user',
  ],
];
In controller
public function behaviors()
{
  return [
    'access' => [
    'class' => AccessControl::className(),

    'rules' => [
          [ 'actions' => ['index','view'],
            'allow' => true,
            'roles' => ['@'] // any authenticated user, use ? for guests
          ],

          [ 'actions' => ['create','update','delete','overzicht'],
            'allow' => true,
            'roles' => ['@'],
            'matchCallback' => function ($rule, $action)
            {
              return (Yii::$app->user->identity->role == 'admin');
            }
          ],

       ],

     ],
  ];
}
via Init method in Controller (replaces the __construct)
// if user object does nit exists or it exists but is anything but admin, go to login screen
public function init() {
    if (! isset(Yii::$app->user->identity->role) || Yii::$app->user->identity->role != 'admin') {
        $this->redirect(['/site/login']);
    }
 }

 

Archive

Opdrachten

Les 1, opdracht 1

  1. Bespreek met elkaar wat een framework is en probeer een omschrijving te maken.

  2. Bespreek met elkaar waarom jij een framework zou willen leren gebruiken?

  3. Welke (PHP) frameworks ken je?


Les 1, opdracht 2

Als jij een framework kiest om te leren, wat is dan belangrijk voor jou? Zet de volgende punten op volgorde van belangrijkheid (van meest belangrijk naar minder).


Huiswerk, les 1

  1. Lees de Yii inleiding
  2. Volg les 1 tot en met "Create New Yii project",

Je hebt dan dus XAMPP draiaen, Composer geïnstalleerd en een nieuw Yii project gemaakt.


 

 

 

CRUD generator issue

In versie 2.0.43 van het Yii framework zit een foutje die soms optreed. Helaas treedt deze fout ook op bij de World database.

Nieuwe fout (urlCreator)

Deze fout is opgelost, maar er zit nu een andere fout in de Yii CRUD generator. Nadat de CRUD is gemaakt krijg je een foutmelding. Deze gaat over de index.php file van de view.

In dit voorbeeld moet je het woord Country weghalen.

image-1655735527573.png

Of in zijn algemeenheid, stel je hebt een CRUD gemaakt voor Abc

Vervang dan dit

'urlCreator' => function ($action, Abc $model$key$index$column) {

in

'urlCreator' => function ($action$model$key$index$column) {

'Abc' is in dit voorbeeld weggehaald.
(in de foutmelding staat het regelnummer en de locatie van de file waar deze regel staat).

Wat is de fout?

Als je op het overzicht (van bijvoorbeeld countries) op het oogje rechts op de pagina klikt, dan krijg je een foutmelding. View, update en delete werken niet of slecht gedeeltelijk.

Wat is de oorzaak?

Als de primary key van een tabel niet id heet dan kan het fout gaan. De fout zit in de crud generator template.

Work around?

Je kunt een oude crud generator installeren. Hieronder wordt uitgelegd hoe dat moet.

Installeren oude CRUD generator template

Lees elke stap goed en werk heel nauwkeurig, we gaan namelijk de werking van Yii hacken.

We maken gebruik van de CRUD generator template 2.0.37

Stap 1

Download de zip file die in deze les staat.

Stap 2

Ga vanuit de project folder naar de volgende directory:

vendor\yiisoft\yii2-gii\src\generators

In deze folder zie je het volgende:

image-1635448540144.png

Stap 3

Hernoem de crud folder in crud.org, en

Stap 4

zet de crud folder uit de zip file in de folder. Het ziet er dan zo uit:

image-1635448675537.png

Stap 5

Maak via de crud generator opnieuw de crud. Eventuele aanpassingen worden dan overschreven.

Create simple report with export CSV

This is generic Yii code to generate a simple report from an SQL query. The query has no parameters.

QueryController.php

The last method/function needs to be changed. The method can also be copied as much as needed.

Change the code that is labeled with < and > in the last function.

Open the controller the usual way: query/<name of action>

<?php

namespace app\controllers;

use Yii;
use app\models\Resultaat;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

use yii\filters\AccessControl;

use yii\helpers\ArrayHelper;

/**
 * BeoordelingController implements the CRUD actions for Beoordeling model.
 */
class QueryController extends Controller
{
    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['POST'],
                ],
            ],
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    // when logged in, any user
                    [ 'actions' => [],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],

        ];
    }


    private function executeQuery($sql, $title="no title", $export=false) {

        $result = Yii::$app->db->createCommand($sql)->queryAll();

        if ($result) { // column names are derived from query results
            $data['col']=array_keys($result[0]);
        }
        $data['row']=$result;
        
        if ($export) {
            $this->exportExcel($data);
        } else {
            $data['title']=$title;
            return $data;
        }

    }

    public function exportExcel($data) {
        header('Content-type: text/csv');
        header('Content-Disposition: attachment; filename="canvas-export' . date('YmdHi') .'.csv"');

        foreach ($data['col'] as $key => $value) {
            echo $value.", ";
        }
        echo "\n";
        foreach ($data['row'] as $line) {
            foreach ( $line as $key => $value) {
                echo $value.", ";
            }
            echo "\n";
        }
        
    }

    public function action<Naam>($sort='desc', $export=false) {

      	// sort parameter is optional, export parameter determines if export to CSV needs to be given
      	// specify query
        $sql="select column1 Header1, column2 Header2,.... from .... order by 3 $sort";

      	// specify report title
        $data=$this->executeQuery($sql, <rapport title>, $export);

      	// specify sub title (descr, can also be omitted).
        return $this->render('output', [
            'data' => $data,
            'action' => Yii::$app->controller->action->id,
          	'descr' => <optionele sub tekst onder title>,
        ]);
    }
}

View

The view does not need any adaptations.

<?php
use yii\helpers\Url;
use yii\helpers\Html;
$nr=0;
?>

<div class="card"  style="width: 900px">

    <div class="container">
        <div class="row  align-items-center">
            <div class="col">
                <h1><?= Html::encode($data['title']) ?></h1>
                    <?php
                        if (isset($descr)) {
                            echo "<small>".$descr."</small>";
                        }
                    ?>
                </div>
            <div class="col-md-auto">
                <?= Html::a('Export', [$action.'?export=1'], ['class'=>'btn btn-primary', 'title'=> 'Export to CSV',]) ?>
            </div>
        </div>
    </div>

    <p></p>

    <div class="card-body">
        <table class="table">
            <thead>
                <tr>
                    <?php
                        if ( ! isset($nocount) ) echo "<td>#</td>";
                        if ( $data['row'] ) {
                            for($i=0;$i<count($data['col']);$i++) {
                                echo "<th>".$data['col'][$i]."</th>";
                            }
                        } else {
                            echo "<td>Empty result set</td>";
                        }
                    ?>
            </thead>
            
            <?php
                if ( $data['row'] ) {
                    foreach($data['row'] as $item) {
                        if ( ! isset($nocount) ) {
                            $nr++;
                            echo "<tr>";
                            echo "<td>".$nr."</td>";
                        }
                        for($i=0;$i<count($data['col']);$i++) {
                            echo "<td>".$item[$data['col'][$i]]."</td>";
                        }
                        echo "</tr>";
                    }
                }
            ?>
        </table>
    </div>
</div>

09, Form en drop down

We gaan onze koffie applicatie uitbreiden.

Om het invoeren van gegevens eenvoudiger te maken gaan we drop down menu's toevoegen.

We gaan naar twee soorten drop-downs kijken; die met vaste waarden en die met dynamische waarden (uit de database).

Bij het invoeren van een bestelling gaan we allereerst de status via een drop down invoeren.

Daarna gaan we waarde voor de medewerker ook uit een drop down halen. Deze waarde moet uit de database komen.

Drop down in Bestelling

We gaan kijken naar de bestelling tabel en CRUD.

Als we de status in de database als een enum hebben aangemaakt dan weet Yii dat de status uit beperkt aantal waarden kan hebben en als het goed is maakt Yii dan vanzelf een een drop down in het form.

Klopt dat heb jij een drop down?

Indien niet, dan kun je de code aanpassen in het form aanpassen.

In de view _form.php van Bestellingen moet de volgende regel staan:

<?= $form->field($model, 'status')->dropDownList([ 'besteld' => 'Besteld', 'klaar' => 'Klaar', 'betaald' => 'Betaald', '' => '', ], ['prompt' => '']) ?>

Deze regel laat een drop down menu in het formulier zien. Kijk eens goed naar de parameter van dropDownList.

[ 'besteld' => 'Besteld', 'klaar' => 'Klaar', 'betaald' => 'Betaald', '' => '', ], ['prompt' => '']

De eerste parameter is een associative array waarin elk element bestaat uit een key en een value.

De key is de waarde die het element uit het form krijgt (de value) en deze waarde wordt door Yii in de database gezet. De key komt dus overeen met de waarde in de database. De value van het associatieve array is wat de gebruiker op het scherm ziet.

Deze waarde kan je dus veranderen. Je kunt dus bijvoorbeeld 'betaald' veranderen in 'afgerekend'. Het enige dat dan gebeurt is dat je iets ander op het scherm ziet.

In dit voorbeeld is de key en de value gelijk (de waarde op het scherm is hetzelfde als de waarde in de database).

Dus samengevat, de key is de waarde in de database en de value is wat de gebruiker op het scherm ziet.

Stel we willen in het form van bestelling de medewerker veranderen. Dan moeten we dus de foreign key die naar medewerker verwijst veranderen. Het juiste id van de medewerker moet in de tabel bestelling worden ingevuld.

De waarde wordt dus het id, maar dat is niet wat je de gebruiker wilt laten zien.

Dus als we voor de medewerkers een drop down willen maken dan hebben we een assiociative array nodig dat er zo uit ziet:

[ '1'=> 'Ayoub', '2'=> 'Brahim','3'=> 'Carla','4'=> 'Diego','5'=> 'Eisa' ]

De keys zijn de id's die als foreign keys in de bestelling tabel staan en de namen zijn de namen van de medewerkers.

 

Opdracht 8a

Maak nu een statische drop down met de waarden zoals in het voorbeeld (Ayoub, Brahim, Carla, ....).

De waarden worden dus (nog) niet uit de database gehaald.

Inleveren
  1. Schermafdruk yii-08a-jouw-naam met het form waarin je de drop down (opengeklapt) laat zien. Maak een schermafdruk van je gehele browser.

 

Opdracht 8b, Dynamische drop down

In opdracht 8a hebben we een drop down van medewerkers gemaakt, maar we willen de lijst van medewerkers natuurlijk uit de database halen.

In deze opdracht leg ik stap-voor-stap uit wat je moet doen omdat oor elkaar te krijgen. Lees alles aandachtig door en sla geen stappen over!

Data uit de database halen doen we in de controller.

Open de BestellingController. Vanuit deze code wordt de create view aangeroepen en vanuit de create view wordt de _form.php aangeroepen. Waarom dit in twee stappen gaat leggen we later uit.

We veranderen de code in Bestelling controller:

use app\models\Medewerker;

..
..

public function actionCreate()
{
    $model = new Bestelling();
    $medewerkers = Medewerker::find()->all();

    if ($model->load(Yii::$app->request->post()) && $model->save()) {
        return $this->redirect(['view', 'id' => $model->id]);
    }

    return $this->render('create', [
        'model' => $model,
        'medewerkers' => $medewerkers
    ]);
}

De eerste regel use app\models\Medewerker; zetten we bovenaan in de BestellingController. Hiermee vertellen we Yii dat vanuit de BestellingController gebruik willen gaan maken van het model Medewerker.

In de function actionCreate die wordt aangeroepen als we een nieuwe bestelling willen maken, voegen we een object toe waarin alle medewerkers zitten. In regel 9 halen we alle medewerkers op en in regel 17 sturen we het resultaat naar de view create.php (van bestelling).

We kunnen de medewerkers ook ophalen via een sql-query. Je mag regel 9 ook vervangen in de volgende twee regels:

// dit is hetzelfde als
// $medewerkers = Medewerker::find()->all();
$sql="select * from medewerker";
$medewerkers = Yii::$app->db->createCommand($sql)->queryAll();

In de view create.php van bestelling passen we het laatste code aan

<?= $this->render('_form', [
    'model' => $model,
    'medewerkers' => $medewerkers
]) ?>

Hiermee geven we het object medewerkers weer door aan de view _form van bestelling.

In het view _form zetten we nu bovenaan in het PHP-gedeelte.

dd($medewerkers);

Dit is de debugfunctie en hiermee controleren we of we inderdaad alle medewerkers naar de view hebben gestuurd.

We hebben nu als het goed is een lijst van medewerkers in de _form maar we moeten het ombouwen naar een assiociative array.

Pas hiervoor de code aan in de view _form van bestelling.

<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\helpers\ArrayHelper;

$medewerkerList = ArrayHelper::map($medewerkers,'id','naam');
dd($medewerkerList);
?>

...
...
...

Deze code laat het eerst stukje van de view_form zien.

Met de functie ArrayHelper::map zetten we het object $medewerkers om in een assiociative array. Met dd() laten we dat zien. Probeer maar! Als het goed is, zie je het volgende:

[
    1 => 'Ayoub'
    2 => 'Brahim'
    3 => 'Carla'
    4 => 'Diego'
    5 => 'Eisa'
]

En dit is precies wat we nodig hebben om de Drop Down te maken.

Pas de regel in de view _form van bestelling waarin de user het id van de medewerker moet intypen aan. Verander deze regel in:

<?= $form->field($model, 'medewerker_id')->dropDownList($medewerkerList, ['prompt' => ''])->label('Medwerker') ?>

Als het goed is heb je hiermee een werkend menu gekregen.

image-1616605065315.png

Gelukt? Wordt de lisjt van medewerkers in de drop down uit de database gehaald?

Inleveren

  1. schermafdruk yii-08b-jouw-naam met het form waarin je de drop down van de medewerkers (opengeklapt) laat zien. Maak een schermafdruk van je gehele browser.

 

Opdracht 8c

Maak nu een drop down voor de bestelling zodat de medewerker
uit een lijstje van koffiesoorten kan kiezen bij het opnemen van
de bestelling.

image-1616605448841.png

Gebruik hiervoor het stappenplan dat hieronder is beschreven.

Het stappenplan voor een drop down

Schrijf eerst het volgende op:

In welk model komt de drop down? Dit noemen we de target.
(in opdracht 2 hierboven is dat Bestelling)

Waar komt de informatie voor de Drop Down uit? Dit noemen we de source.
(in opdracht 2 hierboven is dat Menu)

Voer nu de stappen 1-6 uit

1. Zet in de target controller welke source model we willen gebruiken.

// in controller van target
use app\models\<SourceModelnaam>;

2. Haal in de target controller in de actionCreate() de informatie uit de source op.

// sourceName bedenk jij zelf en daarin staat de informatie die in de drop down moet komen.
$sourceName = source::find()->all();

3. Geef vanuit de target controller in de actionCreate() de informatie door aan de view.

return $this->render('create', [
     'sourceName' => $sourceName,
     ....
]);

4. Open de view create in de target (view) en geef de informatie door aan _form view.

<?= $this->render('_form', [
    'sourceName' => $sourceName,
    ...
]);

5. Open de view _form in de target (view) en zet de informatie om in een assiociative array

// in dit voorbeeld worden id en naam gebruikt:
//   id is het veld dat moet worden opgeslagen
//   naam is het veld dat in de drop te zien is
$sourceNameList = ArrayHelper::map($sourceName,'id','naam');

6. Pas het invoerveld aan in de _form van de target (view).

// veld_in_de_db is het veld van de target dat in dit invoerveld wordt weggeschreven.
<?= $form->field($model, 'veld_in_de_db')->dropDownList($$sourceNameList, ['prompt' => '']) ?>

Als je alles correct gedaan hebt dan moet je nog 1 regel aanpassen in de code om de functionaliteit te gebruiken. Yii controleert automatisch of alle databasekolommen zijn ingevuld in het formulier. Dit controleren wordt validatie genoemd. Deze regels staan in het model. Omdat je geen Bestelling ID opgeeft bij het maken van een bestelling moet je deze requirement verwijderen op regel 33 van model Bestelling.

return [
	[ ['naam', 'medewerker_id', 'menu_id', 'status'], 'required'],
    ...
    ...
]

Inleveren

  1. schermafdruk yii-08c-jouw-naam met het form waarin je de drop down van de producten (opengeklapt) laat zien. Maak een schermafdruk van je gehele browser.
  2. de BestellingController.php en zet jouw naam bij de aanpassingen die je hebt gedaan.

 

Opdracht 8d

De insert en update lijken op elkaar en gebruiken hetzelfde form (_form.php).

We hebben nu de insert aangepast, maar de update nog niet. Controleer maar! We moeten nu de update dus ook nog aanpassen.

aanpassen update view

Weet je nog dat de -update view niet rechtstreeks door de actionCreate van de BestellingController wordt aangeroepen?

Hoe zit dat nu?

image-1616610519616.png

De actionCreate en actionUpdate vanuit de controller gaan bieden naar hun 'eigen' view maar via deze eigen view komen beide op hetzelfde form uit. Dat komt omdat een update en create hetzelfde form gebruiken.

Als we nu dus een update uitvoeren van de bestelling (pennetje vanuit de gridview) dan krijgen we een foutmelding. Dat komt omdat de form verwacht dat er gegevens voor de drop down worden meegestuurd.

Om dit te fixen kopiëren we de code in de controller die we in de actionCreate hebben gemaakt dus naar de actionUpdate.

Deze twee functies zijn bijna hetzelfde. Bij de create wordt alleen een nieuw object gemaakt en bij de update wordt een bestaand object ingeladen.

Nu moeten we de objecten nog doorgeven van de update view naar de _form view. Net zoals we dat ook deden bij  de create. 

Zorg ervoor dat de update weer werkt en dat de objecten op de juiste manier worden doorgegeven. Lees hiervoor de uitleg die hierboven staat.


Pas de actionUpdate in de BestellingController aan.

Pas de update view van de BestellingController aan


Zorg dat de update van een Besteliing goed werkt..

Inleveren

  1. de BestellingController.php en zet jouw naam bij de aanpassingen die je hebt gedaan.

 

Opdracht 8e

De final touch

Laten we nog wat labels aanpassen. Dit zijn de teksten die boven de invoervelden in het form staan.

Pas de labels in het form aan zodat er Medewerker, Klantnaam, Bestelling en Status Bestelling komt te staan:


image-1616611684520.png


Zoek zelf uit hoe dit moet. Tip gebruik de zoektermen:


How to change label text of the ActiveField?


Inleveren

  1. schermafdruk yii-08e-jouw-naam met het bestellingen form waarin je de aangepaste labels laat zien. Maak een schermafdruk van je gehele browser.

--


Yii Cheat Sheet

Back

 <?= Html::a( 'Cancel', Yii::$app->request->referrer , ['class'=>'btn btn-primary']); ?>

Grid View

[
  'attribute' => 'no_answered',
  'label' => '#vragen',
  'enableSorting' => false,
  'filter' => false,
  'headerOptions' => ['style' => 'width:60px;'],
  'contentOptions' => function ($model, $key, $index, $column) {
    if ( ! $model->finished )return ['style' => ""];
      $score = $model->no_questions > 0 ? round(($model->no_correct / $model->no_questions) * 100, 0) : 0;
    if ( $model->no_answered ) {
      $backgroundColor = $score < 55 ? 'lightcoral' : 'lightgreen';
    } else {
      $backgroundColor = "";
    }
     return ['style' => "background-color: $backgroundColor;"];
  },
  'format' => 'raw',
  'value' => function ($model) {
    return Html::a($model->no_answered.'/'.$model->no_questions, ['submission/update', 'id' => $model->id]);
    return $model->no_answered.'/'.$model->no_questions;
  },
],

['class' => 'yii\grid\SerialColumn'],
[
  'attribute' => 'label',
  'label' => 'Label',
  'headerOptions' => ['style' => 'width:200px;'],
  'contentOptions' => [
  'style' => 'color: #404080;'
],

 

xxx