1. Objeto DBQuery
Agora, nosso objeto DBQuery simplesmente emula um procedimento armazenado - uma vez executado, ele retorna um recurso de resultado que deve ser salvo e se você quiser usar funções no conjunto de resultados (como num_rows() ou fetch_row() ) ), você deve passar o objeto MySqlDB. Então, qual será o efeito se o objeto DBQuery implementar as funções implementadas pelo objeto MySqlDB (que foi projetado para operar nos resultados de uma consulta executada)? Vamos continuar usando o código do exemplo anterior e assumir que nossos recursos de resultado agora são gerenciados por um objeto DBQuery. O código fonte da classe DBQuery é mostrado na Listagem 1.
Listagem 1. Utilizando a classe DBQuery.
requer 'mysql_db.php';
require_once 'query.php';
$db = novo MySqlDb;
$db->connect('host', 'nome de usuário', 'pass');
$db->query('usar content_management_system');
$consulta = new DBQuery($db);
$query->prepare('SELECT fname,sname FROM users WHERE username=:1S AND pword=:2S AND expire_time<:3I');
tentar {
if($query->execute("visualad", "avental", time()))->num_rows() == 1) {
echo('Credenciais corretas');
} outro {
echo('Credenciais incorretas/Sessão expirada');
}
} catch (QueryException $e) {
echo('Erro ao executar consulta: ' . $e);
}
O que mais nos interessa no código modificado acima são as instruções catch e execute.
· A instrução execute não retorna mais um recurso de resultado, mas agora retorna o próprio objeto DBQuery.
· O objeto DBQuery agora implementa a função num_rows() – com a qual já estamos familiarizados na interface do banco de dados.
· Se a execução da consulta falhar, lança uma exceção do tipo QueryException. Quando convertido em string, retorna os detalhes do erro ocorrido.
Para fazer isso, você precisa usar um proxy. Na verdade, você já está usando proxies em nosso objeto DBQuery, mas agora irá usá-lo com mais profundidade para vinculá-lo firmemente ao objeto MySqlDB. O objeto DBQuery foi inicializado com um objeto que implementa a interface do banco de dados e já contém uma função membro execute – que chama o método query() do objeto do banco de dados para executar a consulta. O objeto DBQuery em si não consulta o banco de dados, ele deixa essa tarefa para o objeto DB. Este é um proxy, que é um processo pelo qual um objeto pode implementar um comportamento específico, enviando mensagens para outro objeto que implementa o mesmo comportamento ou semelhante.
Para fazer isso, você precisa modificar o objeto DBQuery para incluir todas as funções que operam em um recurso de resultado do objeto DB. Você precisa usar os resultados armazenados ao executar uma consulta para chamar a função correspondente do objeto do banco de dados e retornar seus resultados. Serão adicionadas as seguintes funções:
Listagem 2: Estendendo a classe DBQuery usando proxies.
classDBQuery
{
.....
função pública fetch_array()
{
if (!is_resource($este->resultado)) {
throw new Exception('Consulta não executada.');
}
return $this->db->fetch_array($this->resultado);
}
função pública fetch_row()
{
if (!is_resource($este->resultado)) {
throw new Exception('Consulta não executada.');
}
return $this->db->fetch_row($this->resultado);
}
função pública fetch_assoc()
{
if (!is_resource($este->resultado)) {
throw new Exception('Consulta não executada.');
}
return $this->db->fetch_assoc($this->resultado);
}
função pública fetch_object()
{
if (!is_resource($este->resultado)) {
throw new Exception('Consulta não executada.');
}
return $this->db->fetch_object($this->resultado);
}
função pública num_rows()
{
if (!is_resource($este->resultado)) {
throw new Exception('Consulta não executada.');
}
return $this->db->num_rows($this->resultado);
}
}
A implementação de cada função é bastante simples. Ele primeiro verifica se a consulta foi executada e, em seguida, delega a tarefa ao objeto do banco de dados, retornando seus resultados como se fosse o próprio objeto da consulta (chamada de função básica do banco de dados).
2. Dicas de tipo
Para que o proxy funcione, precisamos garantir que a variável $db do objeto DBQuery seja uma instância de um objeto que implementa a interface do banco de dados. Dicas de tipo são um novo recurso do PHP 5 que permite forçar parâmetros de função em objetos de um tipo específico. Antes do PHP 5, a única maneira de garantir que um parâmetro de função fosse um tipo de objeto específico era usar a função de verificação de tipo fornecida no PHP (ou seja, is_a()). Agora, você pode simplesmente converter um tipo de objeto — prefixando o parâmetro da função com o nome do tipo. Você já viu dicas de tipo do nosso objeto DBQuery, que garante que um objeto que implementa a interface do banco de dados seja passado para o construtor do objeto.
função pública __construct(DB $db)
{
$this->db = $db;
}
Ao usar dicas de tipo, você pode especificar não apenas tipos de objetos, mas também classes e interfaces abstratas.
3. Lançar exceções
Você deve ter notado no código acima que o que você captura é uma exceção chamada QueryException (implementaremos esse objeto mais tarde). Uma exceção é semelhante a um erro, mas mais geral. A melhor maneira de descrever uma exceção é usar emergência. Embora uma emergência possa não ser “fatal”, ainda assim deve ser tratada. Quando uma exceção é lançada no PHP, o escopo atual de execução é rapidamente encerrado, seja uma função, um bloco try..catch ou o próprio script. A exceção então atravessa a pilha de chamadas – encerrando cada escopo de execução – até ser capturada em um bloco try..catch ou atingir o topo da pilha de chamadas – ponto em que gera um erro fatal.
O tratamento de exceções é outro novo recurso do PHP 5. Quando usado em conjunto com OOP, ele pode obter um bom controle sobre o tratamento e relatórios de erros. Um bloco try..catch é um mecanismo importante para lidar com exceções. Uma vez detectada, a execução do script continuará a partir da próxima linha de código onde a exceção foi detectada e tratada.
Se a consulta falhar, você precisará alterar sua função de execução para lançar uma exceção. Você lançará um objeto de exceção personalizado chamado QueryException - o objeto DBQuery que causou o erro será passado para ele.
Listagem 3. Lança uma exceção.
/**
*Execute a consulta atual
*
* Execute a consulta atual – substituindo quaisquer pontos pelos argumentos fornecidos
* .
*
* @parameters: misto $queryParams,... parâmetros de consulta
* @return: Recurso A — referência que descreve o recurso no qual a consulta é executada.
*/
função pública execute($queryParams = '')
{
//Por exemplo: SELECT * FROM tabela WHERE nome=:1S AND tipo=:2I AND nível=:3N
$args = func_get_args();
if ($this->stored_procedure) {
/*Chama a função de compilação para obter a consulta*/
$query = call_user_func_array(array($this, 'compilar'), $args);
} outro {
/*Um procedimento armazenado não foi inicializado, portanto, ele é executado como uma consulta padrão*/
$consulta = $queryParams;
}
$resultado = $this->db->query($query);
if (! $resultado){
lançar nova QueryException($this);
}
$este->resultado = $resultado;
/* Observe como agora retornamos o próprio objeto, o que nos permite chamar funções-membro a partir do resultado de retorno desta função */
retorne $isto;
}
4. Use herança para lançar exceções personalizadas
No PHP, você pode lançar qualquer objeto como uma exceção, porém, primeiro, a exceção deve herdar da classe de exceção integrada do PHP; Ao criar sua própria exceção personalizada, você pode registrar outras informações sobre o erro, criar uma entrada em um arquivo de log ou fazer o que quiser. Sua exceção personalizada fará o seguinte:
· Registrará a mensagem de erro do objeto de banco de dados gerado pela consulta.
· Forneça detalhes precisos da linha de código onde ocorreu o erro de consulta — examinando a pilha de chamadas.
· Exibir mensagens de erro e texto de consulta – quando convertido em uma string.
Para obter a mensagem de erro e o texto da consulta, diversas alterações precisam ser feitas no objeto DBQuery.
1. Um novo atributo protegido – compiledQuery – precisa ser adicionado à classe.
2. A função compile() atualiza a propriedade compiladoQuery da consulta com o texto da consulta.
3. Uma função deve ser adicionada para recuperar o texto da consulta compilado.
4. Uma função também deve ser adicionada - ela obtém o objeto DB atual associado ao objeto DBQuery.
Listagem 4. Lance uma exceção.
classDBQuery
{
/**
*Armazene a versão compilada da consulta após chamar compile() ou execute()*
* @var string $compiladoQuery
*/
protegido $compiladoQuery;
/**
* Retorna a consulta compilada sem executá-la.
* @parâmetros: misto $params,...parâmetros de consulta* @return: string—consulta compilada*/
compilação de função pública ($params='')
{
if (! $this->stored_procedure) {
throw new Exception("O procedimento armazenado não foi inicializado.");
}
/*substituindo parâmetros*/
$params = func_get_args(); //Obter parâmetros da função $query = preg_replace("/(?compile_callback($params, 1, "2")', $this->query);
return ($this->compiledQuery = $this->add_strings($query)); //Coloque a string de volta na consulta}
função pública getDB()
{
return $this->db;
}
função pública getCompiledQuery()
{
return $this->compiledQuery;
}
}
Agora você pode implementar a classe QueryException. Observe como você percorre a pilha de chamadas para encontrar o local no script que realmente causou o erro. Este é exatamente o caso quando o objeto DBQuery que lança a exceção é uma subclasse que herda do objeto DBQuery.
Listagem 5: Classe QueryException.
/**
*Exceção de consulta
*
*Ao tentar executar uma consulta, se ocorrer um erro, um erro será gerado pelo objeto {@link DBQuery}
*/
classe QueryException estende exceção
{
/**
*Texto de consulta*
* @var string $QueryText;
*/
protegido $QueryText;
/**
*Número/código do erro do banco de dados*
* @var string $ErrorCode
*/
protegido $ErroNumber;
/**
*Mensagem de erro do banco de dados*
* @var string $ErrorMessage
*/
protegido $ErrorMessage;
/**
*Construtor de classe*
* @Parameter: DBQuery $db, que é o objeto de consulta que lança a exceção */
função pública __construct(DBQuery $query)
{
/*Obtém a pilha de chamadas*/
$backtrace = $this->GetTrace();
/*Defina a linha e o arquivo para o local onde o erro realmente ocorreu*/
if (contagem($backtrace) > 0) {
$x = 1;
/*Se a classe de consulta for herdada, precisamos ignorar as chamadas feitas pelas subclasses*/
while((! isset($backtrace[$x]['linha'])) ||
(isset($backtrace[$x]['class']) && is_subclass_of($backtrace[$x]['class'], 'DBQuery')) ||
(strpos(strtolower(@$backtrace[$x]['function']), 'call_user_func')) !== false ) {
/*Execução de loop, desde que não haja número de linha ou a função chamada seja uma subclasse da classe DBQuery*/
++$x;
/*Se chegarmos ao fim da pilha, usaremos o primeiro chamador*/
if (($x) >= contagem($backtrace)) {
$x = contagem($backtrace);
quebrar;
}
}
/*Se o loop acima for executado pelo menos uma vez, podemos decrementá-lo em 1 para encontrar a linha de código real que causou o erro*/
se ($x! = 1) {
$x-= 1;
}
/*Finalmente, podemos definir os números dos arquivos e das linhas, que devem refletir a instrução SQL que causou o erro*/
$this->line = $backtrace[$x]['line'];
$this->arquivo = $backtrace[$x]['arquivo'];
}
$this->QueryText = $query->getCompiledQuery();
$this->ErrorNumber = $query->getDB()->errno();
$this->ErrorMessage = $query->getDB()->error();
/*Chama o construtor de exceção da superclasse*/
parent::__construct('Erro de consulta', 0);
}
/**
*obter texto de consulta*
* @return texto de consulta de string */
função pública GetQueryText()
{
return $this->QueryText;
}
/**
*recebi o número do erro*
* @return número do erro da string */
função pública GetErrorNumber()
{
return $this->ErroNumber;
}
/**
*recebi mensagem de erro*
* @return mensagem de erro de string */
função pública GetErrorMessage()
{
return $this->ErrorMessage;
}
/**
* Chamado quando o objeto é convertido em uma string.
* @return string */
função pública __toString()
{
$output = "Erro de consulta em {$this->file} na linha {$this->line}nn";
$output .= "Consulta: {$this->QueryText}n";
$output .= "Erro: {$this->ErrorMessage} ({$this->ErrorNumber})nn"
;
}
}
Agora o código que você viu no início desta seção funciona.
5. Conclusão
Neste artigo, você viu como o agente mapeia a interface do banco de dados associada à consulta para operações em um resultado de consulta específico. Os objetos DBQuery expõem as mesmas funções, como fetch_assoc(), que os objetos DB. No entanto, tudo isso funciona para uma única consulta. Você também aprendeu como usar exceções personalizadas para fornecer informações detalhadas sobre quando e onde ocorreu um erro e como elas podem controlar melhor o tratamento de erros.