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.