Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Cabral Desenvolvedor

VO, DTO, Model, DAO

Recommended Posts

Boa tarde Galera do Fórum Imasters,

 

Tenho algumas dúvidas em relação aos Values Objects, Data Access Object, Models e Data Transfer Object.

 

Pelo que entendi, os VOs e os DTOs, são apenas para armazenar e transferir os valores entre as camadas e classes. Então os são a mesma coisa? Por que recebe estes 2 nomes?

 

Outra dúvida que tenho, é que a model tbm pode ter propriedades para armazenar os valores do objeto, assim como o DTO e VO, além de ter métodos que tratam da regra de negócio, por exemplo, verificar se os valores das propriedades são válidos, etc.

 

Gostaria de saber qual é a melhor forma de integrar tudo isso com o banco de dados, ou seja, colocar as operações de CRUD tudo dentro da Model, ou usar VO e passá-lo como parâmetro, etc.

 

Valeu galera...

Compartilhar este post


Link para o post
Compartilhar em outros sites

vo e dto são a mesma coisa, so muda o autor. se n me engano quem usa o termo dto é o martin fwoler.

seu entendimento sobre vo/dto esta certo.

Compartilhar este post


Link para o post
Compartilhar em outros sites

vo e dto são a mesma coisa, so muda o autor. se n me engano quem usa o termo dto é o martin fwoler.

seu entendimento sobre vo/dto esta certo.

 

E os models? Seria um DTO ou VO, porém com as regras de negócio embutidas na mesma classe?

 

Qual melhor pattern a usar? DTO + DAO ou uma Model com as operações de CRUD dentro da mesma?

 

Abração

Compartilhar este post


Link para o post
Compartilhar em outros sites

eu gosto mais de criar classes utilitarias do q colocar a regra de negocio no model. pq o codigo pode ser melhor reaproveitado...

uso mais DTO + DAO.

Compartilhar este post


Link para o post
Compartilhar em outros sites

manipulção de datas, por exemplo pegar dia util, ultimo dia do mes e outras coisa chatas. ao inves de colocar esse codigo no model você cria uma classe DataUtils, hoje ela começa so com o metodo getDiaUtil(); em outro projeto você tem a necessidade de pegar o ultimo dia do mes... você cria o algoritmo e adiciona na classe DataUtils. Ao logo do tempo você vai ter um biblioteca de classes organizada.

Compartilhar este post


Link para o post
Compartilhar em outros sites

DTO e VO são a mesma coisa:

Definição (DTO, VO, pra que isso?):

- Value Object (VO): nome usado na primeira edição do livro Core J2EE Patterns

 

- Transfer Object (TO): nome usado na segunda edição do livro Core J2EE Patterns, já que o nome 'Value Object' já era usado por outros autores para definir um outro conceito.

 

- Data Transfer Object (DTO): nome usado por Martin Fowler, no livro Patterns of Enterprise Application Architecture.

 

 

Como eu estava pesquisando sistemas distribuídos em java, encontrei sobre isso.

 

Data Transfer Object

 

dtoSketch.gif

 

Se visualizar a imagem, que foi retirada do site do Martin Fowler. Verá que a camada DTO converte o xml para um objeto (toXmlElement() e vice-versa (readXml). Isso facilita muito em sistemas distribuídos.

 

Simplificando, é uma maneira de enviar um objeto, ao invés de atributos de um objeto, por parâmetro para um método. Se fosse ter de enviar os atributos, teria de ser em N parâmetros. Como é apenas um objeto, é apenas um parâmetro. Em um sistema distribuído faz muito sentido. Já em um sistema não distribuído, a camada de negócios trata do objeto em si, sem necessidade de possuir uma camada somente para transferência. Porém, é, também, muito útil em um sistema não distribuído, pois abstrai a criação do objeto entre a camada view e a camada de negócios.

 

Só, ainda não vi a necessidade de seu uso em um sistema não distribuído. Arquitetura DAO sempre me atendeu perfeitamente, MVC também. Profissionalmente, eu utilizo o CodeIgniter, framework de arquitetura MVC. Para sistemas web (não sites), eu utilizo uma arquitetura que aprendi quando estudei C# (WebForms), que nada mais seria que a camada DAO dividida em duas (Data Access e Business Layer). Infelizmente não sei o nome, e provavelmente é mais usado do que imagino (com outro nome obviamente). No bom e velho java, Data Access Object \m/.

Compartilhar este post


Link para o post
Compartilhar em outros sites

nem dto, nem vo, e nem dao sao pattern...o dao eh uma camada da arquitetura 5 tiers, a arquitetura mvc tem 3 camadas: view controller e model, no 5 tiers o model se desmembra em: action, acoes do model, ou as regras de negocio, o dao a camada real de persistencia dos dados no banco, e bean, a entidade.

 

qual o melhor, depende do projeto, nos aki gostamos do mvc, os japoneses do 5 tiers...

 

o vo, e o dto sao a mesma coisa, realmente, e o dao a persistencia...

 

sobre crud, em oo, eu prefiro usar o padrao active record, pra mim eh o padrao q mais permite flexibilizacao e mapeamento objeto-relacional..ha um problema, o acitve record eh muito parecido com o row data gateway:o row data gateway recupera os dados somente pra um momento,, ou seja, recuperou, tera q armazena-lo em uma variavel, ou seja, stateless, ja o active record nao, você o instanciou e recuperou os dados, enquanto o objeto estiver vivo, os dados estao la, ou seja, statefull, ha outro detalhe tb, o row data gateway nao usa o padrao query object, o q o torna inflexivel, o active record usa o query object, o q o torna fexivel....

 

Sobre o Active Record, pelo o que eu li no livro "PHP - Programando com Orientação à Objetos", ele obriga toda tabela ter um campo id, este sendo númerico, o que não acontece no dia-a-dia, já que podemos usar como chave primária qualquer campo que não seja nulo e não se repita. Outra coisa que não gostei, é que obrigatoriamente ele seleciona todos os campos de uma tabela.

 

Gostaria de saber se há algum pattern diferente do active record que permita eu selecionar apenas alguns campos do bd ou selecionar alguns campos com join entre 2 ou mais tabelas, etc...

 

Sinceramente, os exemplos que eu vi eu não gostei devido os objetos ficarei realizando diversas consultas ao carregar dados de tabelas separadas ao invés de usar joins, union, etc...

 

Abração

Compartilhar este post


Link para o post
Compartilhar em outros sites

ledo engano, no livro foi sim um numero, mas chave primaria pode , como você disse, qq tipo nao repetido, logo um varchar tb pode, desde q nao venha a se repetir...a forma mais tradicional de uma chave primaria eh usa-la com numero e auto incremento, eu estou desenvolvendo um sistema onde a chave primaria eh concatenada, ou seja formada, pelo numero do cliente, numero do trabalho do cliente no ano, ano, e revisao do trabalho (por ter sido refeito varias vezes encara-se como revisao)...

 

agora pra selecionar so alguns campos, creio (nao tenho certeza) q nao exista, pois tanto o table row, row data gateway, table data gateway e o active record selecionam todos os campos....a nao ser q você use um collection ou um lazy initiliazation....

Estava pensando no pattern Repository, na qual eu passaria no construtor o nome da classe e um objeto criteria do Query Object no método de buscar no banco e o mesmo me retornaria os dados. Acho que isso seria uma alternativa, sendo passado junto no parâmetro os campos que desejo retornar.

Compartilhar este post


Link para o post
Compartilhar em outros sites
sobre crud, em oo, eu prefiro usar o padrao active record, pra mim eh o padrao q mais permite flexibilizacao e mapeamento objeto-relacional..ha um problema, o acitve record eh muito parecido com o row data gateway:o row data gateway recupera os dados somente pra um momento,, ou seja, recuperou, tera q armazena-lo em uma variavel, ou seja, stateless, ja o active record nao, você o instanciou e recuperou os dados, enquanto o objeto estiver vivo, os dados estao la, ou seja, statefull, ha outro detalhe tb, o row data gateway nao usa o padrao query object, o q o torna inflexivel, o active record usa o query object, o q o torna fexivel....

 

Está enganado quanto a isso.

Tanto Row [Data] Gateway quanto Active Record são stateful, pois ambos armazenam as propriedades (campos da tabela) no objeto.

A diferença é que o Active Record mantém dentro de si toda a função de persistência, violando o Princípio da Responsabilidade Única (SRP), enquanto que um objeto Row Gateway SEMPRE utiliza um objeto Table Data Gateway para a realizar a persistência, apesar de possuir métodos de persistência, como save, delete e update, internamente eles delegam essa função para o TDG.

 

A grosso modo, TDG e RDG são o desmembramento do Active Record em duais estruturas mais coesas, com uma única responsabilidade.

 

nem dto, nem vo, e nem dao sao pattern...

Errado, Fowler descreve ambos como padrões, entretanto, VO é uma definição antiga e de certa forma, errônea. DTO ou TO é a forma mais correta, embora VO ainda seja muito utilizado.

DAO é um padrão de acesso à camada de persistência.

 

Model é um nível maior ainda de abstração, presente em padrões de arquitetura, como o MVC e o Presentation-Model.

Ele representa o modelo (ah, vá?) da sua aplicação.

 

- M-ma-mas que isso quer dizer?

 

O Model, meu caro padawan, representa a lógica de negócio da sua aplicação.

 

Não está bom ainda né?

Vamos exemplificar. Pense numa calculadora, tipo aquela do windows ou a gcalc dos ambientes gnome.

P: Qual é a lógica de negócio de uma calculadora?

R: Fazer operações matemáticas.

 

Ou seja, no Model da calculadora, devemos ter funções tais como soma, subtração, raiz quadrada, seno, cosseno, tangente hiperbólica (sim, uma calculadora científica).

 

A nossa aplicação, além realizar operações, na maioria dos casos, precisa armazenar os dados sobre os quais aplicar essas operações. No caso da nossa calculadora, não é necessário, pois tudo o que ela processa são números. Quando você a desliga, todos os dados são perdidos. Se você quiser manter salvo algum histórico de operação, precisará então de uma camada de persistência de dados.

 

Vamos pensar agora em uma aplicativo de perguntas e respostas. Ele mostra algumas perguntas na tela, as quais o usuário deve responder.

 

P: Quais as operações da aplicação?

R: Procurar por perguntas, Processar respostas, Verificar se a resposta está correta, Autenticar usuários etc, etc, etc...

 

P: O que essa aplicação deve armazenar?

R: Perguntas, suas respostas, usuários do sistema, etc...

 

Tudo o que se lista acima em conjunto compõe o famigerado Model.

 

Perceba que o conceito é meio geral. Ficam perguntas: como representar essas informações no meu sistema? como fazer o acesso aos dados armazenados num meio persistente, como o disco?

 

Dessas perguntas, após muita experimentação, chegou-se aos padrões de representação de objetos (DTO, RowDataGateway, Active Record, Plain Old Object, etc) e de acesso à persistência (TableDataGateway, DAO, etc).

 

Só para sintetizar tudo: DTO, DAO, VO, TDG, RDG, etc. se relacionam com o Model numa relação parte para todo, ou seja, eles em conjunto (não todos ao mesmo tempo) compõem o que chamamos Model.

Compartilhar este post


Link para o post
Compartilhar em outros sites

desculpe henrique, eu esqueci que nao sei nada...

Igor.php, se você não tem nada pertinente ao tópico, por favor, não poste besteira.

Se tiver alguma dúvida, leia as regras.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Igor.php leia as regras e, em seguida, leia a MP que te enviei.

 

Amigos, desconsiderem os posts #13, #14 e #15 e continuem com a discussão normal e que vai gerar conhecimento para todos.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Igor.php, se você não tem nada pertinente ao tópico, por favor, não poste besteira.

Se tiver alguma dúvida, leia as regras.

 

Concordo. Igor deu sua opnião, e não aceita ser corrigo por programadores mais experiêntes. Na boa cara, este seu comentário é totalmente infantil. Se você acha que não sabe nada, então aprimore-se, afinal, ninguém nasce sabendo.

O Henrique apenas afirmou que suas respostas estão erradas, e ele fez mais que certo, pois não é legal passar informações erradas por pessoas que procuram entender algum conceito. Mas se você não tem humildade em aeitar as críticas, me desculpe mas você não será / é um péssimo profissional, pois as críticas nos faz evoluir.

 

Eu sei que não estou participando deste tópico, mas pode ter certeza que leio os posts pra aprender.

 

Desculpe a minha sinceridade ignorante, mas este fórum é uma enorme fonte de conhecimento, além de que, estamos lidando com profissionais, e sempre tem pessoas dispostas a nos ajudar quando estamos com dúvidas em determinado assunto.

Compartilhar este post


Link para o post
Compartilhar em outros sites

A diferença é que o Active Record mantém dentro de si toda a função de persistência, violando o Princípio da Responsabilidade Única (SRP), enquanto que um objeto Row Gateway SEMPRE utiliza um objeto Table Data Gateway para a realizar a persistência, apesar de possuir métodos de persistência, como save, delete e update, internamente eles delegam essa função para o TDG.

 

Me interessei pelo TDG e RDG, mais do que pelo Active Record. Teria algum exemplo dele em PHP ou algum diagrama? Com ele seria possível selecionar apenas alguns campos do bd, ao invés de todos? E para se trabalhar com JOINS nas consultas, é bem flexível utilizar este pattern?

Compartilhar este post


Link para o post
Compartilhar em outros sites

É muito flexível. Estou na aula agora e tenho prova amanhã. Talvez na sexta à noite consiga montar algo.

Enquanto isso, sugiro você dar uma olhada no Zend Framework:

:seta: http://framework.zend.com/manual/en/zend.db.table.html

:seta: http://framework.zend.com/manual/en/zend.db.table.row.html

Compartilhar este post


Link para o post
Compartilhar em outros sites

#18

 

por definição TDG ou Table Data Gateway age como uma ponte, um gateway para uma tabela.

Apenas uma instância carrega todos os rows.

No Active Record você tem acesso as colunas (rows) de uma tabela e adiciona a lógica do negócio (business logic ou business model)

 

Acho uma boa opção optar pelo Table Data Gateway ao Active Record.

De forma resumida e grosseira, o "TDG" não prende a lógica do negócio a modelagem de dados. Já no AR isso é o que acontece.

 

Uso prático

Se optar em usar o ZendFramework, tem um recurso onde poderá desativar a verificação de integridade para "driblar" a definição do padrão TDG, permitindo o uso de relacionamentos com JOIN

 

   $sel = $this->select();
  $sel->setIntegrityCheck(false) // true (default): não permite join, false: permite join
         ->from($this)
         ->join('tabela1', 'tabela1.id' = 'tabela2.tabela1_id');

  return $this->fetchAll($sel);

 

:seta:Zend_Db_Table_Selects

 

 

veja tb exemplo prático com DataMapper

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bom, o que era pra ser um exemplozinho bem básico, que eu levaria 3 horas no máximo pra bolar, se transformou num dia inteiro de programação.

A minha ideia inicial era simples demais, não iria funcionar para a exemplificação, aí tive que tirar algumas coisas do conjunto de classes que eu já possuo aqui, porque se não seria tenso demais explicar amiúde cada detalhe.

 

Tentei ir direto ao ponto aqui, sem classes abstratas ou interfaces, apenas as classes concretas. Por esse motivo, essa implementação é presa a um único SGBD (MySQL), utilizando uma única API de acesso (MySQLi). Se não fizesse assim, teria que construir alguns Adapters também, que tornaria o exemplo ainda mais extenso.

 

A ideia é criar Tables e Rows genéricos, que são definidos em tempo de execução. Não é difícil encontrar exemplos por aí que te dizem se tratar de um RDG, mas na verdade, é um DTO (VO) ou DataMapper, dos quais eu pessoalmente não sou muito fã pois eles amarram sua implementação. Para cada entidade na minha aplicação eu preciso criar uma classe Mapper diferente. Se somar isso ao fato de ao utilizar MVC eu já ter que criar no mínimo 1 Model e alguns Controllers, além de ter alguns arquivos Views, cada inserção de entidade no sistema é um pé no saco.

 

Na implementação que eu criei para este exemplo, o objeto table é definido a partir de um "mapa de colunas", passado para o construtor, que contém informações sobre o nome e o tipo da coluna e se a mesma é chave primária ou não. Para não complicar demais, não há suporte a chaves compostas e elas devem ser preferencialmente inteiros com auto incremento.

 

Para as consultas ao banco, estou utilizando prepared statements, que facilitam bastante nossa vida na prevenção de SQL Injection. Ao invés de fazer a camada de segurança manual, deixamos a cargo do SGBD fazê-lo.

 

Para o melhor entendimento, dêem uma olhada no diagrama de classe do conjunto:

yz52z.png

 

Caso não conheçam UML, vou tentar traduzir:

  • Table possui uma instância de Driver, para executar funções sobre o banco de dados.

  • Row possui uma instância de Table. Não existe nada relacionado à persistência de dados em Row, tudo é feito a partir de Table.

  • Row normalmente não precisa de acesso direto ao Driver, então não há uma instância deste dentro dele. A única exceção é que após uma inserção, Row precisa da informação do último id inserido no banco, para poder setar campos com auto incremento. Para isso, ele faz uso do objeto Driver da tabela Table que faz referência.

  • Table não precisa conhecer Row, pois ela recebe apenas arrays de dados para as operações. A única exceção à regra é quando retornamos dados do banco. Quando isso ocorre, Table instancia objetos Row. Também é possível criar um novo objeto Row a partir de Table.

Observe que apesar de fazer sentido o uso de composição/agregação (afinal, uma tabela possui linhas), isso não é feito.

Por quê?

Porque o objeto TDG (Table Data Gateway) (no caso, Table) armazena apenas dados sobre a ESTRUTURA da tabela, enquanto que o objeto RDG (Row Data Gateway) armazena os dados propriamente ditos.

 

Já vi implementações que adicionam os objetos RDG em um array dentro do objeto TDG. Eu acho meio ilógico isso pelo simples fato de que dificilmente teremos TODAS as linhas da tabela buscadas de uma vez só, então não faz sentido ficar colocando dados esparsos dentro do objeto TDG. Mesmo se o fizéssemos, o objeto TDG ficaria extremamente pesado.

 

Além disso, pela definição de ambos, TDG é stateless (não possui "estados"), enquanto RDG é stateful (possui "estados", ou seja, armazena informações que podem variar).

 

Chega de lero-lero e vamos ao código.

 

Driver.php (487 linhas, inclusos os comentários):

 

 

<?php
class Driver {
const INSERT = 0;
const UPDATE = 1;
const DELETE = 2;
const SELECT = 3;

const FETCH_ASSOC 	= 10;
const FETCH_NUM		= 20;
const FETCH_ARRAY 	= 30;
const FETCH_OBJ		= 40;

/**
	* Armazena o próximo statement a ser executado
	* @var MySQLi_stmt
	*/
private $stmt;

/**
	* Armazena o objeto de conexão com o banco de dados.
	* @var MySQLi
	*/
private $connection;

/**
	* Armazena a operação atualmente sendo executada.
	* @var int : veja as constantes de classe
	*/
private $currOp;

/**
	* O modo de fetch dos dados no banco.
	* @var int : veja as constantes de classe
	*/
private $fetchMode = self::FETCH_ASSOC;

/**
	* Armazena os nomes das colunas de tabelas
	* @var array
	*/
private $keys = array();

/**
	* Armazena os valores das colunas de tabelas
	* @var array
	*/
private $values = array();

/**
	* Construtor.
	*
	* @param MySQLi $conn : um objeto MySQLi para a conexão com o banco
	*/
public function __construct(MySQLi $conn) {
	$this->connect($conn);
}

/**
	* Seta o modo de fetch para o objeto.
	*
	* @param int $mode : veja as constantes de classe
	* @return Driver : fluent interface
	*/
public function setFetchMode($mode) {
	$this->fetchMode = $mode;
	return $this;
}

/**
	* Conecta-se ao banco de dados através de um objeto MySQLi.
	*
	* @param MySQLi $conn : o objeto para conexão com o banco
	* @return Driver : fluent interface
	*/
public function connect(MySQLi $conn = null) {
	if(!$this->isConnected() && $conn === null) {
		throw new Exception('Conexão com o banco de dados fechada! 
				Precisamos de um conector.');
	}

	if(!$this->isConnected()) {
		$this->connection = $conn;
	}

	return $this;
}

/**
	* Retorna o objeto de conexão com o banco de dados.
	*
	* @return MySQLi
	*/
public function getConnection() {
	$this->connect();
	return $this->connection;
}

/**
	* Inicia uma transação, deligando o autocommit.
	*
	* @return Driver : fluent interface
	*/
public function begin() {
	$this->connection->autocommit(false);
	return $this;
}

/**
	* Cancela a transação em andamento.
	*
	* @return Driver : fluent interface
	*/
public function rollback() {
	$this->connection->rollback();
	return $this;
}

/**
	* Salva a transação em andamento.
	*
	* @return Driver : fluent interface
	*/
public function commit() {
	$this->connection->commit();
	return $this;
}

/**
	* Verifica se o Driver está conectado ao banco.
	*
	* @return bool
	*/
public function isConnected() {
	return $this->connection instanceof MySQLi;
}

/**
	* Fecha a conexão com o banco de dados.
	* 
	* @return void
	*/
public function close() {
	if($this->isConnected()) {
		$this->connection->close();
		unset($this->connection);
		$this->connection = null;
	}
}

/**
	* Prepara um statement.
	*
	* @param string $sql : um statement SQL válido
	* @return MySQLi_stmt
	*/
public function prepare($sql) {
	return ($this->stmt = $this->getConnection()->prepare($sql));
}

/**
	* Insere dados em uma tabela.
	*
	* @param string $tableName: o nome da tabela
	* @param array $data : os dados para inserir
	* @return int : o número de linhas inseridas com sucesso
	*/
public function insert($tableName, array $data) {
	$this->currOp = self::INSERT;	
	$cols = array_keys($data);

	$sql = 'INSERT INTO ' . $tableName
        	. '(' . join(', ', $cols ) .  ')'
        	. ' VALUES (' . join(', ', array_fill(0, count($data), '?')) . ')';

	$this->stmt = $this->connection->prepare($sql);
	$this->bindParams($data);
	$this->execute();
	return $this->stmt->affected_rows;
}

/**
	* Atualiza dados em uma tabela, com base numa condicional.
	*
	* @param string $tableName: o nome da tabela
	* @param array $data : os dados para inserir
	* @param string $cond : uma condicional colocada em uma cláusula WHERE
	* @param array $condParams : parâmetros da condição, caso a mesma
	* 		faça parte de um prepared statement
	* @return int : o número de linhas atualizadas
	*/
public function update($tableName, array $data, $cond, array $condParams = array()) {
	$this->currOp = self::UPDATE;	
	$cols = array_keys($data);

	$set = array();
	foreach($data as $key => $value) {
		$set[] = $key . ' = ?';
	}

	$sql = 'UPDATE ' . $tableName
        	. ' SET ' . join(', ', $set)
        	. ' WHERE ' . $cond;

	$params = array_merge($data, $condParams);
	$this->stmt = $this->connection->prepare($sql);
	$this->bindParams($params);
	$this->execute();
	return $this->stmt->affected_rows;
}

/**
	* Deleta dados de uma tabela, com base numa condicional.
	*
	* @param string $tableName: o nome da tabela
	* @param string $cond : uma condicional colocada em uma cláusula WHERE
	* @param array $condParams : parâmetros da condição, caso a mesma
	* 		faça parte de um prepared statement
	* @return int : o número de linhas deletadas
	*/
public function delete($tableName, $cond, array $condParams = array()) {
	$this->currOp = self::DELETE;	

	$sql = 'DELETE FROM ' . $tableName
        	. ' WHERE ' . $cond; 

	$this->stmt = $this->connection->prepare($sql);
	$this->bindParams($condParams);
	$this->execute();
	return $this->stmt->affected_rows;
}

/**
	* Realiza uma seleção de dados na tabela, guiada por alguns parâmetros.
	*
	* @param string $tableName: o nome da tabela
	* @param string $order : como a busca deve ser ordenada, na forma 
	*		{<col_name> ASC | DESC}, {<col_name> ASC | DESC}, ...
	* @param int $count : o número de linhas para selecionar
	* @param int $offset : a linha a partir do qual começar a busca (0-based)
	* @param array $fields : os campos da tabela a selecionar.
	* 		Caso este parâmetro não estaja setado, utilizamos o SQL wildcard '*'
	* @return boolean
	*/
public function select($tableName, $order = null, $count = null, $offset = null, $fields = array()) {
	$this->currOp = self::SELECT;	

	$fields = (array) $fields;
	$selFields = array();
	if(empty($fields)) {
		$selFields[] = '*';
	} else {
		foreach($fields as $alias => $colName) {
			if(is_string($alias)) {
				$selFields[] = $colName . ' AS ' . $alias; 	
			} else {
				$selFields[] = $colName; 
			}
		}
	}

	$sql = 'SELECT ' . join(', ', $selFields)
        	. ' FROM ' . $tableName;

	if($order !== null) {
		$sql .= ' ORDER BY ' . $order;
	}

	if(($count === null || $count === 0) && $offset !== null) {
		$count = PHP_INT_MAX;
	}

	if((int) $count > 0) {
		if($offset === null) {
			$offset = 0;
		}

		$sql .= ' LITMIT ' . $count . ' OFFSET ' . $offset;
	}

	$this->stmt = $this->connection->prepare($sql);
	return $this->execute();
}

/**
	* Executa uma query.
	*
	* @param string $sqlStmt : um statement ou prepared statement válidos
	* @param array $params : os parâmetros caso $sqlStmt seja um prepared statement
	* @return bool
	*/
public function query($sqlStmt, array $params = array()) {
	if(!is_string($sqlStmt)) {
		throw new Exception('O parametro #1 do método Driver::query() deve ser uma string');
	}

	if(preg_match('/^select/i', $sqlStmt)) {
		$this->currOp = self::SELECT;
	} else if(preg_match('/^insert/i', $sqlStmt)) {
		$this->currOp = self::INSERT;
	} else if(preg_match('/^update/i', $sqlStmt)) {
		$this->currOp = self::UPDATE;
	} else {
		$this->currOp = self::DELETE;
	}

	$this->stmt = $this->connection->prepare($sqlStmt);

	if($this->stmt === false) {
		throw new Exception($this->connection->error);
	}
	return $this->execute($params);
}

/**
	* Retorna todas as linhas buscadas a partir de um SELECT statement.
	* 
	* @return array
	*/ 
public function fetchAll() {
	$data = array();
	while($row = $this->doFetch()) {
		$data[] = $row;
	}
	return $data;
}

/**
	* Retorna a primeira linha buscada a partir de um SELECT statement.
	*
	* @return Row|null
	*/
public function fetchOne() {
	return $this->doFetch();
}

/**
	* Faz de fato a busca dos dados no banco.
	* 
	* @return Row|null
	*/ 
private function doFetch() {
	if($this->currOp !== self::SELECT) {
		throw new Exception('Fetch só pode ser executado com operações do tipo SELECT');
	}

	// Faz o fetch no banco de dados...
	$ret = $this->stmt->fetch();
	// Caso tenhamos chegado ao fim...
	if(!$ret) {
		// O statement agora passa a apontar para o início da tabela...
		$this->stmt->reset();
		return null;
	}

	// Faz uma cópia dos valores, pois eles são referências
	// Caso não façamos isso, ao fazer um novo fetch, os dados são sobrescritos
	$values = array();
	foreach($this->values as $val) {
		$values[] = $val;
	}

	$row = null;
	switch($this->fetchMode) {
		case self::FETCH_NUM:
			return $values;
		case self::FETCH_ASSOC:
			return array_combine($this->keys, $values);
		case self::FETCH_ARRAY:
			$assoc = array_combne($this->keys, $values);
			return array_merge($this->keys, $values);
		case self::FETCH_OBJ:
			return (object) array_combine($this->keys, $values);
		default:
			throw new Exception('Modo de fetch inválido');
	}
}

/**
	* Executa o statement atual
	*
	* @param array $params : parâmetros de statement para binding
	* @return bool
	*/
private function execute(array $params = array()) {
	if($this->stmt === null) {
		return null;
	}
	$this->bindParams($params);
	$ret = $this->stmt->execute();
	if($ret === false) {
		throw new Exception('Error # ' . $this->stmt->errno . 
			': ' . $this->stmt->error);
	}

	// Obtenção dos metadados do select, tais como as colunas retornadas
	$metaData = $this->stmt->result_metadata();
	if($this->stmt->errno) {
		throw new Exception('Erro na obtenção dos metadados:' . $this->stmt->error);
	}
	// Metadados só são retornados em operações de SELECT
	if($metaData !== false) {
		// Definindo as chaves do array (nome dos campos da tabela)
		$this->keys = array();
		foreach($metaData->fetch_fields() as $col) {
			$this->keys[] = $col->name;
		}

		// Criamos um array do tamanho do array $keys, com valores NULL
		$aux = array_fill(0, count($this->keys), null);
		$refs = array();

		// MySQLi_stmt::bind_result precisa de referências para funcionar
		foreach($aux as $i => &$f) {
			// Dessa forma $refs passa a referenciar $aux
			$refs[$i] = &$f;
		}

		$this->stmt->store_result();

		// Associa os resultados ao array $values
		call_user_func_array(
			array(
				$this->stmt, 'bind_result'
			),
			$refs

		);

		$this->values = $aux;
	}
	return $ret;
}

/**
	* Faz o binding de parâmetros para o statement atual.
	*
	* @param array $params : os parâmetros de binding
	* @return bool
	*/
private function bindParams(array $params) {
	if(empty($params)) {
		return;	
	}

	// Precisamos indicar o tipod os parâmetros de binding...
	$bindStr = '';
	foreach($params as $col => $value) {
		// São 3 possíveis...
		if(is_int($value)) {
			$bindStr .= 'i';
		} elseif(is_float($value)){
			$bindStr .= 'd';
		} else {
			$bindStr .= 's';
		}
	}

	// Adiciona a string para indicar os formatos dos dados
	array_unshift($params, $bindStr);

	// Para bind_param funcionar, ele precisa de referências.
	// A única maneira de associar referências de arrays em PHP é a abaixo
	$stmtParams = array();
	foreach($params as $key => &$value) {
		$stmtParams[$key] = &$value;
	}

	// Chamamos a função MySQLi_stmt::bind_param com os argumentos apropriados
	$ret = call_user_func_array(
		array($this->stmt, 'bind_param'),
		$params
	);

	if($ret === false) {
		throw new Exception('Erro ao executar bind_param no statement: ', $this->stmt->error);
	}
}

/**
	* Retorna o último ID auto_increment inserido no banco, na sessão atual.
	* 
	* @return int
	*/ 
	public function lastInsertId() {
	return $this->connection->insert_id;
}
}

 

 

 

Table.php (303 linhas, com comentários)

 

 

<?php
class Table {

/**
	* O nome da tabela
	* @var string
	*/
private $name;

/**
	* O driver MySQLi para a execução dos statements.
	* @var MySQLi
	*/
private $driver;

/**
	* O statement a ser executado no banco de dados.
	* @var MySQLi_Stmt
	*/
private $stmt;

/**
	* Mapa dos campos da tabela. 
	* Cada campo é da seguinte forma:
	* 	array (
	*		name => 'nome do campo',
	* 		type => 'tipo SQL do campo',	
	*		is_primary => 'se o campo é parte da chave primária da tabela (boolean)'
	*	)
	* 
	* Por simplicidade, não consideraremos tabelas com chaves compostas.
	* @var array
	*/
private $colMap = array();

/**
	* Armazena os nomes dos campos da tabela.
	* É obtido a partir de $colMap.
	* @var array
	*/
private $cols = array();

/**
	* O nome da coluna que é a chave primária da tabela
	* @var string
	*/
private $idColName;

/**
	* Checar ou não a integridade dos dados ao criar objetos Row desta tabela.
	* Desabilitar a checagem de integridade é útil para JOINs.
	*/
private $integrityCheck = true;

/**
	* Construtor.
	* 
	* @param string $name: o nome da tabela
	* @param Driver $driver : o driver para o banco de dados 
	* @param array $map : um "mapa" para os campos da tabela, da forma:
	* 	array (
	*		name => 'nome do campo',
	* 		type => 'tipo SQL do campo',	
	*		is_primary => 'se o campo é parte da chave primária da tabela (boolean)'
	*	)
	*/ 
public function __construct($name, Driver $driver, array $map) {
	$this->setName($name);
	$this->setDriver($driver);
	$this->setColMap($map);
}

private function setName($name) {
	$this->name = $name;
}

public function getName() {
	return $this->name;
}

public function setDriver(Driver $driver) {
	$this->driver = $driver;	
	return $this;
}

public function getDriver() {
	return $this->driver;
}

/**
	* Seta um mapa de colunas da tabela.
	* Adicionalmente, com base no mapa, seta o array de colunas da tabela.
	*
	* @param array $map : um "mapa" para os campos da tabela, da forma:
	* 	array (
	*		name => 'nome do campo',
	* 		type => 'tipo SQL do campo',	
	*		is_primary => 'se o campo é parte da chave primária da tabela (boolean)'
	*	)
	* @throws Exception : caso nenhuma chave primária seja informada
	*/
private function setColMap(array $map) {
	$this->colMap = $map;
	$hasPrimary = false;
	// Identifica o campo de ID da tabela
	foreach($map as $column){
		$this->cols[] = strtolower($column['name']);
		if(array_key_exists('is_primary', $column) && 
			(bool) $column['is_primary'] == true) {
			$this->idColName = $column['name'];
			$hasPrimary = true;
		}
	}

	if($hasPrimary === false) {
		throw new Exception(sprintf('Não é possível criar a tabela "%s" sem uma chave primária!', $this->name));		
	}
}

/**
	* Setando a checagem de integridade para false, é possível utilizar 
	* o método createRow com dados não pertencentes à tabela. (Ex.: JOINs e alias).
	*
	* @param bool $opt
	* @return Table : fluent interface
	*/
public function setIntegrityCheck($opt) {
	$this->integrityCheck = (bool) $opt;
	return $this;
}

/**
	* Retorna o nome da coluna que é a chave primária da tabela;
	*
	* @return string
	*/
public function getIdColName() {
	return $this->idColName;
}

/**
	* Retorna um array contendo os nomes das colunas da tabela.
	*
	* @return array
	*/
public function getCols() {
	return $this->cols;
}

/**
	* Salva os dados em $data no banco de dados.
	* A decisão por inserção ou atualização é feita automaticamente.
	* 
	* @param $data : os dados a inserir ou atualizar
	* @return int : o número de linhas afetadas
	*/
public function save(array $data) {
	if(!array_key_exists($this->idColName, $data)
		|| $data[$this->idColName] === null) {
		return $this->insert($data);
	} else {
		return $this->update($data);
	}
}

/**
	* Insere os dados em $data no banco de dados.
	*
	* @param $data : os dados a inserir
	* @return int : o número de linhas inseridas
	*/
public function insert(array $data) {
	$data = $this->intersectData($data);
	return $this->driver->insert($this->getName(), $data);
}

/**
	* Atualiza os dados em $data no banco de dados.
	*
	* @param $data : os dados para atualizar
	* @param $cond : uma condicional para a atualização
	* @param array $condParams : caso a condição seja parte de um 
	*		prepared statement, estes são os parâmetros
	* @return int : o número de linhas atualizadas
	*/
public function update(array $data, $cond, array $condParams = array()) {
	$data = $this->intersectData($data);
	return $this->driver->update($this->getName(),$data, $cond, $condParams);
}

/**
	* Deleta dados do banco.
	*
	* @param $cond : uma condicional para a atualização
	* @param array $condParams : caso a condição seja parte de um 
	*		prepared statement, estes são os parâmetros
	* @return int : o número de linhas deletadas
	*/
public function delete($cond, array $condParams = array()) {
	return $this->driver->delete($this->getName(), $cond, $condParams);
}

/**
	* Retorna dados pela chave primária.
	*
	* @param mixed $id : o valor da chave primária do registro desejado
	* @return Row|null
	*/
public function getById($id) {
	$sql = 'SELECT * FROM ' . $this->getName()
        	. ' WHERE ' . $this->idColName . ' = ?';

	$params = array($this->idColName => $id);
	$this->driver->query($sql, $params);

	$data = $this->driver->fetchOne();
	if($data !== null) {
		return $this->doCreateRow($data, true);
	} else {
		return null;
	}
}

/**
	* Retorna um conjunto de linhas da tabela.
	*
	* @param string|array|null $fields : os campos da tabela a serem retornados. 
	*	Caso seja nulo, utilizamos o SQL wildcard '*', que retornará todos os campos.
	* @param string $order : a ordenação da busca "{<nome_campo><ASC|DESC>}*"
	* @param int count : a quantidade de linhas a retornar
	* @param int offset : a linha inicial a partir da qual começar a buscar (0-based)
	* @return array
	*/
public function getAll($fields = null, $order = null, $count = null, $offset = null) {
	$this->driver->select($this->getName(), $order, $count, $offset, $fields);
	$ret = array();
	$result = $this->driver->fetchAll();

	foreach($result as $rowData) {
		$ret[] = $this->doCreateRow($rowData, true);
	}

	return $ret;
}

/**
	* Método público para a criação de um objeto Row a partir de $data.
	*
	* @param $data : os dados para a criação do objeto
	* @return Row
	*/
public function createRow(array $data = array()) {
	return $this->doCreateRow($data, false);
}

/**
	* Internamente, é possível configurar os dados como 'stored',
	* o que indica que os dados são proveniente do banco, logo, são válidos. 
	* Em chamadas externas, é não é possível garantir isso.
	*
	* @param array $data : os dados para a criação do objeot
	* @param bool $stored : indica se os dados são ou não provenientes do banco.
	*/
private function doCreateRow(array $data, $stored) {
	if($this->integrityCheck === false && !empty($data)) {
		// Se a checagem de integridade está desabilitada, removemos a permissão de escrita
		$newRow = new Row($this, array(				
			'readOnly' => true,
			'stored' => $stored,
			'data' => $data
		));
	} else {
		$cols = $this->cols;
		$defaults = array_combine($cols, array_fill(0, count($cols), null));
		$rowData = array_intersect_key(array_replace($defaults, $data),$defaults);

		$newRow = new Row($this, array(
			'readOnly' => false,
			'stored' => $stored,
			'data' => $rowData
		));
	}
	return $newRow;
}

/**
	* Para operações de inserção e atualização, temos que garantir que
	* entre os dados não haja campos inexistentes na tabela, 
	* o que geraria um erro na execução do statement.
	*
	* @param array $data : os dados para a operação
	* @return array : os dados filtrados, contendo apenas campos da tabela
	*/
private function intersectData(array $data) {
	$dataCols = array_change_key_case($data, CASE_LOWER);
	$tableCols = array();
	foreach($this->colMap as $column) {
		$tableCols[strtolower($column['name'])] = $column['name'];
	}

	return array_intersect_key($dataCols, $tableCols);
}
}

 

 

 

Row.php (375 linhas, com comentários)

 

 

<?php
class Row implements IteratorAggregate, ArrayAccess {
/**
	* Armazena o objeto Table ao qual este objeto pertence.
	* @var Table
	*/
private $table;


/**
	* Armazena os dados do objeto.
	* @var array
	*/
private $data = array();


/**
	* Armazena os dados "limpos" do objeto, ou seja,
	* os provenientes de uma operação SELECT no banco de dados.
	* Ao utilizar set or unset, apenas a propriedade $data é alterada,
	* $cleanData mantém-se da mesma forma até a execução de um refresh.
	* @var array
	*/
private $cleanData = array();

/**
	* Array de flags indicando de o campo foi alterado.
	* Útil para fazer updates sob demanda, economizando
	* um pouco no acesso ao banco de dados.
	* @var array
	*/
private $dirtyData = array();

/**
	* Indica se o objeto Row é somente leitura
	* @var bool
	*/
private $readOnly = false;

/**
	* Construtor.
	* Aqui define-se a tabela à qual o objeto Row se refere.
	* Além disso, definimos também os dados existentes no mesmo.
	* Os únicos índices permitidos para os dados do objeto são
	* os que são informados no construtor.
	* 
	* @param Table $table : o objeto Table ao qual este objeto Row pertence.
	* @param array $info : informações sobre a criação do objeto Row.
	* 
	* Parâmetros válidos para $info:
	* - data		=>	(array) os dados das colunas neste objeto Row
	* - stored		=>	(boolean) se os dados são provindos do banco de dados ou não
	* - readOnly	=>	(boolean) se é permitido ou não alterar os dados desta linha
	*/
public function __construct(Table $table, array $info){
	$this->setTable($table);
	$this->setUp($info);
}

/**
	* Checa as informações passadas ao construtor.
	* É obrigatório informar ao menos os dados do objeto Row.
	* 
	* @return void
	* @throws Exception : caso não exista o índice 'data' em $info 
	*/
private function checkInfo(array $info){
	if(!array_key_exists('data', $info)) {
		throw new Exception('Nenhum dado para ser armazenado no objeto Row.');
	}
}

/**
	* Configura o objeto Row.
	*
	* @param array $info : as informações sobre o objeto
	* @return void
	*/
private function setUp(array $info) {
	$this->checkInfo($info);
	$this->data = (array) $info['data'];

	foreach($info as $k => $val) {
		switch($k) {
			case 'readOnly':
				$this->readOnly = (bool) $val;
				break;
			case 'stored':
				if($val === true) {
					$this->cleanData = $this->data;
				}
				break;
			default:
				// sem ação...
		}
	}
}

/**
	* Seta uma tabela para o objeto Row.
	* 
	* @param Table $table
	* @return Row : fluent interface
	*/
public function setTable(Table $table) {
	$this->table = $table;
	// Fluent interface
	return $this;
}

/**
	* Retorna o objeto Table deste objeto.
	* 
	* @return Table
	*/
public function getTable(){
	return $this->table;
}

/**
	* Seta os dados para o objeto Row.
	* 
	* @param array $data
	* @return void
	*/
private function setData(array $data) {
	$this->data = $data;
}

/**
	* Retorna os dados deste objeto Row.
	*
	* @return array
	*/
public function getData() {
	return $this->data;
}

/**
	* Retorna se o objeto Row é somenta para leitura
	*
	* @return bool
	*/
public function isReadOnly() {
	return $this->readOnly;
}

/**
	* Converte um objeto Row em um array, retornando apenas seus campos.
	*
	* @return array
	*/
public function toArray() {
	return $this->getData();
}

/**
	* Retorna a chave primária do objeto Row.
	*
	* @return mixed
	*/
protected function getPk($useDirty = true) {
	$pkCol = $this->table->getIdColName();
	$array = $useDirty === true ? $this->data : $this->cleanData;
	return array_key_exists($pkCol, $array) ? $array[$pkCol] : null;
}

/**
	* Salva os dados do objeto atual no banco de dados (insere ou atualiza).
	* 
	* @return int : o número de linhas afetadas.
	*/
public function save() {
	if($this->isReadOnly()) {
		throw new Exception('Este objeto Db_Table_Row está marcado como somente-leitura.');
	}

	if(empty($this->cleanData)) {
		return $this->doInsert();
	} else {
		return $this->doUpdate();
	}
}

/**
	* Faz a inserção no banco de dados dos dados no objeto Row.
	* 
	* @return int : o número de linhas inseridas
	*/
private function doInsert() {
	$data = array_intersect_key($this->data, $this->dirtyData);
	$result = $this->getTable()->insert($data);

	$pk = $this->getTable()->getIdColName();
	if(!array_key_exists($pk, $data) || $data[$pk] === null) {
		$this->data[$pk] = $this->getTable()->getDriver()->lastInsertId();
	}

	$this->refresh();
	return $result;
}

/**
	* Faz a atualização no banco de dados dos dados no objeto Row.
	* 
	* @return int : o número de linhas atualizadas
	*/
private function doUpdate() {
	$diffData = array_intersect_key($this->data, $this->dirtyData);
	if(!empty($diffData)) {	
		$pk = $this->getPk(false);
		if($pk === null) {
			throw new Exception('Impossível atualizar uma linha sem chave primária!');
		}

		$pkCol = $this->getTable()->getIdColName();
		$result = $this->getTable()->update($diffData, $pkCol . ' = ?', array($pk));

		$this->refresh();
		return $result;
	}
	return 0;
}

/**
	* Atualiza os dados da linha após uma operação no banco de dados.
	*
	* @return void
	*/
private function refresh() {
	$pk = $this->getPk(false);
	if($pk !== null) {
		$row = $this->getTable()->getById($pk);
		if($row === null) {
			throw new Exception('Não foi possível atualizar a linha da tabela.
									Erro ao buscá-la no banco de dados');
		}

		$this->data = $row->toArray();
		$this->cleanData = $this->data;
		$this->dirtyData = array();
	}
}

/**
	* Remove os dados correspondente ao objeto Row do banco de dados.
	*
	* @return int: o número de linhas afetadas.
	*/
public function delete() {
	if($this->readOnly) {
		throw new Exception('Impossível remover uma linha somente-leitura!');
	}

	$pk = $this->getPk();
	if($pk === null) {
		throw new Exception('Impossível remover uma linha sem chave primária!');
	}

	$pkCol = $this->getTable()->getIdColName();
	$result = $this->getTable()->delete($pkCol . ' = ?', array($pk));

	$this->data = array_combine(
		array_keys($this->data),
		array_fill(0, count($this->data), null)
	);

	return $result;
}

/**
	* Seta os dados do objeto a partir de um array.
	* 
	* @param array $data
	* @return Row : fluent interface
	*/
public function setFromArray(array $data) {
	foreach($data as $key => $val) {
		$this->set($key, $val);
	}

	return $this;
}

/**
	* Seta um valor para uma coluna do banco de dados.
	* 
	* @param string $column : o nome da coluna a ser alterada
	* @param mixed $value: o novo valor da coluna
	* @return Row : fluent interface 
	* @throws Exception : caso a coluna não exista
	*/
public function set($column, $value) {	
	// Irá lançar uma exceção caso a coluna não exista...
	$this->verifyColumn($column);
	if($value !== $this->data[$column]) {
		$this->data[$column] = $value;
		$this->dirtyData[$column] = true;
	}
	// Fluent interface, permite o encadeamento de métodos: $row->set('bla', 'foo')->set('baz', 'bazzinga');
	return $this;
}

/**
	* Retorna o valor de uma coluna.
	*
	* @param string $column : o nome da coluna
	* @return mixed : o valor da coluna
	* @throws Exception : caso a coluna não exista
	*/
public function get($column) {
	// Irá lançar uma exceção caso a coluna não exista...
	$this->verifyColumn($column);
	return $this->data[$column];
}

/**
	* Verifica se a coluna existe no objeto.
	* 
	* @param sting $column : o nome da coluna
	* @return boolean
	* @throws Exception : caso a coluna não exista
	*/
public function has($column) {
	return array_key_exists($column, $this->data);
}

/**
	* Verifica se a coluna existe, lançando uma exceção caso não exista.
	*
	* @param $column : o nome da coluna
	* @throws Exception : caso a coluna não exista
	*/
private function verifyColumn($column) {
	if(!$this->has($column)) {
		throw new Exception(sprintf('A coluna "%s" não existe neste objeto Row!', $column));
	}	
}

/**
	* @see ArrayAccess::offsetSet()
	*/
public function offsetSet($offset, $value) {
	$this->set($column, $value);
}

/**
	* @see ArrayAccess::offsetGet()
	*/
public function offsetGet($offset) {
	return $this->get($column);
}

/**
	* @see ArrayAccess::offsetExists()
	*/
public function offsetExists($offset) {
	return $this->has($column);
}

/**
	* @see ArrayAccess::offsetUnset()
	*/
public function offsetUnset($offset) {
	$this->verifyColumn($column);
	$this->set(offset, null);
}

/**
	* @see IteratorAggregate::getIterator()
	*/
public function getIterator() {
	return new ArrayIterator($this->data);
}
}

 

 

 

Para dar uma brincada, utilize este banco de dados:

 

 

-- --------------------------------------------------------

--
-- Estrutura da tabela `clients`
--

CREATE TABLE IF NOT EXISTS `clients` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(30) COLLATE latin1_bin DEFAULT NULL,
 `registered_by` int(10) unsigned DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `user_reg_client_fk` (`registered_by`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COLLATE=latin1_bin AUTO_INCREMENT=6 ;

--
-- Extraindo dados da tabela `clients`
--

INSERT INTO `clients` (`id`, `name`, `registered_by`) VALUES
(1, 'Seu Madruga', 1),
(2, 'Chapolin', 2),
(3, 'Bob Esponja', 1),
(4, 'Lula Molusco', 1),
(5, 'Patrick Estrela', 3);

-- --------------------------------------------------------

--
-- Estrutura da tabela `users`
--

CREATE TABLE IF NOT EXISTS `users` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(30) COLLATE latin1_bin DEFAULT NULL,
 `login` varchar(30) COLLATE latin1_bin DEFAULT NULL,
 `password` char(32) COLLATE latin1_bin DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COLLATE=latin1_bin AUTO_INCREMENT=22 ;

--
-- Extraindo dados da tabela `users`
--

INSERT INTO `users` (`id`, `name`, `login`, `password`) VALUES
(1, 'Zezinho', 'zezinho_123', '588029867c884e84dfbdb78c5ec6f3a4'),
(2, 'Manuelzinho', 'manololoko', 'd8578edf8458ce06fbc5bb76a58c5ca4'),
(3, 'Luluzinha', 'lugatinha', '912ec803b2ce49e4a541068d495ab570');

--
-- Restrições para as tabelas dumpadas
--

--
-- Restrições para a tabela `clients`
--
ALTER TABLE `clients`
 ADD CONSTRAINT `user_reg_client_fk` FOREIGN KEY (`registered_by`) REFERENCES `users` (`id`);

 

 

 

Exemplo:

<?php
require_once 'Driver.php';
require_once 'Table.php';
require_once 'Row.php';

$connector = new MySQLi('SEU_SERVER', 'SEU_USUARIO', 'SUA_SENHA', 'SEU_DB');
$driver = new Driver($connector);
$table = new Table('users', $driver, array(
array('name' => 'id', 'type' => 'integer', 'is_primary' => true),
array('name' => 'name', 'type' => 'varchar'),
array('name' => 'login', 'type' => 'varchar'),
array('name' => 'password', 'type' => 'char'),
));

 

O código acima é a base pra brincar com a tabela usuários. Note que o "mapa" de colunas não é muito bem detalhado e nem precisa.

 

Inserir uma nova linha

$row = $table->createRow();
$row->set('name', 'Juquinha');
$row->set('login', 'jukaloko');
$row->set('senha', md5('jjjj'));

$row->save(); // Irá inserir os dados na tabela

 

Atualizando dados

$row = $table->getById(1);
$row->set('name', 'Mariazinha');
$row->set('login', 'mary_gatinha_cam_21');
$row->set('senha', md5('mary'));

$row->save(); // Irá atualizar os dados na tabela

 

Removendo dados

$row = $table->getById(1);
$row->delete();

 

A estrutura é bem flexível. Desativando o integrity check de Table, é possível criar Rows a partir de JOINS:

$sql = 'select c.name as client_name, u.name as user_name
from users as u
join clients as c
	on c.registered_by = u.id
WHERE u.name = ?';
$driver->query($sql, array('Zezinho'));
$data = $driver->fetchAll();
$row = $table->setIntegrityCheck(false)->createRow($data);
var_dump($row);

 

Pois é, tudo junto dá aproximadamente 1000 linhas de código, e olha que eu simplifiquei o máximo possível.

O que eu realmente uso para as minhas aplicações tem no mínimo umas 4000, isso porque só tem suporte pra MySQL no momento, pretendo incluir suporte ao Postgre assim que tiver um tempinho.

 

Não tive tempo de testar tudo, pode haver algum erro ou outro aí. Se alguém encontrar, por favor, me avise. Eu desconfio que vai dar pau na se você inventar de alterar e atualizar o ID da linha.

 

Provavelmente você não vai entender de primeira, mas é assim mesmo, volte e leia quantas vezes for necessário, pergunte, só assim você vai conseguir entender.

 

[]s

Compartilhar este post


Link para o post
Compartilhar em outros sites
A ideia é criar Tables e Rows genéricos, que são definidos em tempo de execução. Não é difícil encontrar exemplos por aí que te dizem se tratar de um RDG, mas na verdade, é um DTO (VO) ou DataMapper, dos quais eu pessoalmente não sou muito fã pois eles amarram sua implementação. Para cada entidade na minha aplicação eu preciso criar uma classe Mapper diferente. Se somar isso ao fato de ao utilizar MVC eu já ter que criar no mínimo 1 Model e alguns Controllers, além de ter alguns arquivos Views, cada inserção de entidade no sistema é um pé no saco.

Penso o mesmo.

De forma resumida, o DataMapper viola o MVC, por isso prefiro não usá-lo.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Depois de ler este excelente post do Henrique, eu percebi que o meu Model está uma m*****!

Puuts, gostei bastante,meus parabéns. Só me tira uma dǘvida: Este é o uso do Row Data Gateway e Table Data Gateway?

 

Vou pegar este exemplo para estudar!

Compartilhar este post


Link para o post
Compartilhar em outros sites
Este é o uso do Row Data Gateway e Table Data Gateway?

Sim, ambos só existem em conjunto...

Na verdade, até pode haver o TDG sem o RDG, mas nunca vi fazerem isso...

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.