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.