PHP e MySQL
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 da nova classe PHP para trabalhar com bases de dados MySQL: PDO.
Pretende-se desenvolver uma pequena aplicação Web para gerir os logs de uma qualquer outra aplicação de grande dimensão. No tutorial de PHP e XML havíamos colocado a persistência dos dados num documento XML com a estrutura que é apresentada mais abaixo, neste tutorial iremos colocal a persistência numa base de dados MySQL mas manteremos também a informação no documento XML.
Assim, o Log Manager terá 5 serviços:
No tutorial anterior especificou-se um XML Schema para documentos XML que armazenam logs que se mostra na figura seguinte.
Neste tutorial vamos criar uma base de dados MySQL para a mesma informação.
Como o objetivo é obter uma aplicação simples para exemplificar a utilização da classe PDO, a base de dados é constituída apenas por uma tabela onde se guardam os registos dos logs.
A classe PDO define uma interface leve e consistente para aceder a bses de dados em PHP. Esta classe permite criar drivers específicos para bases de dados específicas através do mecanismo de extensão de classes.
A classe PDO fornece uma camada de abstração de acesso aos dados, o que significa que, independentemente de qual a base de dados a que se está a aceder, usam-se as mesmas funções para realizar inserções, consultas ou pesquisas de dados.
Esta classe usa algumas funcionalidades OO implementadas na nova versão do PHP e apenas funciona em versões do PHP superiores à 5.1. Neste momento as recomendações são para que se use esta classe em detrimento dos métodos procedimentais nativos nas versões anteriores do PHP.
É estabelecida uma ligação à base de dados quando se cria uma instância da classe. A classe PDO trata de abstrair o driver específico para o sistema de gestão de bases de dados em causa. O construtor da classe aceita todos os parâmetros necessários ao estabelecimento da ligação a uma base de dados específica com autenticação. A seguir apresenta-se um exemplo de ligação à nossa base de dados instalada localmente.
<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
?>
No exemplo apresentado, $user e $pass correspondem ao nome e password de um utilizador com permissões para realizar operações sobre a base de dados.
Se pretendermos tratar os erros ocorridos na tentativa de estabelecer uma ligação podemos apanhar a excepção gerada.
<?php
try {
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
foreach($dbh->query('SELECT * from FOO') as $row) {
print_r($row);
}
$dbh = null;
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "
";
die();
}
?>
No exemplo apresentado, $e é a excepção gerada quando não se consegue realizar a conexão.
Como no caso da nossa aplicação os parâmetros de ligação são sempre os mesmos, pode-se criar uma extensão à classe reescrevendo o construtor com aqueles parâmetros pré-estabelecidos.
<?php
class myDB extends PDO{
public function __construct(){
parent::__construct('mysql:host=localhost;dbname=logbook', 'user', 'pass');
}
}
?>
Na sua aplicação, substitua as strings user e pass pelos valores configurados no seu servidor. Esta pequena script foi gravada no ficheiro bd.php.
Com esta extensão, para conetarmos a uma base de dados basta:
<?php
require_once('bd.php');
$bd = new myDB();
...
?>
A linha 2 é necessária para garantirmos que a extensão da classe é carregada.
Vamos agora ver como codificaríamos os restantes serviços da nossa aplicação.
Pretende-se obter como resultado desta operação um logbook vazio. Falta definir qual a representação para um logbook vazio.
Na tutorial anterior havíamos considerado um documento XML com um elemento logs vazio como se ilustra a seguir:
<?xml version="1.0" encoding="ISO-8859-1"?> <logs/>
Neste tutorial queremos simplesmente apagar todos os registos existentes na tabela da base de dados.
As operações na base de dados fazem-se enviando comandos SQL à mesma. O comando SQL para apagar todos os registos de uma base de dados é:
DELETE * FROM table_name
ou simplesmente
DELETE FROM table_name
O nosso serviço codifica-se então da seguinte forma:
<?php
require_once('bd.php');
$bd = new myDB();
$bd->query("DELETE * FROM logs");
echo "Os logs foram apagados. <br/><a href='index.html'>Voltar ao início</a>";
?>
A linha 5 ilustra o envio de um comando SQL à base de dados.
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 neste caso recorre à nossa classe PDO estendida para inserir o registo na base de dados através de um comando SQL.
A sintaxe do comando SQL para inserir registos numa tabela, Insert, é a seguinte:
INSERT INTO table_name
VALUES (value1, value2, value3,....)
ou
INSERT INTO table_name (column1, column2, column3,...)
VALUES (value1, value2, value3,....)
A script de atendimento do formulário que faz a inserção da informação recebida na base de dados apresenta-se a seguir:
<?php
require_once('bd.php');
$nvs = $_GET;
$bd = new myDB();
$qstring = "INSERT INTO logs VALUES ('".
$nvs['tipo']."','".$nvs['data']."','".
$nvs['utilizador']."','".$nvs['operacao']."')";
$bd->query($qstring);
echo "Registo gravado com sucesso. <br/> <a href='index.html'>Voltar ao nicio</a>";
?>
Notas:
$nvs = $_GET; que permite mudar o método de envio declarado no formulário, method="get", para method="post", alterando a script de atendimento apenas nesta atribuição: $nvs = $_POST;;$qstring;$bd->query($qstring);.Muitos dos sistemas de gestão/processamento de bases de dados já suportam o conceito de queries preparadas ou pré-compiladas. Uma query destas pode ser considerada um modelo compilado para SQL que pode ser parametrizado com variáveis.
A principal vantagem deste tipo de queries é que a query apenas é analisada uma vez mas pode ser executada várias vezes, mesmo com parâmetros diferentes.
Veja o seguinte exemplo que realiza a mesma query mas agora criando um modelo parametrizado (neste caso como vamos fazer apenas uma query não há vantagem em fazê-lo desta forma, apenas se demonstra para fins didáticos e para poder ser usado mais à frente).
<?php
require_once('bd.php');
$nvs = $_GET;
$bd = new myDB();
$query = $bd->prepare("INSERT INTO logs VALUES (:tipo, :data, :utilizador, :operacao)");
$query->bindValue(":tipo", $nvs['tipo'], PDO::PARAM_STR);
$query->bindValue(":data", $nvs['data'], PDO::PARAM_STR);
$query->bindValue(":utilizador", $nvs['utilizador'], PDO::PARAM_STR);
$query->bindValue(":operacao", $nvs['operacao'], PDO::PARAM_STR);
$query->execute();
echo "Registo gravado com sucesso. <br/> <a href='index.html'>Voltar ao nicio</a>";
?>
Notas:
$nvs;->prepare(...);->bindValue(...);->execute(...).No nosso exemplo acima parametrizamos o modelo com variáveis mas podíamos tê-los omitido, parametrizando o modelo pela posição.
<?php
require_once('bd.php');
$nvs = $_GET;
$bd = new myDB();
$query = $bd->prepare("INSERT INTO logs VALUES (?, ?, ?, ?)");
$query->bindValue( 1, $nvs['tipo'], PDO::PARAM_STR);
$query->bindValue( 2, $nvs['data'], PDO::PARAM_STR);
$query->bindValue( 3, $nvs['utilizador'], PDO::PARAM_STR);
$query->bindValue( 4, $nvs['operacao'], PDO::PARAM_STR);
$query->execute();
echo "Registo gravado com sucesso. <br/> <a href='index.html'>Voltar ao nicio</a>";
?>
A script é praticamente igual apenas substituímos os placeholders no modelo por ? e fizemos o bind pela posição do parâmetro: ->bindValue( posX, valor).
Este serviço irá listar numa tabela Hyper Text Markup Language a lista de logs armazenados.
O comando SQL para selecionar todos os registos duma tabela tem a seguinte forma:
SELECT * FROM table_name
Para solucionar o nosso problema, basta executar uma query com aquele comando.
O resultado duma seleção é sempre uma lista de registos. No nosso caso, quando a query é enviada com o método PDO::query o resultado é na mesma uma lista de registos que é retornado como um objecto da classe PDOStatement. Podemos iterar sobre este objeto usando a função ->fetch(). Quando os registos se esgotarem esta função retorna o valor lógico falso. Este procedimento consome recursos do lado do servidor da base de dados, provavelmente uma cache onde é mantida a lista de registos. Estes recursos devem ser libertados antes de executarmos outra query. Para isso usaremos o método PDO:closeCursor como veremos num exemplo mais à frente.
<?php
require_once('bd.php');
echo "<table border='1' style='text-align:center;'><tr><th>Tipo</th><th>Data</th><th>Utilizador</th><th>Operação</th></tr>";
$bd =new myDB();
$query = $bd->query("SELECT * FROM logs");
while ($linha = $query->fetch()){
echo "<tr><td>".$linha['tipo']."</td>";
echo "<td>".$linha['data']."</td>";
echo "<td>".$linha['utilizador']."</td>";
echo "<td>".$linha['operacao']."</td>";
echo "</tr>";
}
echo "</table><br/> <a href='index.html'>Voltar ao ínicio</a>";
?>
Até à linha 6, cria-se uma ligação à base de dados e executa-se a query.
Uma alternativa ao método PDO::fetch seria iterarmos diretamente sobre o resultado da query, como se pode ver a seguir.
<?php
require_once('bd.php');
echo "<table border='1' style='text-align:center;'><tr><th>Tipo</th><th>Data</th><th>Utilizador</th><th>Operação</th></tr>";
$bd =new myDB();
$qstring = "SELECT * FROM logs";
foreach ($bd->query($qstring) as $linha)
{
echo "<tr><td>".$linha['tipo']."</td>";
echo "<td>".$linha['data']."</td>";
echo "<td>".$linha['utilizador']."</td>";
echo "<td>".$linha['operacao']."</td>";
echo "</tr>";
}
echo "</table><br/> <a href='index.html'>Voltar ao ínicio</a>";
?>
Uma das maneiras de se conseguir interoperabilidade entre aplicações é através do intercâmbio de informação em formatos bem definidos e abertos. O XML é o melhor candidato no que concerne ao formato. Assim iremos usar o formato XML desenvolvido ao longo do tutorial anterior para fazer backup da aplicação e poder recuperar o seu estado mais tarde.
Neste serviço, recuperamos um estado anterior lendo o documento XML e acrescentando a sua informação à base de dados. Para uma maior flexubilidade, o serviço foi partido em duas partes: um formulário que permite selecionar o ficheiro que se quer carregar e a script que vai processar o ficheiro.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Load XML</title>
</head>
<body>
<form method="post" action="load_xml.php" enctype="multipart/form-data">
<fieldset>
<legend>Logs File:</legend>
<table>
<input type='file' name='logfile'/>
</table>
</fieldset>
<input type="submit" name="Enviar"/>
</form>
</body>
</html>
Repare na utilização do método post e no tipo associado ao formulário multipart/form-data.
Neste caso, a importação de registos de XML, a script fica bastante mais simples se usarmos a classe SimpleXML.
<?php
require_once('bd.php');
$bd = new myDB();
$logs = simplexml_load_file($_FILES['logfile']['tmp_name']);
$query = $bd->prepare("INSERT INTO logs VALUES (:tipo, :data, :utilizador, :operacao)");
foreach($logs->log as $l)
{
$query->bindValue(":tipo", (string)$l->tipo, PDO::PARAM_STR);
$query->bindValue(":data", (string)$l->data, PDO::PARAM_STR);
$query->bindValue(":utilizador", (string)$l->utilizador, PDO::PARAM_STR);
$query->bindValue(":operacao", (string)$l->operacao, PDO::PARAM_STR);
$query->execute();
$query->closeCursor();
}
echo "Logs importados com sucesso.
Voltar ao nicio";
?>
E por fim, o último serviço da nossa aplicação.
Aqui iremos usar a classe DOM porque, como vimos no último tutorial, dá-nos mais controlo na criação do documento.
<?php
require_once('bd.php');
$bd= new myDB();
$dom = new DOMDocument('1.0', 'ISO-8859-1');
$dom->formatOutput = true;
$dom->preserveWhiteSpace = false;
$dom->normalizeDocument();
$logs = $dom->createElement('logs');
$dom->appendChild($logs);
$query = $bd->query("SELECT * FROM logs");
while ($linha = $query->fetch()){
$element = $dom->createElement('log');
$element->appendChild($dom->createElement('tipo', $linha['tipo']));
$element->appendChild($dom->createElement('data', date('Y-m-d',strtotime($linha['data']))));
$element->appendChild($dom->createElement('utilizador', $linha['utilizador']));
$element->appendChild($dom->createElement('operacao', $linha['operacao']));
$logs->appendChild($element);
}
header("Content-type: text/xml;");
echo $dom->saveXML();
?>
Fica aqui terminado este tutorial onde se pretenderam demonstrar as funcionalidades básicas do processamento de bases de dados com PHP. Este conjunto de ideias e scripts pode ser facilmente adaptado para trabalhar com muitas outras aplicações.