Maak een PHP5-raamwerk - deel 2

Met de basisstructuur voor ons Framework op zijn plaats, is het tijd om er functionaliteit aan toe te voegen. In deze tutorial zullen we een sjabloomanager en database-handler maken, waarmee we een stap dichter bij een krachtige Framework fit voor bijna elk project komen. Bekijk eerst deel 1 van deze serie als je dat nog niet hebt gedaan!




MVC: tweak de structuur

In het eerste deel van deze tutorial hebben we een map gemaakt met de naam controllers om de bedrijfslogica voor onze applicaties op te slaan. Zoals daok in een opmerking opmerkte, is dit niet de beste plaats voor alle bedrijfslogica, en dat een model- moet worden gebruikt om deze logica op te slaan. Eerder heb ik de database zelf altijd als het model in de meeste van mijn toepassingen gebruikt. Als u dit echter wat meer uit elkaar trekt, wordt ons framework nog krachtiger en gemakkelijker uit te breiden.

Dus wat is MVC? MVC is een ontwerppatroon (net als de Singleton- en registerpatronen die we in deel 1 hebben bekeken), en het staat voor Model View Controller en het doel van dit patroon is om de bedrijfslogica, gebruikersinterfaceacties en de gebruikersinterface te scheiden van elkaar. Hoewel we nog niets met onze modellen en controllers gaan doen, laten we onze frameworks mapstructuur bijwerken met de map "models". Het model zal de belangrijkste bedrijfslogica bevatten en de controller zal omgaan met gebruikersinteractie (bijvoorbeeld het verzenden van gegevens, zoals een opmerking). NB: Onze __autoload-functie hoeft niet te worden gewijzigd.

Databasehandler

De meeste websites en webtoepassingen die gebruik maken van PHP maken ook gebruik van een database-engine, zoals MySQL. Als we al onze database gerelateerde functies op dezelfde plaats houden, dan kunnen we (in theorie) gemakkelijk de database-engine veranderen die we gebruiken. We kunnen bepaalde bewerkingen ook eenvoudiger maken, zoals het invoegen van records, het bijwerken van records of het verwijderen van records uit de database. Het kan het ook gemakkelijker maken om met meerdere databaseverbindingen te werken.

Dus ... wat zou moeten onze database handler do:

  • Beheer verbindingen met de database
  • Probeer een niveau van abstractie uit de database te halen
  • Cache-query's zodat we ze later kunnen gebruiken
  • Maak algemene databasebewerkingen eenvoudiger

Laten we de code van onze databasehandler bekijken, daarna bespreken we deze.

 verbindingen [] = nieuwe mysqli ($ host, $ user, $ password, $ database); $ connection_id = count ($ this-> connections) -1; if (mysqli_connect_errno ()) trigger_error ('Fout bij verbinden met host.'. $ this-> verbindingen [$ connection_id] -> error, E_USER_ERROR);  return $ connection_id;  / ** * Sluit de actieve verbinding * @return void * / public function closeConnection () $ this-> connections [$ this-> activeConnection] -> close ();  / ** * Wijzigen welke databaseverbinding actief wordt gebruikt voor de volgende bewerking * @param int de nieuwe verbindings-id * @return void * / public function setActiveConnection (int $ new) $ this-> activeConnection = $ new;  / ** * Bewaar een query in de query-cache om later te verwerken * @param Tekenreeks string * @terug het punt naar de query in de cache * / public function cacheQuery ($ queryStr) if (! $ Result = $ this-> verbindingen [$ this-> activeConnection] -> query ($ queryStr)) trigger_fout ('Fout bij uitvoeren en cachingquery:'. $ this-> verbindingen [$ this-> activeConnection] -> error, E_USER_ERROR); return -1;  else $ this-> queryCache [] = $ resultaat; return count ($ this-> queryCache) -1;  / ** * Haal het aantal rijen uit de cache * @param in de query-cache pointer * @return int het aantal rijen * / openbare functie numRowsFromCache ($ cache_id) return $ this-> queryCache [$ cache_id] -> NUM_ROWS;  / ** * Haal de rijen uit een gecachte zoekopdracht * @param in de query cache pointer * @return array de rij * / public functie resultsFromCache ($ cache_id) return $ this-> queryCache [$ cache_id] -> fetch_array ( MYSQLI_ASSOC);  / ** * Bewaar gegevens in een cache voor later * @param array de gegevens * @return int de wees naar de array in de datacache * / public function cacheData ($ data) $ this-> dataCache [] = $ data; return count ($ this-> dataCache) -1;  / ** * Haal gegevens op uit de datacache * @param int datacache gericht * @return array the data * / public function dataFromCache ($ cache_id) return $ this-> dataCache [$ cache_id];  / ** * Records verwijderen uit de database * @param Teken de tabel om rijen te verwijderen uit * @param Teken de voorwaarde in voor welke rijen moeten worden verwijderd * @param int het aantal rijen dat moet worden verwijderd * @return ongeldig * / openbare functie deleteRecords ($ table, $ condition, $ limit) $ limit = ($ limit == ")?": 'LIMIT'. $ Limiet; $ delete = "DELETE FROM $ table WHERE $ condition $ limit"; $ this-> executeQuery ($ delete);  / ** * Update records in de database * @param Teken de tabel * @param array of changes field => value * @param String the condition * @return bool * / public function updateRecords ($ table, $ changes, $ condition ) $ update = "UPDATE". $ tafel. "SET"; foreach ($ verandert als $ field => $ value) $ update. = "'". $ veld. " '=' $ Value',";  // verwijder onze trailing, $ update = substr ($ update, 0, -1); if ($ condition! = ") $ update. =" WHERE ". $ condition; $ this-> executeQuery ($ update); return true; / ** * Records in de database invoegen * @param De database koppelen tabel * @param array-gegevens om veld in te voegen => waarde * @return bool * / openbare functie insertRecords ($ table, $ data) // stel enkele variabelen in voor velden en waarden $ fields = ""; $ values ​​= ""; // vul ze voor elke ($ data als $ f => $ v) $ fields. = "'$ f',"; $ values. = (is_numeric ($ v) && (intval ($ v) == $ v ))? $ v. ",": "'$ v',"; // verwijder onze trailing, $ fields = substr ($ fields, 0, -1); // verwijder onze trailing, $ values ​​= substr ( $ waarden, 0, -1); $ insert = "INSERT INTO $ table ($ fields) VALUES ($ values)"; $ this-> executeQuery ($ insert); return true; / ** * Voer een querystring uit * @param Teken de query * @return void * / public function executeQuery ($ queryStr) if (! $ Result = $ this-> connections [$ this-> activeConnection] -> query ($ queryStr)) trigger_error ('Fout bij het uitvoeren van query:'. $ this-> verbindingen [$ t his-> activeConnection] -> error, E_USER_ERROR);  else $ this-> last = $ resultaat;  / ** * Verkrijg de rijen van de laatst uitgevoerde query, exclusief gecachte query's * @return array * / public function getRows () return $ this-> last-> fetch_array (MYSQLI_ASSOC);  / ** * Hiermee wordt het aantal betrokken rijen uit de vorige query opgehaald * @return int het aantal getroffen rijen * / aangetroffen openbare functieRows () return $ this -> $ this-> connections [$ this-> activeConnection] - > affected_rows;  / ** * Sanitize data * @param String the data to sanitized * @return String the cleaned data * / public function sanitizeData ($ data) return $ this-> connections [$ this-> activeConnection] -> real_escape_string ( $ gegevens);  / ** * Deconstrueer het object * sluit alle databaseverbindingen * / public function __deconstruct () foreach ($ this-> verbindingen als $ connectie) $ connection-> close (); ?>

Voordat ik dit in meer detail bespreekt, moet ik erop wijzen dat deze databasehandler erg eenvoudig is. We kunnen volledige abstractie bieden door query's niet rechtstreeks uit te voeren, maar in plaats daarvan query's op basis van parameters te construeren naar een queryfunctie en deze vervolgens uit te voeren.

Onze verwijder-, insert- en update-recordmethodes maken het eenvoudiger om een ​​aantal algemene taken uit te voeren (zoals ik hierboven al zei, we kunnen dit uitbreiden om nog veel meer te doen), door alleen informatie te geven zoals de tabelnaam, een reeks velden en bijbehorende waarden, grenswaarden en voorwaarden. Query's kunnen ook in de cache worden opgeslagen, zodat we later dingen met ze kunnen doen. Ik vind deze functie (evenals de mogelijkheid om arrays met gegevens te "cachen") erg handig in combinatie met een sjabloommanager, omdat we eenvoudig rijen gegevens kunnen herhalen en deze met weinig gedoe in onze sjablonen kunnen vullen, net zoals je zult doen zie wanneer we naar de sjabloonbeheerder kijken.

 // insert record $ registry-> getObject ('db') -> insertRecords ('testTable', array ('name' => 'Michael')); // update een record $ register-> getObject ('db') -> updateRecords ('testTable', array ('name' => 'MichaelP'), 'ID = 2'); // verwijder een record (tot 5 in dit geval) $ register-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5);

We kunnen ook relatief gemakkelijk met meerdere databaseverbindingen werken, zolang we tussen de juiste verbindingen schakelen wanneer dat nodig is (hoewel dit niet werkt wanneer zoekopdrachten worden gecacht en via onze sjabloomanager worden opgehaald zonder verder te werken), bijvoorbeeld onderstaande codefragment zou ons in staat stellen records uit twee databases te verwijderen.

 // onze tweede databaseverbinding (laten we aannemen dat we al een verbinding hebben met onze hoofddatabase) $ newConnection = $ registry-> getObject ('db') -> newConnection ('localhost', 'root', 'password', 'secondDB '); // verwijderen uit de primaire db-verbinding $ register-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5); // verander onze actieve db-verbinding om toekomstige query's mogelijk te maken in het tweede $ register $ -> getObject ('db') -> setActiveConnection ($ newConnection); // delete from the secondary db connection $ registry-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5); // de actieve verbinding herstellen zodat toekomstige query's zich op de primaire db-verbinding $ register-> getObject ('db') -> setActiveConnection (0) bevinden;

Hoe kunnen we deze klas uitbreiden??

  • Volledige abstractie
  • Maak gebruik van overerving, maak een interface en laat database klassen er van erven, elk voor verschillende database-engines
  • Bewaar de verbindings-ID's samen met de query bij het cachen van query's
  • Verbeter data-ontsmetting, afhankelijk van het type data dat we willen saneren

Template Manager

De sjabloonbeheerder verwerkt alle uitvoer, moet met verschillende sjabloonbestanden kunnen werken, vervangt plaatsaanduidingen (ik noem ze tags) met gegevens en doorzoek delen van de sjabloon met meerdere rijen gegevens uit de database.

Om het u gemakkelijk te maken, zullen we gebruik maken van een paginaclass die de inhoud van de pagina bevat, dit maakt het ook gemakkelijker voor ons om dit uit te breiden en later functies toe te voegen. De sjabloonbeheerder beheert dit object.

 pagina = nieuwe pagina ();  / ** * Voeg een sjabloonbit toe aan onze pagina * @param String $ tagt de tag waar we de sjabloon invoegen, bijvoorbeeld hello * @param String $ bit het sjabloonbit (pad naar bestand, of alleen de bestandsnaam) * @return void * / public function addTemplateBit ($ tag, $ bit) if (strpos ($ bit, 'skins /' ) === false) $ bit = 'skins /'. PCARegistry :: getSetting ('skin'). '/ templates /'. $ Bit;  $ this-> page-> addTemplateBit ($ tag, $ bit);  / ** * Zet de sjabloonbits in onze pagina-inhoud * Updates van de pagina-inhoud * @return void * / private functie replaceBits () $ bits = $ dit-> pagina-> getBits (); foreach ($ bits als $ tag => $ sjabloon) $ templateContent = file_get_contents ($ bit); $ newContent = str_replace (''. $ tag. '', $ templateContent, $ this-> page-> getContent ()); $ this-> page-> setContent ($ newContent);  / ** * Vervang tags op onze pagina met inhoud * @return void * / private function replaceTags () // haal de tags $ tags = $ this-> page-> getTags (); // doorloop ze allemaal foreach ($ tags als $ tag => $ data) if (is_array ($ data)) if ($ data [0] == 'SQL') // het is een cachevraag ... vervang DB-tags $ this-> replaceDBTags ($ tag, $ data [1]);  elseif ($ data [0] == 'DATA') // het zijn sommige gegevens in de cache ... vervang datatags $ this-> replaceDataTags ($ tag, $ data [1]);  else // vervang de inhoud $ newContent = str_replace (''. $ tag. '', $ data, $ this-> page-> getContent ()); // update de pagina's inhoud $ this-> page-> setContent ($ newContent);  / ** * Vervang inhoud op de pagina met gegevens van de DB * @param String $ tag de tag die het inhoudsgebied definieert * @param int $ cacheId de query-ID in de query-cache * @return void * / private functie replaceDBTags ($ tag, $ cacheId) $ block = "; $ blockOld = $ this-> page-> getBlock ($ tag); // voor elke record met betrekking tot de query ... while ($ tags = PCARegistry :: getObject ( 'db') -> resultsFromCache ($ cacheId)) $ blockNew = $ blockOld; // maak een nieuw inhoudsblok met de resultaten erin vervangen foreach ($ tags als $ ntag => $ data) $ blockNew = str_replace ("". $ ntag. "", $ data, $ blockNew); $ block. = $ blockNew; $ pageContent = $ this-> page-> getContent (); // verwijder de seperator in de template , schonere HTML $ newContent = str_replace (''. $ blockOld. '', $ block, $ pageContent); // update de pagina-inhoud $ this-> page-> setContent ($ newContent);  / ** * Vervang inhoud op de pagina door gegevens uit de cache * @param String $ tag de tag die het inhoudsgebied definieert * @param int $ cacheId de datas-ID in de datacache * @return void * / private function replaceDataTags ($ tag, $ cacheId) $ block = $ this-> page-> getBlock ($ tag); $ blockOld = $ block; while ($ tags = PCARegistry :: getObject ('db') -> dataFromCache ($ cacheId)) foreach ($ tags als $ tag => $ data) $ blockNew = $ blockOld; $ blockNew = str_replace ("". $ tag. "", $ data, $ blockNew);  $ block. = $ blockNew;  $ pageContent = $ this-> page-> getContent (); $ newContent = str_replace ($ blockOld, $ block, $ pageContent); $ this-> page-> setContent ($ newContent);  / ** * Haal het pagina-object op * @return Object * / public function getPage () return $ this-> page;  / ** * Stel de inhoud van de pagina in op basis van een aantal sjablonen * geef sjabloonbestandslocaties door als individuele argumenten * @return void * / public function buildFromTemplates () $ bits = func_get_args (); $ content = ""; foreach ($ bits als $ bit) if (strpos ($ bit, 'skins /') === false) $ bit = 'skins /'. PCARegistry :: getSetting ('skin'). '/ templates /'. $ Bit;  if (file_exists ($ bit) == true) $ content. = file_get_contents ($ bit);  $ this-> page-> setContent ($ content);  / ** * Converteer een array met gegevens (dwz een db rij?) Naar sommige tags * @param array de gegevens * @param teken een prefix in die wordt toegevoegd aan de veldnaam om de tagnaam te maken * @return void * / public function dataToTags ($ data, $ prefix) foreach ($ data als $ key => $ content) $ this-> page-> addTag ($ key. $ prefix, $ content);  openbare functie parseTitle () $ newContent = str_replace ('','<title>'. $ page-> getTitle (), $ this-> page-> getContent ()); $ this-> page-> setContent ($ newContent);  / ** * Ontleed het pagina-object in een uitvoer * @return void * / public function parseOutput () $ this-> replaceBits (); $ This-> replaceTags (); $ This-> parseTitle (); ?></pre> <p> <em>Wat doet deze klasse precies?? </em></p> <p> <strong>Maakt ons paginaobject en baseert dit op sjabloonbestanden</strong>, het pagina-object bevat de inhoud en informatie die nodig is om de HTML van de pagina op te maken. We bouwen vervolgensFromTemplate ('templatefile.tpl.php', 'templatefile2.tpl.php') om de eerste inhoud voor onze pagina te krijgen, deze methode neemt een willekeurig aantal sjabloonbestanden als argumenten en voegt ze samen in volgorde, handig voor header-, inhouds- en voettekstsjablonen.</p> <p> <strong>Beheert de inhoud die aan de pagina is gekoppeld</strong> door het pagina-object te helpen bij het bijhouden van gegevens die moeten worden vervangen in de pagina, en ook aanvullende sjabloonbits die moeten worden opgenomen in de pagina (addTemplateBit ('userbar', 'usertoolsbar.tpl.php')).</p> <p> <strong>Voegt gegevens en inhoud toe aan de pagina</strong> door verschillende vervangbewerkingen uit te voeren op de pagina-inhoud, inclusief het ophalen van resultaten uit een in cache geplaatste query en deze aan de pagina toe te voegen.</p> <p>Het sjabloonbestand moet in zichzelf markeren waar een query in de cache moet worden opgehaald en de gegevens uit de query moeten worden vervangen. Wanneer de sjabloonbeheerder een te vervangen tag tegenkomt die een query is, krijgt deze het deel van de pagina waar het moet worden doorgevoerd door getBlock ('block') op het paginaobject aan te roepen. Dit brok inhoud wordt vervolgens gekopieerd voor elke record in de query en de tags daarin worden vervangen door de resultaten van de query. We zullen later in deze zelfstudie bekijken hoe dit eruit ziet in de sjabloon.</p> <h3>Sjabloonbeheer: pagina</h3> <p>Het paginaobject wordt beheerd door de sjabloonbeheerder en bevatte alle details met betrekking tot de pagina. Dit laat de sjabloonbeheerder vrij om te beheren, terwijl we het voor ons gemakkelijker maken om de functionaliteit hiervan op een later tijdstip uit te breiden.</p> <pre> <?php /** * This is our page object * It is a seperate object to allow some interesting extra functionality to be added * Some ideas: passwording pages, adding page specific css/js files, etc */ class page  // room to grow later? private $css = array(); private $js = array(); private $bodyTag ="; private $bodyTagInsert ="; // future functionality? private $authorised = true; private $password ="; // page elements private $title ="; private $tags = array(); private $postParseTags = array(); private $bits = array(); private $content = ""; /** * Constructor… */ function __construct()   public function getTitle()  return $this->titel;  public function setPassword ($ password) $ this-> password = $ password;  openbare functie setTitle ($ title) $ this-> title = $ title;  public function setContent ($ content) $ this-> content = $ content;  publieke functie addTag ($ key, $ data) $ this-> tags [$ key] = $ data;  openbare functie getTags () return $ this-> tags;  public function addPPTag ($ key, $ data) $ this-> postParseTags [$ key] = $ data;  / ** * Zorg ervoor dat tags worden geparseerd nadat de eerste batch is geparseerd * @return array * / public function getPPTags () return $ this-> postParseTags;  / ** * Voeg een sjabloonbit aan de pagina toe, voegt de inhoud eigenlijk nog niet toe * @param Teken de tag waar de sjabloon is toegevoegd * @param Teken de naam van het sjabloon * * return void * / public function addTemplateBit ($ tag, $ bit) $ dit-> bits [$ tag] = $ bit;  / ** * Haal de sjabloonbits op die in de pagina moeten worden ingevoerd * @return array de array met sjabloontags en sjabloonbestandsnamen * / openbare functie getBits () return $ this-> bits;  / ** * Krijgt een stuk pagina-inhoud * @param Teken de tag die het blok omwikkelt ( <!-- START tag --> blok <!-- END tag --> ) * @return Tekenreeks van de inhoud * / openbare functie getBlock ($ tag) preg_match ('#<!-- START '. $tag . ' -->(. +?)<!-- END '. $tag . ' -->#si ', $ this-> content, $ tor); $ tor = str_replace ('<!-- START '. $tag . ' -->', "", $ tor [0]); $ tor = str_replace ('<!-- END ' . $tag . ' -->', "", $ tor); return $ tor;  openbare functie getContent () return $ this-> content; ?></pre> <p>Hoe kan deze klasse worden uitgebreid en verbeterd?</p> <ul> <li>PostParseTags: mogelijk wilt u dat tags worden vervangen <em>na</em> het grootste deel van de pagina is geparseerd, misschien bevat de inhoud in de database tags die moeten worden geparseerd.</li> <li>Pagina's met een wachtwoord: wijs een wachtwoord toe aan een pagina, controleer of de gebruiker het wachtwoord in een cookie of sessie heeft zodat zij de pagina kunnen zien.</li> <li>Beperkte pagina's (hoewel we eerst onze authenticatiecomponenten nodig hebben!)</li> <li>De. Wijzigen </li> <li>Dynamisch toevoegen van verwijzingen naar javascript- en css-bestanden op basis van de pagina of applicatie.</li> </ul> <h3>Laad kernobjecten</h3> <p>Nu we enkele objecten hebben die ons register voor ons gaat opslaan, moeten we het register laten weten welke objecten dit zijn. Ik heb een methode gemaakt in het object PCARegistry met de naam loadCoreObjects dat (zoals het zegt) de kernobjecten laadt. Dit betekent dat u dit gewoon vanuit ons index.php-bestand kunt oproepen om het register met deze objecten te laden.</p> <pre> public function storeCoreObjects () $ this-> storeObject ('database', 'db'); $ this-> storeObject ('template', 'template'); </pre> <p>Deze methode kan later worden gewijzigd om de andere kernobjecten te bevatten die het register moet laden, natuurlijk kunnen er objecten zijn die we door ons register willen laten beheren, maar alleen afhankelijk van de toepassing waarvoor het framework wordt gebruikt. Deze objecten worden buiten deze methode geladen.</p> <h3>Een aantal gegevens</h3> <p>Om de nieuwe functies die aan ons framework zijn toegevoegd te demonstreren, hebben we een database nodig om gebruik te kunnen maken van de databasehandler en een aantal functies voor sjabloonbeheer (waarbij we een blok met inhoud vervangen door de rijen in de database).</p> <p>De demonstratiesite die we tegen het einde van deze serie tutorials zullen maken met ons framework is een website met een ledendirectory, dus laten we een heel eenvoudige databasetabel maken voor ledenprofielen, die een ID, naam en e-mailadres bevatten.</p> <img src="//accentsconagua.com/img/images_27_8/create-a-php5-framework-part-2_2.png"> <p>Het is duidelijk dat we een paar rijen met gegevens in deze tabel nodig hebben!</p> <h3>Een snel sjabloon</h3> <p>Om alles weer te geven, hebben we een basissjabloon nodig, waarin we de gegevens uit onze ledentabel zullen vermelden.</p> <pre> <html> <head> <title> Mogelijk gemaakt door PCA Framework   

Onze leden

Hieronder staat een lijst met onze leden:

  • name email

De HTML-opmerkingen voor START-leden en END-leden geven het ledenblok (dat wordt verkregen via de methode getBlock () op de pagina) aan. Dit is waar de sjabloonmanager de records in de database doorloopt en deze weergeeft.

Framework in gebruik

Nu moeten we dit allemaal samenbrengen, met ons index.php-bestand:

 // vereisen onze registry require_once ('PCARegistry / pcaregistry.class.php'); $ register = PCARegistry :: singleton (); // sla die kernobjecten op $ registry-> storeCoreObjects (); // maak een databaseverbinding $ register-> getObject ('db') -> newConnection ('localhost', 'root', ", 'pcaframework'); // stel de standaard skin-instelling in (we slaan deze op in de database later ...) $ registry-> storeSetting ('default', 'skin'); // vult ons pagina-object in vanuit een sjabloonbestand $ register-> getObject ('template') -> buildFromTemplates ('main.tpl.php') ; // cache een query van onze leden tabel $ cache = $ register-> getObject ('db') -> cacheQuery ('SELECT * FROM leden'); // wijs dit toe aan de leden tag $ registry-> getObject (' template ') -> getPage () -> addTag (' members ', array (' SQL ', $ cache)) / // stel de paginatitel in $ register-> getObject (' template ') -> getPage () -> setTitle ('Onze leden'); // ontleed alles, en spuug het uit $ registry-> getObject ('template') -> parseOutput (); print $ register-> getObject ('template') -> getPage () -> getContent ();

Als we deze pagina nu in onze webbrowser bekijken, worden de resultaten van de zoekopdracht op de pagina weergegeven:

Komt in deel 3 ...

In deel drie zullen we een kleine omweg maken van de ontwikkelingszijde van ons Framework en bekijken hoe we kunnen ontwerpen met ons framework in gedachten, en hoe we HTML-sjablonen kunnen segmenteren zodat ze geschikt zijn voor ons framework. Wanneer we beginnen met het bouwen van onze eerste applicatie met ons framework, zullen we in meer detail kijken naar enkele van de werkingen van deze klassen. Tot slot, bedankt voor uw opmerkingen vorige keer!

  • Abonneer u op de NETTUTS RSS-feed voor meer dagelijkse webontwikkelingen, tuts en artikelen.