Camada de Persistência Orientada a Objetos em PHP

Gabriel Ortiz Lour

Universidade Federal do Paraná, Setor Escola Técnica

Rua Dr. Alcides Vieira Arcoverde, 1225, 81.520-260, Curitiba, PR, Brazil.

elbriga@ufpr.br

Abstract

This paper aims to explain the implementation of a object oriented persistent layer for the graduation project called “pontoConf”. This project is implemented in PHP, a very powerfull scripting language, and is 99% object oriented. For this reason the need for an object oriented persistent layer became absolutely necessary due to the need to persist and restore objects all the time, since if this would be done whithout such persistent layer all the persistent methods for all the persistent objects would need to be coded, one by one. With the implementation of this layer all the persistent methods have the same body, calling default methods, and they will behave different from this only on especial cases, when they are needed.

1. Introdução

Este artigo visa explicar os principios básicos por traz da implementação de uma camada de persistência orientada a objetos.

O termo “camada de persistência orientada a objetos” refere-se a um conjunto de rotinas (muitas vezes em forma de biblioteca) que permite a realização do processo de persistência (isto é, o armazenamento e manutenção do estado de objetos em algum meio não-volátil, como um banco de dados) de forma transparente. Graças à independência entre a camada de persistência e o repositório (backend) utilizado, também é possível gerenciar a persistência de um modelo de objetos em diversos tipos de repositórios, teoricamente com pouco ou nenhum esforço extra. A utilização deste conceito permite ao desenvolvedor trabalhar como se estivesse em um sistema completamente orientado a objetos – utilizando métodos para incluir, alterar e remover objetos e uma linguagem de consulta para SGBDs Orientados a Objetos – comumente a linguagem OQL – para realizar consultas que retornam coleções de objetos instanciados.

A implementação proposta por este artigo é um tanto simples, propositalmente, com o intuito de exemplificar de forma clara a construção de tal biblioteca, por esta razão está for a do escopo a implementação de uma linguagem “OQL”, devido ao maior grau de complexidade. A solução utilizada aqui é a busca simples de objetos, utilizando os números de identificação do objeto (ID), propriedade que deve estar presente em todos os objetos, como pré-requisito para o funcionamento da biblitoca que implementaremos. Caso se faça necessário métodos avançados de busca de objetos o desenvolvedor encontrará facilidade na implementação, devido as rotinas básicas já existentes.

2. Histórico

A biblioteca para persistência orientada a objetos aqui descrita foi implementada para facilitar a utilização de objetos no projeto de graduação “pontoConf”, onde foi verificada a necessidade de um grande número de objetos de negócios, o que aumentaria o tempo de implementação devido a necessidade de se codificar rotinas de banco de dados para todos estes objetos. Este tempo foi diminuido em 70% com a utilização desta camada de persistência orientada a objetos.

3. Fundamentos da biblioteca

Para a utilização da biblioteca que iremos implementar o projeto deve ser planejado desde seu inicio, utilizando um atributo numérico de identificação em cada um dos objetos persistentes do sistema. Além deste requisito inicial uma outra restrição imposta pela nossa biblioteca é o fato de todos os atributos persistentes destes objetos deverão ter o mesmo nome tanto na sua definiçao na classe codificada quanto na respectiva tabela no banco, criando de forma transparente a relação entre a classe e o repositório.

4. Implementação

A implementação do nosso exemplo será feita em PHP, uma linguagem de programação de computadores interpretada, que segue os conecitos de software livre, muito utilizada para gerar conteúdo dinâmico na Web. A escolha desta linguagem de programação se deve ao fato dela ser extremamente flexível, não utilizando tipagem nas variáveis, ou seja, você não diz ao PHP qual o tipo da variável ao declara-la, cabendo ao interpretador descobrir qual o tipo que você deseja e também fazer as conversões necessárias entre estes tipos automaticamente.

Tendo ainda a flexibilidade em mente, e o próprio propósito da camada de persistência, o banco de dados utilizado não precisa ser definido, sendo apenas necessário que seja compativel com o padrão SQL ANSI. Para se conseguir este nível de desacoplamento estaremos trabalhando com duas classes principais, uma que irá cuidar da conexão com o banco de dados, aqui denominada classeConexao, e a outra que irá implementar as funções da tradução dos objetos para banco de dados relacional, aqui denominada classeBdFacil.

Outro conecito muito importante neste tipo de software é a reflexão computacional, ou simplesmente reflexão, que permite que um objeto obtenha informações sobre sua estrutura, sendo usado como uma extensão para o paradima da orientação à objetos. Este conceito é utilizado para se varrer a estrutura dos objetos em tempo de execução, sendo assim possível relacionar as tabelas do repositório com as classes de negócio, assim como relacionar os atributos destas classes com suas respectivas colunas da respectiva tabela persistente.

Antes de começar com os detalhes da implementação é preciso o entendimento das camadas do sistema.

4.1 Camadas

A camada de persistência do sistema é divida em três partes, como podemos observar na figura 1.

A classe de mais baixo nível é a classeConexao.php que conversa diretamente com o repositório de dados. É ela quem cuida da conexão e da execução de instruções SQL. Com esta separação é possível haver suporte a diferentes tipo de bancos de dados, selecionáveis com uma única configuração. No exemplo de implementação deste artigo a classeConexao tem suporte aos SGBDs PostgreSQL e MySQL.

A classebdfacil é a camada intermediária do sistema, onde é realmente implementada a camada de persistência orientada a objetos.

Nesta classe estão implementados métodos para salvar, alterar, recuperar e remover objetos do banco de dados, além de métodos auxiliares, mais específicos para cada sistema.

Na camada de mais alto nível estão as classes persistentes, que se utilizam dos métodos fornecidos pela camada inferior, classebdfacil, para interagir com o repositório de dados.

Figura 1

4.2 Estrutura dos Objetos Persistentes

Os objetos persistentes devem conter os métodos de persistência, enumerados aqui: buscar, salvar, atualizar e remover, que na sua forma mais simples apenas fazem chamada aos métodos da classebdfacil. Quando os métodos persistentes são mais complexos estes podem utilizar os métodos auxiliares da classebdFacil, ou em último caso fazer chamadas a classeConexao diretamente.

4.3 Resumo da Codificação

Colocarei aqui o esqueleto das classes mecionadas, com os métodos explicados em uma mistura de PHP com português estruturado:

Na primeira camada temos a classeConexao:

<?php

define("POSTGRE", 1);

define("MYSQL", 2);

/**

* Conexao com o banco de dados PostgreSQL ou MySQL

* @author Gabriel Ortiz - elbriga@ufpr.br

* @version 1.0, 22/05/2006

*/

class Conexao {

var $tipoBD = POSTGRE;

var $id, $res, $qtd, $data, $erro;

/**

* Faz a conexo padro com o PostgreSQL

*/

function Conexao($serv, $porta, $db, $usuario, $senha) {

if($this->tipoBD == POSTGRE) {

// Faz a conexão com o banco PostgreSQL

} else if($this->tipoBD == MYSQL) {

// Faz a conexão com o banco MySQL

}

}

/**

* Retorna um array de inteiros conforme o SQL

* Estruturar o SQL de forma a prover um... array de ints!

* @param String sql - SQL na forma "SELECT id FROM ...."

*/

function arrayDeInteiros($sql) {

// Implementação, retornar um Array[] de ints

}

/**

* Executa uma query no BD e retorna os dados.

* @param String sql - SQL a ser executado

* @return true para OK, ou MSG de erro

*/

function executa($sql) {

if ($sql=="") {

$this->res = 0;

$this->qtd = 0;

// erro

} else {

if($this->tipoBD == POSTGRE) {

// Executa o SQL no PostgreSQL

} else if($this->tipoBD == MYSQL) {

// Executa o SQL no MySQL

}

}

}

/**

* Busca os dados de uma linha do resultado e posiciona

* o ponteiro na prxima. Para listar os dados no código

* basta utilizar por exemplo:

* while ($conn->temMaisDados()) {

* echo $conn->data["nome"];

* }

* Caso o parametro no seja vazio j retorna o valor do

* campo pedido. Exemplo:

* $conn->executa("SELECT a FROM b WHERE c=1")

* echo $conn->temMaisDados("a")

*/

function temMaisDados($campo="") {

if($this->tipoBD == POSTGRE) {

// $this->data = BUSCAR OS DADOS NO POSTGRESQL AQUI

} else if($this->tipoBD == MYSQL) {

// $this->data = BUSCAR OS DADOS NO MYSQL AQUI

}

}

/**

* Encerra a conexo com o banco de dados.

*/

function fecha() {

if($this->tipoBD == POSTGRE) {

// Fechar conexao com o postgresql

} else if($this->tipoBD == MYSQL) {

// Fechar conexao com o mysql

}

$this->id = "";

$this->res = 0;

$this->qtd = 0;

$this->data = "";

}

}

?>

Na segunda camada temos a classebdFacil:

<?php

require_once("classeConexao.php");

/**

* Classe que auxilia na conversa com o BD

* Só contem métodos estáticos, não deve ser instanciada

*/

class bdFacil {

/**

* Carrega um objeto qualquer do banco, pelo seu ID

* Relaciona as colunas da respectiva tabela, que tem o Nome

* da classe de $obj, com os atributos desta mesma classe

* @param *Object obj - objeto que deve ter o atributo ID > 0

*/

function carregar(&$obj) {

$conn = new Conexao();

$sql = "SELECT * FROM $nomeClasse WHERE id = $id";

$conn->executa($sql);

if($conn->temMaisDados()) {

para_cada(chaves($conn->data) como $nomeAtrBD) {

$obj->$nomeAtr = $conn->data[$nomeAtrBD];

}

}

$conn->fecha();

return $ret;

}

/**

* Salva um Objeto no banco, devendo-se passar os atributos do

* mesmo que se deseja salvar.

* @param Object obj - Objeto que deve ter o atributo ID > 0

* @param String atrs - Nome dos atributos para salvar

* @param String valores - Opcional, valores dos

* atrsParaSalvar, com aspas para strings.

*/

function salvar($obj, $atrs, $valores="") {

// Construir: $nomeClasse$, $camposSet$ e $id

$conn = new Conexao();

$sql = "INSERT INTO $nomeClasse($atrs) VALUES($valores)"

$ret = $conn->executa($sql);

$conn->fecha();

}

/**

* Atualiza um Objeto no banco, devendo-se passar os atributos

* do mesmo que se deseja atualizar.

* @param Object obj - Objeto que deve ter o atributo ID > 0

* @param String camposs - Nome dos atributos a atualizar

* @param String valores - Opcional, valores dos

* camposParaAtualizar, separados por espaços

*/

function atualizar($obj, $camposParaAtualizar, $valores="") {

// Construir: $nomeClasse$, $camposSet$ e $id

$conn = new Conexao();

$sql = "UPDATE $nomeClasse SET $campos WHERE id = $id"

$ret = $conn->executa();

$conn->fecha();

}

/**

* Remove um objeto do banco.

* @param Object obj - objeto a ser removido, ID > 0

* @param int idP - Opcional, ID do objeto a ser removido

*/

function remover($obj, $idP="") {

$id = (empty($idP)) ? $obj->id : $idP;

$conn = new Conexao();

$conn->executa("DELETE FROM $nomeClasse WHERE id = $id");

$conn->fecha();

}

/**

* Lista os IDs de uma tabela

*/

/* static */ function listaIds($nomeTabela, $ordem="") {

}

/**

* Retorna o proximo numero de uma coluna qualquer de uma

* tabela qualquer

* SQL gerado: "SELECT MAX($colunaMax$) FROM $tabela$ "

* + opcionalmente: " WHERE $campoWhere$ = '$valorWhere$'"

*/

function proxNum($colunaMax, $tabela, $campoWhere="",

$valorWhere="", $padrao=1) {

}

/**

* Carrega um array de objetos de uma tabela com restricao

* SQL gerado:

* "SELECT id FROM $estaClasse$ WHERE $nomeOutro$ = $idOutro"

* + Opcionalmente: " ORDER BY $ordem$"

*/

function carregarPor($estaClasse, $nomeOutro, $idOutro,

$ordem="") {

}

/**

* Carrega um ID de uma tabela com alguma restricao

* SQL gerado:

* "SELECT id FROM $estaClasse$ WHERE $nomeOutro$ =

* '$valOutro'"

*/

function getIdPor($estaClasse, $nomeOutro, $valOutro) {

}

/**

* Retorna o total de linhas de uma tabela.

*/

function total($nomeClasse) {

}

}

?>

Na ultima camada temos as classes Persistentes:

Exemplo:

<?php

/**

* Representa uma Tarefa a ser executada pelo sistema

*/

class Tarefa {

var $id;

var $usuario_id;

var $nome;

var $dataCriacao;

var $grupoPadrao;

var $descricao;

var $listaAcaoMin; // Lista de Ação

var $listaAcao; // Lista de Implementações da Ação

/**

* Construtor, carrega o Ação Minima por padrão.

* @param boolean completa - Inidica se deve carregar a

* Tarefa por completo, com todas as suas Ações

*/

function Tarefa($id=0, $completa=false) {

$this->id = $id;

if($id != 0) $this->carregar($completa);

}

/**

* Carrega esta tarefa no BD

* @param boolean completa - Se for true carrega tambem a

* listaAcao, alem da listaAcaoMin

*/

function carregar($completa=false) {

bdFacil::carregar($this);

$this->carregaAcoes($completa);

}

/**

* Método auxiliar para carregar a lista de Ações da Tarefa

* @param Conexao conn - Conexao com o banco, já aberta,

* pois não fecha no final.

* @param boolean completa - Deixar false para carregar

* apenas 3 (max) Acao Mí­nima.

*/

function carregaAcoes($completa=false) {

$this->listaAcaoMin = Acao::carregarPorTarefa($this->id);

if($completa) {

$this->listaAcao = array();

foreach($this->listaAcaoMin as $acaoMin) {

$this->listaAcao[] = Acao::fabrica($acaoMin);

}

}

}

/**

* Salva esta tarefa no banco

*/

function salvar() {

$atrsParaSalvar =

"id,usuario_id,nome,dataCriacao,descricao,grupoPadrao";

return bdFacil::salvar($this, $atrsParaSalvar);

}

/**

* remove esta Tarefa do banco

*/

function remover() {

if($this->id < 1)

return "Impossível remover Tarefa sem ID!";

foreach($this->listaAcaoMin as $act) {

if($act->id > 0)$act->remover();

}

bdFacil::remover($this);

}

}

?>

5. O Futuro

Como já foi dito, a implementação descrita aqui é um tanto simples, e não contempla funcionalidades mais avançadas. Gostaria de expor aqui algumas idéias adicionais para incrementar as funcionalidades desta camada de persistência.

Uma boa implementação seria criar classes para representar os campos no banco, e que ao mesmo tempo contemplassem informações sobre como este campo será apresentado na interface. Informações estas como qual o tipo do campo(caixa de texto, caixa de seleção, caixa de marcação boleana, etc), se tal campo é de preenchimento obrigatório (NOT NULL), qual a máscara que deve ser validada. Com estes metadados sobre os campos em mãos é possível fazer uma integração do backend com o frontend, de forma que ao programador só restaria informar onde tal campo se localizará na interface, chamando um método como campo->toHtml(), ou algo do gênero.

Outra implemetação que melhoraria muito este projeto seria a utilização da herança para prover aos objetos persistentes os métodos básicos de persistência. Neste novo modelo os objetos persistentes fariam herança de uma super-classe, no caso a própria classebdFacil, o que tornaria parte destes objetos os métodos de persistência, os quais só seriam sobrescritos caso a rotina de persistencia para tal objeto fosse mais complexa que a rotina padrão. Este método não foi utilizado desde o inicio por limitações da orientação a objetos da versão 4 do PHP.

Por fim uma outra grande melhoria a ser feita seria a adicionar a capacidade de persistir objetos com herança, utilizando recursividade para ir do objeto mais específico, na ponta da herança, até o objeto pai mais genérico, persistindo-os na ordem correta e nas suas devidas tabelas. Com esta implementação eliminaria-se 90% da necessidade de se implementar métodos de persistencia específicos nos objetos.

5. Conclusão

Este artigo mostra as bases para se construir uma camada de persistência orientada a objetos, e ainda da idéias de melhorias na base propósta. A estrutura mostrada aqui é simples, apenas o necessário para a aplicação em questão, mas serve de base para uma implementação mais complexa que contemple todos os aspectos que um software deste tipo deve ter.

A utilização de tal técnica se torna práticamente indispensável em um grande sistema orientado a objetos, e o melhor de tudo é que uma vez implementada tal camada de persistencia ela pode ser reutilizada em outro projetos, que já poderiam ser inclusive modelas de modo a aproveitar todas as funcionalidades providas.

8. Referencias Bibliográficas

Wikipédia: http://pt.wikipedia.org/

PHP.net: http://php.net/.