Een concreet voorbeeld van het risico om iets zelf te ontwikkelen

In mijn vorige blog beschreef ik over de strategie die ik heb om metadata aan de relaties tussen posts (en ook users) toe te voegen. Daar ben ik direct mee aan de slag gegaan. Een eerste concept heb ik binnen enkele minuten gemaakt omdat ik de een simpele en doeltreffende methode gebruik die perfect aansluit hoe WordPress zelf in elkaar zit.

Het toevoegen en ophalen van de metadata aan de relaties mag dan heel eenvoudig zijn. De uitdaging is in dit geval de integriteit van de database. Wanneer een bepaalde relatie tussen twee posts wordt verwijderd dan blijft de metadata die daaraan gekoppeld is nog gewoon in de database staan. Daarmee raakt de database vervuild.

Die vervuiling is op zichzelf geen al te groot probleem. Ook kan niet worden gezegd dat daardoor de database direct corrupt raakt of anderzijds de integriteit van de database in het geding is. Dat gebeurt wel op het moment dat de relatie opnieuw wordt toegevoegd én de metadata die een oude relatie beschrijft als metadata van de nieuwe relatie worden beschouwd.

Dat probleem ontstaat in het geval van de ORM die ik in Wpx gebruik omdat de relatie tussen twee posts géén (eigen) uniek ID heeft. De koppeling vindt daardoor plaats met de foreign_key van de post waaraan gekoppeld wordt. Die zal bij een oude en nieuwe relatie daarom hetzelfde zijn.

Het is daarom belangrijk om er rekening mee te houden dat dit kan gebeuren. Daarvoor zijn er verschillende mogelijkheden. De meest accurate zijn om bij het toevoegen en/of verwijderen van een bepaalde relatie de (oude) metadata te verwijderen en vervolgens eventuele nieuwe metadata toe te voegen. Daarnaast is het ook mogelijk om dit te doen met een cronjob die speurt naar metadata die niet meer aan een relatie is gekoppeld en deze verwijderd.

De mogelijkheid om met een timestamp of een checksum als extra metadata te controleren of de relatie nog wel de juiste is zie ik niet. Het probleem is dat hiermee niet kan worden gecontroleerd of dat de relatie op enig moment verwijderd is en later weer is toegevoegd. Ook het ID in de metadata tabel zelf is hier niet voor geschikt omdat bij wijzigingen van posts vaak eerst (alle) metadata die wordt bijgewerkt wordt verwijderd en vervolgens weer wordt toegevoegd.

Dit laatste maakt ook de eerste twee oplossingen een uitdaging om te implementeren. Het verwijderen en weer toevoegen van de metadata zijn twee verschillende processen. Er wordt dus een losse verwijdering waargenomen (die te filteren is met een hook) en vervolgens een losse toevoeging waargenomen (die eveneens te filteren is met een hook).

Ook deze twee processen in de gaten houden is een uitdaging. Het probleem is namelijk dat een verwijdering niet perse gevolgd hoeft te worden door het (onmiddellijk) weer toe te voegen. Het probleem is dat het systeem met filters niet weet of die toevoeging nog zal volgen. Wanneer dat niet het geval is, dan moet de metadata worden verwijderd alleen twijfel ik er over of daar nog (volledige) zekerheid over te krijgen is. Dat soort onzekerheid mag er simpelweg niet zijn.

Het probleem geldt ook met het filteren op het toevoegen van een relatie. De toevoeging van de relatie kan vooraf zijn gegaan door een verwijdering van die relatie. Die verwijdering moet dan wel met zekerheid zijn geregistreerd door het systeem. Ook hier moet daar absolute zekerheid over zijn. Het probleem is dat die absolute zekerheid er niet is.

Dit is dus precies het risico dat ik in een eerdere blog beschreef over dingen zelf bouwen. De dingen die er al zijn hebben hun beperkingen alleen zijn ze wel volledig doorontwikkeld en heb je daarmee als programmeur ook véél meer zekerheid dat de applicatie die je maakt ook zal functioneren zoals dat bedoeld is.

De mogelijkheid om metadata aan relaties tussen posts toe te voegen

De mogelijkheid om relaties tussen posts aan te brengen is de basis van de ORM die het Wpx framework aan WordPress toevoegt. Het principe is héél eenvoudig namelijk door aan een bepaalde post als metadata toe te voegen waarbij de meta_value naar het ID van een andere post verwijst als foreign key. De systematiek is even simpel als doeltreffend.

Op dit moment wordt de relatie tussen twee posts alleen bepaald door de verwijzing vanuit de metadata van één post naar een andere post. De relatie zelf, die met een bepaalde reden bestaat, is alles wat we weten over de relatie. Of te wel het feit dat er een relatie bestaat tussen bijvoorbeeld “company” en “hostingbase” betekent dat het de hostingbase van een bepaalde company is.

Wanneer we naar autorisaties kijken waarbij de “user” die eigenaar is van een bepaalde “company” post de daaraan gekoppelde “hostingbase” posts mag bewerken dan is het eenvoudig om te controleren of een bepaalde “user” geautoriseerd is. Stel echter dat de “company” gebruik maakt van een bepaald “datacenter” en ook daaraan gekoppeld is, dan is het mogelijk de “company” dat ruimte huurt als ook dat het zelf de eigenaar/exploitant ervan is.

Ook dit probleem is simpel en doeltreffend op te lossen. Je maakt namelijk een relatie aan voor huurders én de eigenaar. Het soort relatie vertelt dan of dat de bewuste “company” eigenaar of huurder is. Het enige serieuze nadeel hiervan is dat er twee aparte relaties zijn tussen de beide posts. Het al dan niet aanwezig zijn van één of beide relaties vertelt dan iets over de relatie.

Hetzelfde probleem als hierboven zou je kunnen hebben wanneer je gebruikers hebt met verschillende autorisaties. Het gaat dan om een user-post relatie al komt het verder op hetzelfde neer. Dan is het handig als je aan de relatie een bepaalde hoedanigheid of autorisaties kan toevoegen zodat meer fine-grained kan worden bepaald of een bepaalde “user” voor bepaalde zaken geautoriseerd is.

De uitdaging die ik zie is om het toevoegen van metadata aan relaties tussen posts én tussen posts en users toe te voegen met het enkele gebruik van de native mogelijkheden die WordPress biedt. Ik wil dus nadrukkelijk géén eigen tables aan de WordPress database toevoegen of code die om de WordPress Core API’s heengaat.

Het opslaan van de relatie tussen twee objecten (posts en/of users) is op twee plaatsen mogelijk: in de metadata van objecten (Metadata API) en in via de Options API. Het gebruik van de Metadata API betekent dat er altijd aan één bepaald object wordt gekoppeld. Daarnaast biedt de Metadata API ook de mogelijkheid om WP_Query en WP_Meta_Query te gebruiken. De Options API biedt enkel een héél simpele CRUD interface en koppelt data niet aan bepaald object.

De Options API is daarom vooral interessant wanneer er héél veel werk zelf wordt gedaan, waarbij ook SQL-statements zelf zullen moeten worden opgebouwd. Dat lijkt er daarom op neer te komen dat de WordPress Core API’s feitelijk toch worden omzeild en dan is het waarschijnlijk zelfs verstandiger om eigen tabellen aan de WordPress database toe te voegen.

Dan blijft de optie om gebruik te maken van de Metadata API. Hierbij wordt de metadata over de relatie aan een bepaalde post toegevoegd. Dat is in lijn met hoe de ORM van het Wpx framework werkt. De relatie zelf wordt immers ook gedefinieerd als een verwijzing vanuit de metadata vanuit één van de posts.

De vraag is daarom vooral hoe metadata over de relatie op te slaan in de metadata van een bepaalde post. Het meest eenvoudige is om één bepaalde meta key/value toe te voegen waarin alle data staat die is gedefinieerd. Dat is makkelijk te maken en alleen wanneer je met behulp van de WordPress Core API’s een query wilt uitvoeren op die data dan werkt dat niet of zijn er complexe (custom) SQL-statements nodig om dat te laten werken.

Daarom is mijn denkrichting om de relatie tussen posts in de metadata van de post die naar de andere posts verwijst op te op te slaan. Daarbij zou er voor elk individueel stuk metadata over de relatie zoveel mogelijk eigen meta key/value paren moeten worden gebruikt zodat die zo simpel en doeltreffend mogelijk kunnen worden gebruikt met WP_Query en WP_Meta_Query.

De complexiteit zal er voor in zitten om een dergelijke oplossing te bouwen. Dat is ook het idee achter een framework. Het framework moet de ontwikkeling op applicatieniveau vereenvoudigen en doet de “heavy lifting” voor de programmeur op applicatieniveau. Het is nu de kunst om daar oplossingen voor te vinden.

Het JNOA-veld om technische features eenvoudig vergelijkbaar te maken

Een van mijn beste vindingen die ik als programmeur in de backend van mijn vergelijkingssites heb geïntroduceerd is het JNOA-veld. Als je specificaties van technische dienstverlening of producten vergelijkbaar maakt dan zijn er vaak verschillende features die mogelijk zijn, of niet, onbeperkt in aantal is, of een beperkt aantal of als optie bij zijn te bestellen.

Dat zorgt voor een uitdaging om dat goed te kunnen vastleggen. Bij verschillende aanbieders kan op verschillende features er andere mogelijkheden zijn. De uitdaging die dat als programmeur geeft is dat je dan alle mogelijkheden moet vangen en dat dan voor elk van die features. Het mooie aan zelf aan de knoppen zitten is dat je als programmeur niet gehouden bent aan wat er al is. Je kan het ook zelf maken.

Daarom heb ik het zogeheten JNOA-veld jaren geleden geïntroduceerd in mijn vergelijkingssites. JNOA staat voor Ja, Nee, Optie, Aantal. Het werkt heel eenvoudig. Je hebt twee inputs, één waarbij door de gebruiker wordt aangegeven of de features aanwezig is (met een “select” of “radio inputs”): ja, nee, optioneel of een aantal. Wanneer de gebruiker voor aantal kiest dan kan deze het aantal in een aparte “text input” het aantal aangeven.

Het systeem ziet het echter als één veld en slaat het ook op in één veld in de database. De truc hierbij is dat elk van de waarden die met de JNO corresponderen een niet positieve integer value te geven en wanneer de gebruiker voor de A kiest dat de door de gebruiker ingegeven waarde positief is. Een aantal van een bepaalde feature moet immers altijd positief zijn. Dat kan dan in een “SIGNED INT” of vergelijkbaar veld worden opgeslagen.

Wanneer er wél met negatieve waarden zou moeten worden gewerkt dan kan er een non-integer value aan de JNO-waarden worden gegeven en dan dient de waarde in een “VARCHAR” of vergelijkbaar veld worden opgeslagen.

Het mooie aan het JNOA-veld is dat ook bepaalde waarden voor bepaalde velden niet relevant zijn. Stel dat de vraag of is of dat je PHP-scripts kan draaien, dan is het aantal niet heel relevant. Wanneer de vraag is of een domeinnaam is inbegrepen bij een bepaald hostingpakket dan is “optie” niet relevant. Het is immers inbegrepen of niet. Daarnaast zal de optie “Ja” ook niet snel relevant zijn en “onbeperkt” vermoedelijk ook niet. In een dergelijk geval zal een JNOA-veld dan alleen Nee én Aantal als opties moeten weergeven.

Het JNOA-veld kan met allerlei waarden worden toegepast. In het huidige ontwikkelingsproces heb ik daar ook de U van Unlimited aan toegevoegd. Zo kan er in principe onbeperkt worden gevarieerd met dit concept. Daarnaast is het een perfect voorbeeld hoe je als programmeur zelf de controle moet nemen en je niet moet blindstaren op de opties en mogelijkheden die er al zijn in de programmeertaal, het framework of de library die je gebruikt. Als het er niet nog is, dan bouwen we het toch gewoon zelf!

Security-by-design kan applicatieontwikkeling vereenvoudigen

Dat het custom management systeem dat ik bouw telkens meer vorm begint te krijgen is natuurlijk heerlijk. Bij een dergelijk bouwproces is het ook belangrijk om rekening te houden dat alleen gebruikers die bevoegd zijn of te wel de juiste autorisatie hebben bepaalde acties kunnen uitvoeren. Het is belangrijk dat het security ingebouwd zit in het systeem. Of in andere woorden security-by-design is belangrijk.

In het Wpx framework is er behoefte aan autorisatie op het niveau van een post die als “root” functioneert. De autorisaties die er op die post zijn, bepalen of de gebruiker iets wel of niet mag. In de applicatie waarmee ik het prototype ontwikkel heb je een “company”, “hostingbase” en “hostingpackage”. De toegang tot een bepaalde “company” post bepaald dat je geautoriseerd bent op de daaraan gekoppelde “hostingbase” en “hostingpackage” posts.

Als security-by-design principe wordt bij elke actie die door het custom mangement systeem wordt uitgevoerd altijd eerst de “root” post gezocht, wat in dit geval een “company” post is. Daarna wordt er gekeken of dat de gebruiker geautoriseerd is op die post. Als dat het geval is, dan mag de actie worden uitgevoerd. Als dat niet het geval is dan blokkeert het systeem.

Alleen al het feit dat je in staat moet zijn om de “root” post te vinden maakt security-by-design zo belangrijk. Stel dat onder bepaalde omstandigheden in het custom management systeem deze post niet te vinden is. Dan heb je dus al een probleem dat je moet oplossen. Of mogelijk nog erger dat deze post om een of andere reden niet betrouwbaar is vast te stellen of zelfs ontbreekt voor een bepaalde bewerking.

Daarom is het zo belangrijk om bij elke stap in het bouwproces rekening te houden met dit principe. Als het niet mogelijk is om de “root” post vast te stellen, dan moeten er andere oplossingen worden gekozen waarbij dat wel het geval is.

Het mooie aan dit systeem is dat zodra je de “root” post eenmaal hebt je centraal kan vaststellen of dat de gewenste autorisatie aanwezig is. Sterker nog de actie zelf hoeft alleen de “root” post voor het systeem klaar te zetten. Vervolgens controleert het autorisatiesysteem of dat de gebruiker die de actie probeert uit te voeren bevoegd is. Alleen als dat het geval kan de gebruiker door en in andere gevallen wordt de actie geblokkeerd.

Het custom management systeem heeft hierdoor een gelaagdheid gekregen, waarbij eerste de routing de actie naar de juiste functie doorstuurt. Deze functie zet alle variabelen klaar voor verdere verwerken. Daarna wordt de actie doorgezet naar een functie die lager in het systeem zit. Op dat niveau kan dan meteen worden gekeken of dat de gebruiker geautoriseerd is.

Op applicatieniveau hoef je daardoor nauwelijks nog bewust te zijn van de autorisaties. Het enige dat je moet doen is er voor zorgen dat de “root” post beschikbaar is. Wanneer dat niet zo is, dan blokkeert het systeem ook automatisch omdat er geen autorisaties zijn om te controleren. Op deze manier zorgt security-by-design voor zowel veiligheid áls gemak voor de ontwikkeling op basis van het custom management systeem op applicatieniveau.

Aan een beroep op de WordPress Core API’s ontkom je soms niet

Bij het bouwen van cascade delete in WordPress merkte ik dat de performance bij het deleten van een bepaalde post door de cascade delete van de daaraan gekoppelde posts niet gunstig was. De veroorzaker was de joint fields generation functie die voor elke post werd aangeroepen bij het opslaan van de “cascade” post status.

Het bovenstaande is gewoon positief. Het generen van joint fields is gewoon een feature van het Wpx framework die perfect werkt én ook specifiek is ingesteld voor het type posts. Mijn eerste gedachte was, nou dan zorg ik er toch voor dat ik het genereren daarvan in bepaalde gevallen uit kan zetten. Probleem opgelost. Daar is geen speld tussen te krijgen.

Het punt is alleen dat het ORM bedoeld is om programmeren op applicatie niveau makkelijker te maken. In dit geval gaat het om het Wpx framework zelf waar iets wordt uitgevoerd. Het framework moet applicatie ontwikkeling niet alleen makkelijker maken. Het moet ook zo stabiel en snel mogelijk zijn. De ORM voegt verschillende zaken toe alleen die zorgen ook voor een verminderde performance.

Daarom heb ik er bij de cascade delete voor gekozen om een rechtstreeks beroep te doen op de WordPress Core API’s om de post status in te stellen. Daarvoor gebruik ik “wp_update_post”. Daarnaast voeg ik – net als WordPress zelf bij het trashen van een post – extra metadata aan een post toe met de post_id van de post die verantwoordelijk is voor de cascade én de oude post status. Daarvoor gebruik ik “update_post_meta”. Op die manier is ook een cascade undelete mogelijk.

In dit geval is performance héél belangrijk. Wanneer je verschillende onderling relaties tussen posts heb dan kan het gebeuren dat er door één post te verwijderen plotseling tientallen of zelfs honderden posts moeten worden verwijderd en daarvan ook weer de metadata, categorieën, tags, etc. Als de ORM voor elk van die acties een paar milliseconden toevoegt dan worden dat al snel hele seconden die de gebruiker moet wachten.

Zeker wanneer het om écht veel data gaat die aan elkaar gekoppeld is en in één actie moet worden verwijderd kan het daarom zelfs noodzakelijk zijn om zelfs buiten de WordPress Core API’s bewerkingen rechtstreeks in de database uit te voeren. Op die manier zou je alle ID’s van posts die moeten worden verwijderd kunnen bepalen en dan met één query per tabel alle data verwijderen.

Dat geeft een gigantische performance verbetering. Daar staat tegenover dat de logica en functies in WordPress zelf én in de ORM ook worden omzeilt. Je zult dus héél erg moeten oppassen dat je de database niet sloopt. Performance is belangrijk. De integriteit van de applicatie is heilig. Pas als je de database integriteit onder alle omstandigheden kan garanderen is rechtstreeks schrijf bewerkingen in de database uitvoeren acceptabel.

Daarbij merk ik ook nog op dat óók het rechtstreeks lezen van gegevens uit de database kan leiden tot het slopen van de database. Wanneer de gegevens die worden opgehaald niet kloppen weer worden weggeschreven dan raakt de database ook corrupt. Wel is de kans daarop aanzienlijk kleiner. Al moet ook dat risico niet worden onderschat.

De custom management omgeving krijgt structuur

De custom mangement omgeving die ik aan het bouwen ben begint dan toch structuur te krijgen. Het prototype ontwerp liet zich door creatieve en flexibele keuzes maken snel vangen in een structuur die toch al aardig in beton gegoten begint te raken. Sterker nog het geheel heb ik direct al geïntegreerd binnen het Wpx framework.

De eerste stap die ik heb gezet is om de routing niet meer door WordPress te laten regelen. In plaats daarvan maak ik nu gebruik van de “do_parse_request” filter waardoor ik de routing van mijn manager in eigen hand heb genomen. Feitelijk komt het er op neer dat de functie die aangeroepen wordt door de filter kijkt of de request uri met een bepaald keyword begint. Wanneer dat het geval is dan wordt de request verder door Wpx afgehandeld en doorgeleid naar de custom management omgeving. Dit stuk Wpx code heb ik hieronder neergezet.

    public function init_manager($bool,$wp,$extra_query_vars)
    {
        $uri = trim(esc_url_raw(add_query_arg([])), '/');
        /**
         * Remove query args if any 
         */
        if ($found = strpos($uri,'?')) {
            $uri = substr($uri,0,$found);
        }


        if (substr($uri,0,strlen($this->manager_uri)) == $this->manager_uri) {
                        
            $route = explode('/',substr($uri,strlen($this->manager_uri)+1));

            $name = $route[0];
            if (isset($this->manager_classes[$name])) {
                $class = $this->manager_classes[$name];
                $manager = new $class($this);
                $manager->init();
                $manager->execute_action($route);
                exit();
            }
        }
        return true;

    }

Door Wpx wordt de request vervolgens doorgeleid naar een object dat de verdere routing en uitvoering van de request bepaald. Hierbij heb ik een basis object gemaakt dat de routing regelt, bepaalde basis actions definieert zodat die niet op applicatie niveau hoeven te worden gemaakt en er zijn functies als koppeling fungeren naar de daadwerkelijke uitvoer van HTML code en het verwerken van de invoer daarvan.

De HTML code zelf heb ik óók opgenomen in een class die onderdeel van Wpx is. Ook dat is weer best “dirty”. Daarom heb ik daarvoor een aparte class gemaakt die wordt geïnstantieerd door de functies die vanuit het basis object verantwoordelijk zijn voor de uitvoer van HTML en de verwerking van de invoer daaruit.

Op deze manier is er één class die afwijkt van hoe de Wpx classes normaliter zijn opgebouwd. Aan de andere kant hoeft er op deze manier niet voor één of enkele templates een voorziening in Wpx te worden gebouwd om die op te slaan en op te halen. Uiteindelijk is het uiteraard wel de bedoeling om hier een andere oplossing voor te vinden. Op dit moment werkt deze oplossing uitstekend. Het is een beetje op zijn Nederlands polderen in de code.

De basis objecten worden op applicatie niveau verder uitgebouwd tot manager objecten met eigen actions die bepalen welke uitvoering (execution) gewenst is. Daarbij heb ik als structuur gekozen om op drie dingen te letten, de request method (get, post, put, delete of “any”), de post_type op basis van het post_id in de request én de action. Daarnaast heeft ook de manager een vaste uri én het manager object heeft zelf een naam.

Op die manier krijg je een volgende structuur van de URI:

GET %manager_uri% / %manager_object% / %post_id% / %action% / %var1% / … / %varN%

De uitvoering komt daardoor op het volgende neer:

  1. De “do_parse_request” filter functie wordt aangeroepen
  2. Er wordt gecontroleerd of dat %manager_uri% overeenkomt met het begin van de request uri.
  3. Er wordt gekeken of er een manager object is geregistreerd met de naam die overeenkomt met %manager_object%. Dat wordt dan geladen.
  4. Het manager object neemt de verdere uitvoering over en bepaald aan de hand van de request method, %post_id% en %action% wat de verdere uitvoering moet zijn.
  5. De callback functie die overeenkomt met de drie voornoemde variabelen wordt aangeroepen.
  6. De callback functie haalt de relevante instance op, voert eigen instructies uit en/of instantieert de class met de HTML uitvoer functies en roept de betreffende functie aan.
  7. De HTML wordt door de class uitgevoerd of er wordt juist invoer vanuit de HTML interface door het systeem opgeslagen.
  8. Indien de uitvoering nog niet ten einde zorgt het manager object ervoor dat de gebruiker naar de juist pagina wordt geredirect.

Een idee moet je niet direct in beton proberen te gieten

Het bouwen van een custom management omgeving in WordPress ben ik een klein half jaar geleden al eens mee begonnen. Mijn aanpak was om het direct in classes op te bouwen en de functionaliteit daarmee direct in beton te gieten. Dat werd geen succes. Sterker nog ik heb er weken aan gewerkt en het werkte voor geen meter.

De tweede poging om de custom management omgeving te bouwen ben ik de afgelopen week mee begonnen. Door op een nogal dirty manier – zoals ik in mijn vorige blog heb beschreven – een prototype te bouwen ging dat extreem snel. Dat niet alleen. Het werkt ook nog eens perfect.

Vandaag was mijn gedachte. Het prototype is klaar. Nu is het tijd om het prototype alsnog in classes te verwerken en het geheel in Wpx verwerken. Na een dag programmeren is de conclusie: Dit was geen goed idee. Door alles in een vaste structuur te gieten moet ik keuzes maken die ook in de toekomst gevolgen zullen hebben. Als gevolg daarvan ga je op een heel andere manier programmeren.

Als je iets lekker in beton giet dan kan het geen kant meer op zodra het beton is uitgehard. Dat is precies wat er gebeurd. Je bent je flexibiliteit in het ontwikkelingsproces in één klap volledig kwijt. Daarnaast kun je niet zomaar meer voor creatieve oplossingen gaan om problemen die je gaandeweg tegenkomt op te lossen.

Wanneer de custom management omgeving onderdeel van Wpx wordt dan zal het een bepaalde structuur moeten hebben. Dan moet het in beton gegoten zijn. Op dat moment is het immers onderdeel van een groter geheel. Daar moet het dan ook perfect op aansluiten. Natuurlijk is het ook lekker als het er meteen onderdeel van wordt. Alleen blijkt dat dus een utopie te zijn.

De vervolgvraag is, wat is dan wel verstandig om te doen. De oplossing die ik zelf zie is om te kijken waar flexibiliteit en ruimte voor creatieve oplossingen nodig is en waar standaardisatie in deze fase van het ontwikkelingsproces van toegevoegde waarde is. In andere woorden wat werkt er al perfect én waar zijn nog creatieve en flexibele oplossingen gewenst of zelfs noodzakelijk.

Waar ik bij het bouwen van de custom management omgeving tegenaan loop is vooral de routing en ook verwijzing tussen verschillende management pagina’s. Het prototype heb ik gebouwd rondom hosters, die weer hostingbase posts en daarvan afgeleide hostingpackages hebben. Het is lastig om de onderlinge verwijzingen (relationships) in een model te standaardiseren.

Om dat concreet te maken. Het makkelijkste is om iets te doen als /beheer/%post_id%/edit. Daarbij kan %post_id% van een willekeurige post zijn. Stel je bewerkt een hostingpackage en na het opslaan van de wijzigingen wil je een redirect (terug) naar het overzicht van hostingpackages van de hoster hebben.

Ergens zal het systeem moeten weten dat hij terug moet verwijzen naar de hoster. Door de url structuur kan het systeem het daar niet uithalen. Dan moet dat dus “onderwater” geconfigureerd worden. Dat met een cookie regelen ben ik niet van gecharmeerd. Dan moet er iets in de configuratie van de manager staan. Die configuratie op een zodanige manier standaardiseren betekent dat je of een complexe structuur moet bouwen die rekening houdt met verschillende situaties of juist een simpele structuur pakt die weer beperkingen heeft.

Gelukkig zijn er ook zaken die wél al in beton gegoten kunnen worden. Het bewerken van een bepaalde post zelf is redelijk overzichtelijk. Het grote voordeel daarbij is ook dat in het Wpx framework de metadata velden al gestandaardiseerd zijn. Hier blijkt dus weer een voordeel van standaardisatie. Je zit er aan vast en anderzijds geeft het ook duidelijkheid en maakt het (toekomstige) ontwikkeling van software mogelijk.

De kunst is om in elke fase van het ontwikkelingsproces de juiste mix te vinden van standaardisatie en flexibiliteit bij het bouwen van een prototype. Wanneer ik morgen weer aan de slag ga ik mij eerst richten op wat wél al voor standaardisatie in aanmerking komt. Daarna ga ik daar de flexibele schil als prototype omheen bouwen om vervolgens te kijken wat er al zodanig ontwikkeld is dat ik het ook in beton kan gieten.

Dat proces zal zich waarschijnlijk blijven halen totdat de volledige custom management omgeving in WordPress volledig in beton gegoten is en het uiteindelijk zelfs een integraal onderdeel van het Wpx framework zal uitmaken.

Een custom WordPress management omgeving bouwen

Gezien vanuit het perspectief van de gebruiker kent WordPress twee omgevingen waar een gebruiker mee te maken kan hebben, de blog-omgeving én de wp-admin. Het idee is dat de blog-omgeving voor het lezen van informatie is en de wp-admin voor het beheer.

Om een gebruiker zelf dingen te laten bewerken heb je daardoor twee mogelijkheden. In de wp-admin is héél veel geregeld waardoor het relatief eenvoudig is om dingen te bewerken. Wat je vooral moet regelen als developer is er voor zorgen dat de gebruiker niet te veel kan bewerken. Het is dus vooral een kwestie van de juiste bevoegdheden.

Er zit echter ook een stevig nadeel aan de wp-admin. Het heeft een bepaalde structuur die heel erg dwingend is. Daarnaast is het goed regelen van de permissies ook een uitdaging. Ook het fine-grained regelen van permissies is een uitdaging. Om die structuur heen werken is op zich ook geen probleem zolang je de WordPress API’s correct gebruikt. Dat is dan alleen wel weer aanzienlijk meer werk.

Het belangrijkste nadeel is wat mij betreft niet zozeer technisch van aard. Wanneer een gebruiker ziet dat die met WordPress werkt dan geeft dat een bepaald gevoel geeft. Heel veel mensen werken zelf voor hun huis-tuin-en-keuken blogs ook met WordPress. De associatie daarmee wil je in veel gevallen vermijden.

Het alternatief is om de gebruiker zaken te laten managen via de blog-omgeving van WordPress. Het probleem daarvan is dat je in principe alles zelf zal moeten ontwikkelen. Dat begint al met de routing. Je eigen management omgeving moet op een bepaalde uri bereikbaar zijn. Er moeten daarom nieuwe routes aan WordPress worden toegevoegd. Die routes laat je verwijzen naar de custom management omgeving.

Op dit moment ben ik zelf bezig om zo’n omgeving te bouwen. Dat doe ik als prototype. Daarbij heb ik een héél snelle methode gekozen. Ik heb routes in WordPress aangemaakt die ik naar een bepaalde “page” laat verwijzen. De management interface zelf heb ik gebouwd in de template van de page.

Deze opzet is best “dirty”. Voor het bouwen van een prototype is dat helemaal niet erg. Daarnaast leunt de prototype op de applicatie die gebouwd is op basis van het Wpx framework. Op die manier is de hoeveelheid PHP-code in het template beperkt tot hetgeen dat absoluut noodzakelijk is om de custom management interface te laten werken.

Juist omdat elke vorm van structuur ontbreekt in het prototype heb ik gemerkt dat er vanzelf structuur komt in de custom management omgeving. De structuur die is ontstaan is even eenvoudig als doeltreffend. De gebruiker heeft altijd een bepaalde post die actief is, die post wordt weergegeven, bewerkt, een relationship weergegeven of bewerkt.

De routes van de post zijn daarom als volgt weer te geven, waarbij elke %action% en volgende %sub_action% facultatief zijn: /beheer/%post_id%/%action%/%sub_action1%/%sub_action2%/%sub_action3%/%sub_action4%/

De routes én query variabelen die ik aan WordPress heb toegevoegd om dit mogelijk te maken staan hieronder. In volgende blogs zal ik de verdere ontwikkeling van het prototype van mijn custom management omgeving voor WordPress beschrijven.

add_rewrite_rule('beheer/([^/]*)/([^/]*)/([^/]*)/([^/]*)?$','index.php?pagename=beheerder&beheer_id=$matches[1]&action=$matches[2]&sub_action1=$matches[3]&sub_action2=$matches[4]&subb_action3=$matches[5]','top');
add_rewrite_rule('beheer/([^/]*)/([^/]*)/([^/]*)/([^/]*)?$','index.php?pagename=beheerder&beheer_id=$matches[1]&action=$matches[2]&sub_action1=$matches[3]&sub_action2=$matches[4]','top');
add_rewrite_rule('beheer/([^/]*)/([^/]*)/([^/]*)?$','index.php?pagename=beheerder&beheer_id=$matches[1]&action=$matches[2]&sub_action1=$matches[3]','top');
add_rewrite_rule('beheer/([^/]*)/([^/]*)?$','index.php?pagename=beheerder&beheer_id=$matches[1]&action=$matches[2]','top');
add_rewrite_rule('beheer/([^/]*)/?$','index.php?pagename=beheerder&beheer_id=$matches[1]','top');

add_rewrite_tag('%beheer_id%', '([^/]*)');
add_rewrite_tag('%action%', '([^/]*)');
add_rewrite_tag('%sub_action1%', '([^/]*)');
add_rewrite_tag('%sub_action2%', '([^/]*)');
add_rewrite_tag('%sub_action3%', '([^/]*)');

Een wrapper die WP_Post bijwerkt bij database updates

In mijn vorige blog beschreef ik hoe elk WP_Post object zelfstandig is. Daarnaast merkt een WP_Post object niet op dat de toestand van de post die het vertegenwoordigd in de database veranderd is. Wanneer een applicatie op basis van het WordPress platform wordt ontwikkeld kan dat problematisch zijn.

Helaas is het niet mogelijk om de WP_Post class te overloaden door het gebruik van het keyword “final”. Daarom heb ik een wrapper class gemaakt die een WP_Post object als argument accepteert en door het gebruik van magic methods te gebruiken is als het object dat het wrapt.

De werking van de wrapper is verder eenvoudig. Door het gebruik van de “wp_insert_post” action wordt wanneer er wijzigingen van de post in de database worden doorgevoerd het nieuwe WP_Post object opgenomen in de wrapper. Op die manier is de wrapper “self-aware” en beschik je altijd en automatisch over de versie van het WP_Post object zoals die in de database zijn weggeschreven tijdens runtime.

class Aware_Post
{
    public $ID;
    public $wp_post_object;

    function __construct($post)
    {
        if (is_a('WP_Post',$post)) {
            $this->wp_post_object = $post;
            $this->ID = $post->ID;
        } else {
            $this->ID = $post;
            $this->wp_post_object = get_post($this->ID);
        }
        $this->init_hooks();
    }

    public function init_hooks()
    {
        add_action("wp_insert_post",array($this,'wp_post_updated'),3,10);
    }

    /**
     * Set new post object if it represents our WP_Post object
     */
    public function wp_post_updated($post_id,$post,$updated)
    {
        if ($this->ID == $post_id) {
            $this->wp_post_object = $post;    
        }
    }

    public function __get($key)
    {
        return $this->wp_post_object->__get($key);
    }
    public function __isset($key)
    {
        return $this->wp_post_object->__isset($key);
    }
    public function __call($function,$args)
    {
        if (method_exists("WP_Post", $function)) {
            return call_user_func_array(array($this->wp_post_object, $function), $args);
        }
    }

}

WP_Post objecten in het licht van applicatie ontwikkeling

In de kern mag WordPress dan niet object georiënteerd zijn. Het platform maakt gelukkig wel veel gebruik van classes. Ook de representatie van een post in de database gebeurd met behulp het WP_Post objecten. Dat werkt in principe goed behalve in het geval dat een post tijdens de uitvoering van een applicatie die op WordPress is gebaseerd wordt aangepast.

Het probleem is dat elk WP_Post object dat wordt verkregen via de “get_post” functie op zichzelf staat. WordPress houdt weliswaar een cache bij met alle post data bijhoudt. Wanneer “get_post” wordt aangeroepen wordt er een nieuw WP_Post object aangemaakt.

De enige uitzondering hierop lijkt de global $post variabele te zijn die in “The Loop” wordt gebruikt die wordt teruggeven wanneer “get_post” zonder parameters wordt aangeroepen. Of te wel in dat geval krijg je in plaats van een nieuw WP_Post object een reference naar de global $post variable.

Als gevolg hiervan kunnen er in theorie meerdere WP_Post objecten tegelijkertijd bestaan tijdens runtime met allemaal een andere representatie van de post in de database. Dat wordt niet alleen veroorzaakt doordat er meerdere zelfstandige WP_Post objecten zijn. Er is ook geen signaal naar de objecten toe vanuit WordPress dat er een wijziging in de database is doorgevoerd.

Dit is enkel een “blote constatering”. Als platform waarop blogs worden gepubliceerd is het ook niet bepaald logisch om WP_Post objecten bewust te maken van veranderingen die in de database worden doorgevoerd. Bij een blog schrijf je een artikel in de admin kant van WordPress, dat wordt opgeslagen in de database. Op de website zelf wordt een artikel uit de database geladen zodat het aan de gebruiker kan worden getoond.

In andere woorden de oorspronkelijke opzet van WordPress is eenrichtingsverkeer tijdens een enkele request. Data gaat de database in om op te slaan en wordt er weer uitgehaald om weer te geven. Wanneer het WordPress platform echter als applicatie wordt gebruikt, dan kan er sprake zijn van meerdere keren wordt opgehaald, bewerkt en opnieuw opgeslagen.

Wanneer WordPress als platform voor een applicatie wordt gebruikt is het daarom minder handig als er verschillende representaties van dezelfde post zijn. Natuurlijk kan er altijd een discrepantie zijn tussen wat er in de database is opgeslagen en de representatie tijdens runtime. Dat is zelfs onvermijdelijk omdat aanpassingen eerste op een object worden aangebracht én daarna pas in de database worden doorgevoerd. Zo werkt ORM.

Het is alleen niet de bedoeling dat er tijdens runtime verschillende objecten zijn die allemaal naar dezelfde post in de database verwijzen én verschillende waarden hebben. Dat kan er toe leiden dat er met verkeerde data wordt gewerkt of er zelfs verkeerde data naar de database wordt weggeschreven.

Dit is ook weer één van de zaken die ik met Wpx probeer op te lossen. Als framework zorgt Wpx er voor dat er altijd één versie van een object die verwijst naar een post in de database is. Elke volgende versie van het object dat wordt opgevraagd verwijst naar het al bestaande object. Op die manier is er altijd één versie van een object dat naar een post in de database verwijst.

Er is ook een belangrijk verschil tussen een WP_Post object en de objecten die Wpx gebruikt. Een WP_Post object is vooral een container die data bevat. Daarnaast geeft het ook toegang tot de metadata van een post die worden opgehaald via get_post_meta. De Wpx objecten zijn vooral bedoeld als intermediair tussen WordPress en de PHP code die door de applicatie die daarop gebaseerd is wordt uitgevoerd.

Deze filosofie is ook terug te zien hoe wijzigingen van de data in een Wpx object ten opzichte van de representatie in WordPress en de achterliggende database als “dirty” wordt aangemerkt. Het object is vies omdat de database altijd als leidend moet worden gezien. Pas wanneer de wijzigingen in de database zijn doorgevoerd is het object weer schoon omdat de representatie door het object en de database weer overeenkomen.

Wanneer je een applicatie ontwikkelt op basis van het WordPress platform dan is het belangrijk om rekening te houden met de wijze waarop WP_Post objecten binnen WordPress worden gebruikt. Zeker wanneer gegevens in de database tijdens de uitvoering worden aangepast is het belangrijk om rekening te houden met het feit dat de representatie van een bepaalde post in de database af kan wijken van het WP_Post object waarmee wordt gewerkt.

Gelukkig biedt WordPress ook een eenvoudige oplossing om te voorkomen dat een WP_Post object niet meer overeenkomt met wijzigingen die (via het WordPress platform) tijdens runtime in de database zijn aangebracht. Wanneer via “wp_insert_post” (en functies die daar op leunen zoals “wp_update_post”) wijzingen in de database worden doorgevoerd dan wordt de post cache ook direct gereset. Daardoor zit je door het opvragen van een nieuw WP_Post object met “get_post” in principe altijd veilig.