Using SimpleXML and DOM
Tutorial onde se descreve a implementação de uma pequena aplicação Web para gestão de logs e se usam os vários serviços desta aplicação para demonstrar a utilização das duas classes PHP para processamento de XML: SimpleXML e DOM.
Pretende-se desenvolver uma pequena aplicação Web para gerir os logs de uma qualquer outra aplicação de grande dimensão.
O Log Manager deverá ter 3 serviços:
Primeiro há que desenhar a estrutura do registo de logs: o logbook. Optou-se por especificar um XML Schema que se apresenta a seguir.
Depois cria-se uma instância que servirá de base a testes.
<?xml version="1.0" encoding="ISO-8859-1"?> <logs> <log> <tipo>MMM</tipo> <data>1234-11-02</data> <utilizador>jcr</utilizador> <operacao>Criação</operacao> </log> <log> <tipo>PPPP</tipo> <data>1956</data> <utilizador>maria</utilizador> <operacao>remoção</operacao> </log> </logs>
Pretende-se obter como resultado desta operação um logbook
vazio. Falta definir qual a representação para um logbook
vazio.
Para o nosso exercício vamos considerar um documento XML com um elemento logs
vazio como se ilustra a seguir:
<?xml version="1.0" encoding="ISO-8859-1"?> <logs/>
Uma estratégia possível será a de criar um documento XML apenas com a declaração XML e o elemento principal vazio. A classe SimpleXML não pode ser usada na implementação desta estratégia uma vez que só permite manipular o elemento principal, não é possível acrescentar a declaração XML ou outras instruções de processamento que serão necessárias mais à frente. Assim resta-nos a classe DOM como se exemplifica a seguir.
<?php $dom = new DOMDocument('1.0', 'ISO-8859-1'); $element = $dom->createElement('logs'); $dom->appendChild($element); $dom->save('logbook.xml'); echo ">p>Reset feito com sucesso.>/p>"; echo ">a href='log-manager.html'>Voltar ao log-manager.>/a>"; ?>
De início, cria-se uma instância de uma árvore abstracta, apenas com o nodo raíz e um filho que é a declaração XML com os parámetros passados como argumento: new DOMDocument
.
A seguir cria-se um elemento e acrescenta-se este à árvore: os métodos createElement
e appendChild
devem usar-se aos pares.
Por fim, grava-se a estrutura no ficheiro de registo: save(...)
.
Este serviço é constituído por duas partes. Um formulário que recolhe a informação do registo e uma script que processa a informação recebida do formulário. Optou-se por usar o método get
permitindo que a informação, alternativamente ao formulário, possa ser enviada diretamente pelo URL.
A seguir apresenta-se o formulário para recolha de informação.
<h2>Add Log</h2> <form action="add-log.php" method="get"> <fieldset> <legend>Registo de um Log:</legend> Tipo: <input name="tipo" type="text" size="20"/><br/> Data: <input name="data" type="text" size="10"/><br/> Utilizador: <input name="utilizador" type="text" size="20"/><br/> Op: <input name="operacao" type="text" size="60"/><br/> <input type="submit" value="Enviar"/> </fieldset> </form>
Com a utilização de umas stylesheets CSS este formulário terá o seguinte aspecto:
Se não pretendermos usar o formulário e registar diretamente uma entrada podemos tirar partido do método get
que nos permite fazer a operação invocando apenas a script de atendimento com os parâmetros apropriados (este mecanismo poderá ser usado, por exemplo, para integrar este serviço numa aplicação Web).
http://servidor.uminho.pt/app/add-log.php?tipo=MMM&data=2012-11-08&utilizador=jcr&operacao=teste
Note que:
?
inicia a lista de parâmetros;&
.Para processar os dados do formulário é preciso desenvolver uma script que tanto poderá ser escrita usando a classe DOM como a SimpleXML.
Comecemos pela script em DOM.
<?php $logs = new DOMDocument(); $logs->load('logs.xml'); $log = $logs->createElement('log'); $tipo = $logs->createElement('tipo',$_GET['tipo']); $data = $logs->createElement('data',$_GET['data']); $utilizador = $logs->createElement('utilizador',$_GET['utilizador']); $operacao = $logs->createElement('operacao',$_GET['operacao']); $log->appendChild($tipo); $log->appendChild($data); $log->appendChild($utilizador); $log->appendChild($operacao); $logs->documentElement->appendChild($log); $logs->formatOutput = true; $logs->save("logs.xml"); header("Location: log-manager.html"); ?>
Notas:
Element
necessários, repare na utilização do array associativo $_GET
, que corresponde ao método usado no formulário;A mesma script, agora usando a classe SimpleXML
teria o seguinte aspeto:
<?php $logbook = simplexml_load_file("logs.xml"); $log = $logbook->addChild('log'); $log->addChild('tipo', $_GET['tipo']); $log->addChild('data', $_GET['data']); $log->addChild('utilizador', $_GET['utilizador']); $log->addChild('operacao', $_GET['operacao']); $logbook->asXML("logs.xml"); header("Location: log-manager.html"); ?>
A diferença principal é que enquanto em DOM temos duas instruções para colocar um nodo na árvore (criar o nodo e inseri-lo no sítio certo), em SimpleXML podemos fazê-lo de uma só vez.
Este serviço irá listar numa tabela Hyper Text Markup Language a lista de logs armazenados. Pode ser feito de várias maneiras:
SimpleXML
;DOM
;$logs = simplexml_load_file("logs.xml"); print "<html> <head> <title>Listagem de Logs</title> </head> <body> <table id=\"logs\" border=\"1\"> <tr> <th>Tipo</th><th>Data</th><th>Utilizador</th><th>Operação</th> </tr> "; foreach ($logs->log as $l) { print "<tr> <td>".(string)$l->tipo."</td>". "<td>". (string)$l->data."</td>". "<td>".(string)$l->utilizador."</td>". "<td>".(string)$l->operacao."</td>". "</tr> "; } print "</table>"; print "[<a href=\"log-manager.html\">Voltar ao incio</a>] </body> </html>";
Notas:
simplexml_load_file
;foreach
percorre-se um a um cada log
;foreach
podíamos usar alternativamente a expressão: $logs->xpath('//log')
que é mais genérica uma vez que os elementos a processar são os constantes na lista devolvida pela expressão XPath.O modelo de dados do DOM é mais granular e a respectiva classe oferece métodos de mais baixo nível o que tem como implicações: ser possível fazer tudo sobre a estrutura e algumas operações são mais complicadas desmembrando-se em várias pequenas ações.
O parser que cria o DOM não deixa nada de fora, nem os caracteres brancos, o que tem um impacto sobre a estrutura. Se o documento tiver sido criado num IDE ou mesmo pelos métodos do DOM, foi gerado espaço branco entre os elementos. Este espaço branco não é descartado mas sim armazenado em nodos no modelo abstracto baptizados com o nome #text
.
Observe a seguinte rotina PHP que faz uma listagem dos nodos da estrutura abstracta após carregar o documento logs.xml
.
<?php $xmlDoc = new DOMDocument(); $xmlDoc->load("logs.xml"); $x = $xmlDoc->documentElement; foreach ($x->childNodes as $item) { print $item->nodeName . " = " . $item->nodeValue . "<br />"; } ?>
Notas:
DOMDocument
;load
;$x = $xmlDoc->documentElement
;foreach
percorre-se um a um cada filho do elemento principal que corresponde a um log
;nodeName
para listar o nome do nodo;nodeValue
para listar o conteúdo do nodo.Se aplicarmos esta script ao nosso registo de logs obtemos o seguinte resultado:
#text = log = MMM 1234-11-02 jcr Criação #text = log = PPPP 1956 maria remoção #text =
Como se pode ver pelo exemplo, vai ser preciso "fintar" os nodos #text
extra que aparecem no documento.
Podemos então implementar a listagem da seguinte forma:
<?php $logs = new DOMDocument(); $logs->load('logs.xml'); print "<html> <head> <title>Listagem de Logs</title> </head> <body> <table id=\"logs\" border=\"1\"> <tr> <th>Tipo</th><th>Data</th><th>Utilizador</th><th>Operação</th> </tr> "; $loglist = $logs->getElementsByTagName('log'); foreach ($loglist as $l) { print "<tr>"; foreach($l->childNodes as $log) { if($log->nodeName != "#text") { print "<td>".$log->nodeValue."</td>"; } } print "</tr>"; } print "</table>"; print "[<a href=\"log-manager.html\">Voltar ao incio</a>] </body> </html>"; ?>
Nesta e na opção seguinte vai-se utilizar uma stylesheet XSL para transformar o documento XML numa tabela em HTML. O que vai diferir é a maneira como iremos aplicar a transformação ao documento XML.
A stylesheet XSL tem o seguinte aspeto.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl" exclude-result-prefixes="xs xd" version="2.0"> <xsl:output method="html" indent="yes" encoding="ISO-8859-1"/> <xsl:template match="/"> <table border="1"> <tr><th>Tipo</th><th>Data</th><th>Utilizador</th><th>Operação</th></tr> <xsl:apply-templates select="logs/*"/> </table> </xsl:template> <xsl:template match="log"> <tr> <td> <xsl:value-of select="tipo"/> </td> <td> <xsl:value-of select="data"/> </td> <td> <xsl:value-of select="utilizador"/> </td> <td> <xsl:value-of select="operacao"/> </td> </tr> </xsl:template> </xsl:stylesheet>
A stylesheet apresentada é muito simples porque o objetivo aqui é mostrar como as coisas se fazem, não havendo grandes preocupações com o resultado final que poderá ser apurado individualmente nas aplicações a serem desenvolvidas.
Para aplicar esta stylesheet na transformação do documento XML em PHP é preciso carregar ambos em memória e utilizar o processador do XSL que vem com o PHP para fazer a transformação. A script seguinte mostra uma possível maneira de o fazer.
<?php # LOAD XML FILE $logs = new DOMDocument(); $logs->load('logs.xml'); # START XSLT $xslt = new XSLTProcessor(); $XSL = new DOMDocument(); $XSL->load( 'logs.xsl', LIBXML_NOCDATA); $xslt->importStylesheet( $XSL ); #PRINT print $xslt->transformToXML( $logs ); ?>
Notas:
DOMDocument
com a representação abstrata do documento XML (linhas 3 e 4);DOMDocument
com a stylesheet (linhas 8 e 9);Nesta alternativa, depois de termos a stylesheet XSL basta associarmos esta ao documento XML com a seguinte instrução de processamento:
<?xml-stylesheet type="text/xsl" href="logs.xsl"?>
O documento XML ficará com a seguinte estrutura:
<?xml version="1.0" encoding="ISO-8859-1"?> <?xml-stylesheet type="text/xsl" href="logs.xsl"?> <logs> ... </logs>
Na página de administração coloca-se agora o link do List logs
como sendo o documento XML. O browser ao carregar o documento vai detetar a indicação da stylesheet e irá usar o seu processador para transformar o documento antes de o apresentar.
Se optarmos por esta alternativa temos de ter consciência de que o documento XML terá de ser criado sempre com a associação à stylesheet. No caso desta aplição, isto significaria que teríamos de acrescentar a inclusão daquela instrução de processamento na operação de Reset
como se mostra a seguir.
<?php $dom = new DOMDocument('1.0', 'ISO-8859-1'); $xslt = $dom->createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="logs.xsl"'); $dom->appendChild($xslt); $element = $dom->createElement('logs'); $dom->appendChild($element); $dom->save('logs.xml'); echo "<p>Reset feito com sucesso.</p>"; echo "<a href='log-manager.html'>Voltar ao log-manager.</a>"; ?>
Acrescentaram-se as linhas 4 e 5.
Fica aqui terminado este tutorial onde se pretenderam demonstrar as funcionalidades básicas do processamento de XML com PHP. Este conjunto de ideias e scripts pode ser facilmente adaptado para trabalhar com muitas outras aplicações.