Anders är en webbutvecklare och hårdrockare som gillar brädspel, kaffe och öl.

Detta är ett arkiverat inlägg, som importerats hit för referens. Det kan se konstigt ut och innehålla utdaterad information eller inaktuella åsikter.

Hantera DOM-noder med ArrayAccess och Iterator

Man talar ofta om XML och dess fantastik, dess underbara potential att cacha databaser och annat. Själv använder jag det för enklare data-arrayer som är för simpla och laddas in för sällan för att tjäna på att ligga i en databas.

Att arbeta med DOM i PHP5 har dock sina baksidor: det är litet klurigt att arbeta med vyer om man som jag har en förkärlek för DOMDocument. Problemet bottnar i att det finns tre typer av noder, varav två - attributnoder och textnoder - innehåller data. Dessa två har dessutom helt annorlunda sätt att nås via DOM API.

Vår exempelfil (testcase.xml)

  1. <?xml version="1.0" encoding="utf8"?>
  2. <madr>
  3. <foo name="anders" role="gui developer">
  4. <cons name="yo">pizza</cons>
  5. <cons name="yo">kebab</cons>
  6. <cons name="yo">computers</cons>
  7. <goods>hates vegetables.</goods>
  8. </foo>
  9. <foo name="mimmie" role="author">
  10. <cons name="yo">music</cons>
  11. <cons name="yo">cars</cons>
  12. <goods>silent.</goods>
  13. </foo>
  14. </madr>

Jag tänkte mig först en serialiseringslösning, där jag först laddade in dokumentet, körde en get-operation och förvandlade hela resultatobjektet (DOMNodeList eller DOMElement) till en array. Detta har jag dock redan gjort förut, så jag tog mig ann en litet annorlunda lösningsmetod; jag skrev en klass som agerar som abstrakt resultatshanterare och som implementerar ArrayAccess. ArrayAccess ger mig möjligheten att kunna skapa getters med arraygränssnitt.

I nedanstående exempel använder jag ResultSet för att skriva ut värden på name, role och första cons för varje foo i ovanstående dataträd. Därefter gör jag samma sak med de implementerade DOM-funktionerna i PHP5.

  1. <?php
  2. $doc = new DOMDocument('1.0','utf8');
  3. $doc->load(realpath('testcase.xml'));
  4. $peoples = $doc->getElementsByTagName('foo');
  5. // with ResultSet class
  6. $rs = new ResultSet( $peoples );
  7. foreach($rs as $foo) {
  8. $name = $foo['name'];
  9. $role = $foo['role'];
  10. $firstCons = $foo['cons'][0]['body'];
  11. printf('<p><strong>%s</strong>, <em>%s</em>: %s>/p>',
  12. $name, $role, $firstCons);
  13. }
  14. // old fashion way
  15. foreach ($peoples as $foo) {
  16. $name = $foo->getAttribute('name');
  17. $role = $foo->getAttribute('role');
  18. $firstCons = $foo->getElementsByTagName('cons')->item(0)->lastChild->nodeValue;
  19. printf('<p><strong>%s</strong>, <em>%s</em>: %s>/p>',
  20. $name, $role, $firstCons);
  21. }

Resultatet blir följande utskrift:

anders, gui developer: pizza

mimmie, author: music

anders, gui developer: pizza

mimmie, author: music

Jag föredrar det.

Enbart sett till gränssnittet är det ur min synvinkel en klar förbättring, då det är ett mer konsekvent genom att inte göra skillnad på attribut och textnoder.

Men det är inte allt: ResultSet är konstruerat för att prova alla möjliga utslag; gettern kikar alltså igenom attribut och barnelement, och gör utifrån detta en kvalificerad gissning på vad som efterfrågas.

Detta gör att jag t.ex skulle kunna flytta ner role som ett barnelement, alltså <role>gui developer</role> i,stället för att ha det som ett attribut till foo. Denna flexibilitet har inte traditionella lösningar, då jag specifikt måste be om vad jag vill ha.

Ej perfekt; feedback uppskattas!

Klassen är ett nyck som jag spontant hårdkodade utan minsta planering tidigt imorse (igår natt), och det finns givetvis behov av förbättringar. Det viktigaste är att ingen hantering för dubletter - att ett barnelement och ett attribut skulle ha samma namn - existerar i urvalet.

Klassen nyttjar dessutom inte setters; skrivskydd råder därför i nuvarande version.

Uppdatering: Punkter strukna. Jag har nu suttit och lekt vidare med ett annat interface, Iterator. Denna tillför det sista jag ville ha från klassen. Det är nu fullt möjligt att slänga in objekten direkt i foreach-block for att skapa en iteration.

Klassen är nu en del av MWA, och används redan på en annan sajt (just det, MWA är på väg utanför sina vanliga gränser).