Ir para conteúdo

Arquivado

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

Evandro Oliveira

avaliação de classe

Recommended Posts

Pessoal, fiz uma classe que extende a PDO com um CRUD básico e configurações via .CFG ini_like externo.

Os focos principais dela são a interação entre SGDB's de mídias diferentes - para conversão, exportação e backup, por exemplo - e orientação a objetos.

 

O que eu preciso:

- Que quem puder (tiver tempo) pelo menos avalie (encontre erros) e, se possível, estudem o código para sugerir implementações de performance e escrita/codificação.

 

- Dêem sugestões de métodos e comportamentos para poder modificar a query em tempo de execução. Ex: Falta a opção de escolher como os erros são manipulados, falta exteriorizar as msgs de erro para personalizá-las, falta um query linha-a-linha. Sugiram conforme a vossa necessidade

 

a classe

DBManager.php

 

<?php

/**
 * 
 * @author Evandro Oliveira <evandrofranco@gmail.com>
 * @version 1.0 03232010
 * @copyright 2010
 * 
 * DBManager Classe para PHP
 * 
 * DBManager é uma classe desenvolvida a partir da classe nativa PDO.
 * Foi criada para tornar fácil operações CRUD entre bancos de diferentes
 * servidores. Também é possível, através de prévias manipulações, criar
 * webservices ou interagir com outros objetos de maneira simples. Apenas o
 * método PDO::__construct foi sobrescrito. Todos os outros métodos foram 
 * mantidos para garantir a compatibilidade com scripts prontos e evitar
 * confusão entre programadores que já possuem prática com PDO.
 * 
 * Propriedades e métodos
 * 		string $dbtype
 * 			Armazena o tipo de servidor BD que será utilizado. Pode conter
 * 		um dos seguintes valores:
 * 		- sybase
 * 		- mssql
 * 		- dblib
 * 		- firebird
 * 		- ibm
 * 		- informix
 * 		- mysql
 * 		- pgsql
 * 		- oci
 * 		- odbc
 * 		- sqlite
 * 			Se o valor passado para $dbtype não pertencer a esta lista, ou o
 * 		driver correspondente não for encontrado, uma exceção de classe Exception
 * 		será disparada.
 * 
 * 		string $dsn
 * 			Armazena a linha de conexão que será passada ao construtor ancestral.
 * 		Não há tratamento de erros para esta propriedade, uma vez que a objeto pai
 * 		tentará efetuar a conexão e se encarregará de tratar os erros, caso existam.
 * 
 * 		string $password
 * 			Armazena a senha de conexão ao banco de dados. É enviada como terceiro
 * 		parâmetro do método pdo::__construct()
 * 
 * 		string $username
 * 			Armazena o nome de usuário que se conectará ao banco. É o segundo
 * 		parâmetro enviado ao método pdo::__construct()
 * 
 * 		void insert(string $table, mixed $data)
 * 			Método de inserção de dados em série.
 * 			string $table
 * 				Tabela onde o método efetuará a inserção. Se não for passado um valor
 * 		de tipo string, uma exceção é disparada. Caso o método deva trabalhar em
 * 		outro banco, $table também recebe o valor do banco, no formato:
 * 		$table = "`banco`.`tabela`";
 * 			mixed $data
 * 				Recipiente de dados percorríveis no formato "campo"=>"valor".
 * 		$data pode receber uma matriz bidimensional - array(array(),array()),
 * 		como também pode receber objetos percorríveis como SimpleXMLElement's. Em
 * 		resumo, qualquer tipo de dados que possa sofrer iteração por foreach() pode
 * 		ser passado através de $data. Cuidado para o detalhe que o DBManager
 * 		nterpreta o nome da chave como o nome do campo no banco de dados, se forem
 * 		enviados valores sem chaves - array(array('a','b','c','d')) - os nomes dos
 * 		campos serão tratados como 0,1,2,3 e 4.
 * 			Se qualquer um dos dois parâmetros for de tipo incompatível, uma Exception
 * 		é disparada.
 * 			Não há retorno para este método. Utilize propriedades como PDO::$lastInsertId
 * 		para verificar o número de linhas modificadas.
 * 
 * 		mixed read(string $query [, string pdo::fetch_param [, string pdo::fetch_param [,...]]])
 * 			Método de consulta e tratamento de dados.
 * 			string $query
 * 				A consulta sql. Caso não seja uma string, uma Exception é disparada. Caso
 * 		não seja uma consulta válida, um erro PDO é criado conforme o attributo
 * 		PDO::ATTR_ERRMODE.
 * 			string pdo::fetch_param
 * 				As variáveis subsequentes correspondem ao funcionamento do método PDO::fetchAll().
 * 		Todos os valores que seriam passados ao método fetch, deverão ser enviados na mesma ordem
 * 		para DBManager::read(). Qualquer anormalidade no envio dos valores gera um erro do tipo
 * 		PDO::ATTR_ERRMODE.
 * 			O retorno deste método é exatamente igual ao retorno enviado pelo método
 * 		PDO::fetchAll().
 * 
 * 		void update(string $table, mixed $data [, string $condition])
 * 			Método de atualização de dados.
 * 			string $table
 * 				A tabela a ser modificada. Seu comportamento é idêntico ao de DBManager::insert().
 * 		Caso não receba uma string, uma Exception é disparada.
 * 			mixed $data
 * 				Os dados a serem atualizados. Trabalha de maneira semelhante à DBManager::insert()
 * 		com a diferença que não existe o nível de recipiente. Os dados a serem atualizados devem
 * 		ser passados diretamente no formato "campo"=>"valor" para a variável $data.
 * 			string $condition
 * 				A condição para atualização dos dados. Deve receber uma string de comandos SQL a
 * 		partir da sintaxe 'WHERE'(não incluso). Caso não seja uma string, uma Exception é disparada.
 * 			O método não retorna valores.
 * 
 * 		void delete(string $table [, string $condition])
 * 			Método de exclusão de dados.
 * 			string $table
 * 				A tabela a ser modificada. Tem comportamento idêntico a DBManager::insert() e
 * 		DBManager::update(). Caso não receba uma string, dispara uma Exception.
 * 			string $condition
 * 				A condição para remover os dados. Seu comportamento é idêntico à $condition presente
 * 		em DBManager::update().
 * 			O método não retorna valores.
 * 
 * 		DBManager __construct([mixed $config])
 * 			Método construtor padrão da classe.
 * 			mixed $config
 * 				Recebe as configurações do objeto. Pode ser uma string contendo o caminho do arquivo
 * 		de configurações ou uma matriz/objeto no formato "variável"=>"valor" para a conexão do banco.
 * 			O método retorna o próprio objeto.
 * 		
 */

class DBManager extends PDO {
	private
		$dbtype,
		$dsn,
		$password,
		$username;
		
	public function __construct($config=NULL){
		if(empty($config)) $config = __DIR__.'/DBManager.cfg';
		if(is_string($config)){
			if(file_exists($config) && is_readable($config)) $config = parse_ini_file($config,true);
			else throw new Exception('DBManager[01]: Impossível ler arquivo de configurações.');
		}
		if($this->is_iterateable($config)){
			$config = $this->turn_in_array($config);
			if(!isset($config['dbtype'])) throw new exception('dbMananger[02]: Tipo de banco de dados não foi informado.');
			if(!in_array($config['dbtype'],parent::getAvailableDrivers()))
				throw new Exception('DBManager[03]: Tipo de banco de dados indisponível.');
			$this->dbtype = $config['dbtype'];
			if(isset($config['username'])) $this->username = $config['username'];
			if(isset($config['password'])) $this->password = $config['password'];
			if(isset($config[$this->dbtype])) $config = $this->turn_in_array($config[$this->dbtype]);
			else {
				unset($config['dbtype']);
				unset($config['username']);
				unset($config['password']);
			}
			$this->dsn = $this->dbtype.':';
			if($this->dbtype == 'sqlite') $this->dsn .= $config['dbfile'];
			elseif(isset($config['dsn'])) $this->dsn .= $config['dsn'];
			else {
				$this->dsn = $this->dbtype.':';
				foreach($config as $dsnKey=>$dsnValue) $this->dsn .= "{$dsnKey}={$dsnValue};";
			}
		}
		else throw new Exception('DBManager[04]: Configurações recebidas são incompatíveis com a classe.');
		parent::__construct($this->dsn,$this->username,$this->password);
		parent::setAttribute(parent::ATTR_ERRMODE,parent::ERRMODE_EXCEPTION);
	}
	
	private function is_iterateable($var){
		if(is_array($var)) return true;
		if(is_object($var)) {
			$test = new ReflectionClass(get_class($var));
			return $test->isIterateable();
		}
		return false;
	}
	
	private function turn_in_array($var){
		if(is_array($var)) return($var);
		foreach($var as $key=>$value) $tmp[$key] = $value;
		return $tmp;
	}
	
	public function insert($table,$data){
		if(!is_string($table) || !$this->is_iterateable($data)) throw new Exception('DBManager[05]: Parâmetros inválidos. [insert]');
		$data = $this->turn_in_array($data);
		foreach($data as $line2ins){
			$line2ins = $this->turn_in_array($line2ins);
			$fields = array_keys($line2ins);
			$stmt = $this->prepare("INSERT INTO `{$table}`(`".implode('`,`',$fields)."`) VALUES(:".implode(',:',$fields).")");
			foreach($line2ins as $field=>$value) $sql[':'.$field] = $value;
			$stmt->execute($sql);
		}
	}
	
	public function read(){
		if(sizeof(func_num_args()) == 0) throw new Exception('DBManager[05]: Parâmetros inválidos. [query]');
		$sql = func_get_arg(0);
		if(!preg_match('/^\s*((select)|(describe)|(explain)|(show))/i',$sql))
			throw new Exception('DBManager[06]: A consulta deve receber um comando tabular. [query]');
		$stmt = parent::query($sql);
		$fetch = func_get_args();
		unset($fetch[0]);
		return call_user_func_array(array($stmt,'fetchAll'),$fetch);
	}
	
	public function update($table,$data,$condition=null){
		if(!is_string($table) || !is_string($condition) || !$this->is_iterateable($data))
			throw new Exception('DBManager[05]: Parâmetros inválidos. [update]');
		foreach($this->turn_in_array($data) as $field=>$value) {
			$sqlUpd[] = "`{$field}`=:{$field}";
			$sql[':'.$field] = $value;
		}
		$stmt = $this->prepare("UPDATE `{$table}` SET ".implode(', ',$sqlUpd).(!empty($condition)? " WHERE {$condition}":''));
		return $stmt->execute($sql);
	}
	
	public function delete($table,$condition=''){
		if(!is_string($table) || !is_string($condition)) throw new Exception('DBManager[05]: Parâmetros inválidos. [delete]');
		return $this->exec("DELETE FROM `{$table}`".(!empty($condition)?" WHERE {$condition}":''));
	}
}

?>

 

 

DBManager.cfg (deve ser colocado no mesmo diretório da classe, ou ser definido no momento da instância de objeto(__construct)

 

dbtype	 = ''
username = ''
password = ''

[sybase]
host	 = 
dbname	 =
charset	 =

[mssql]
host	 = 
dbname	 =
charset	 =

[dblib]
host	 = 
dbname	 =
charset	 =

[firebird]
dbname	 =
charset	 =
role	 =

[ibm]
driver	 = {IBM DB2 ODBC DRIVER}
database =
hostname =
port	 =
protocol = 'tcpip'

[informix]
dsn	 =

[mysql]
host	 = 
port	 =
dbname	 =

[pgsql]
host	 =
port	 =
dbname	 =

[oci]
dbname	 =
charset	 =

[odbc]
dsn	 =

[sqlite]
dbfile	 =

 

 

alguns exemplos pra facilitar a compreensão (todos foram testados)

1. Conectando

 

1.1 Arquivo DBManager.cfg no mesmo diretório (Padrão)

$exemplo11 = new DBManager();

1.2 Arquivo de configurações em um outro diretório

$exemplo12 = new DBManager('config/DBManager.cfg');

1.3 Array de configurações

$conf = array(
		'dbtype'=>'sqlite',
		'dbfile'=>'database.s3db'
	);
	$exemplo13 = new DBManager($conf);

1.4 Objeto de configurações

class conf implements IteratorAggregate {
		public $dbtype, $host, $username, $password, $dbname, $dbfile;
			
		public function getIterator () {
			return new ArrayIterator($this);
		}
	}
	$cfg = new conf();
	$cfg->dbtype = 'sqlite';
	$cfg->dbfile = 'database.s3db';
	$exemplo14 = new DBManager($cfg);

2. Definindo um banco de dados

 

2.1 Através da conexão (Formato recomendado)

$conf = array(
		'dbtype'=>"mysql",
		'host'=>'localhost',
		'username'=>'root',
		'password'=>'',
		'dbname'=>'database'
	);
	$exemplo21 = new DBManager($conf);

2.2 Através da consulta USE

ATENÇÃO: NEM TODOS OS SGDB FORNECEM SUPORTE A ESTE FORMATO!

$conf = array(
		'dbtype'=>"mysql",
		'host'=>'localhost',
		'username'=>'root',
		'password'=>'',
	);
	$exemplo21 = new DBManager($conf);
	$exemplo21->exec('USE `database`');

3. Inserindo e alterando dados

 

3.1 Através de arrays

$data = array(
		array('name'=>'John','surname'=>'Lennon','instrument'=>'Bass'),
		array('name'=>'George','surname'=>'Lennon','instrument'=>'Guitar'),
		array('name'=>'Paul','surname'=>'Lennon','instrument'=>'Bass'),
		array('name'=>'Ringo','surname'=>'Lennon','instrument'=>'Drums')
	);
	$exemplo31 = new DBManager();
	$exemplo31->insert('beatles',$data);

3.2 Através de objeto

class beatle implements IteratorAggregate {
		public $name, $surname, $instrument;
			
		public function getIterator () {
			return new ArrayIterator($this);
		}
		
		final public function __construct($name,$surname,$instrument){
			$this->name = $name;
			$this->surname = $surname;
			$this->instrument = $instrument;
		}
	}
	$john = new beatle('John','Lennon','Guitar');
	$exemplo32 = new DBManager();
	$exemplo32->update('beatles',$john,'`name` = "John"');

4. Apagando

$exemplo4 = new DBManager();
	$exemplo4->delete('beatles','`name` NOT NULL');

5. Consultando

	$exemplo5 = new DBManager();
	print_r($exemplo5->read('SELECT * FROM `beatles`',PDO::FETCH_ASSOC));

/* Saída
	Array
(
    [0] => Array
        (
            [name] => John
            [surname] => Lennon
            [instrument] => Guitar
        )

    [1] => Array
        (
            [name] => George
            [surname] => Lennon
            [instrument] => Guitar
        )

    [2] => Array
        (
            [name] => Paul
            [surname] => Lennon
            [instrument] => Bass
        )

    [3] => Array
        (
            [name] => Ringo
            [surname] => Lennon
            [instrument] => Drums
        )

)*/

Notas

* No Tópico 3, foi utilizado um insert(array()) e um update(object) apenas para evitar redundância. Ambos os tipos de dados funcionam para ambos os métodos

* DBManager::read() irá retornar um array de **elementos** definidos no segundo parâmetro do método. Se nenhum valor for passado, o valor padrão pdo::FETCH_BOTH (matriz numérica e associativa) é utilizado

* Definir o Default_Fetch com PDOStatement->setFetchMode() NÃO SOBRESCREVE o fetch padrão de DBManager::read()

 

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

 

Em breve, exemplos mais elaborados para provar o poder da classe.

Espero que gostem e, principalmente, USEM!

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bom Evandro, movi o tópico para laboratório de scripts para que ela não se perca entre os vários tópicos existentes no fórum principal.

 

Primeiro, parabéns, tenho acompanhado seus posts no fórum principal e sua evolução é notória, esse seu código não me deixa mentir.

 

Tenho algumas consideração em relação ao seu código, se não se importar, seguem abaixo:

 

1. Comentários:

Os comentários do seu código podem ser descritos como muito bom e ao mesmo tempo, muito ruim.

 

O comentário do cabeçalho da classe é excelente, descreve todo o funcionamento da classe de uma forma direta, simples e limpa, contudo, você pecou na ausência de comentários para os métodos; Editores de alto nível como Eclipse, que oferecem dica de código baseado no comentário de um método, não conseguem saber o tipo de cada parâmetro nem o que o método retorna, sendo assim, um objeto retornado por um método não terá nenhuma dica, nem com a análise semântica do Zend Studio foi possível saber o que um determinado método irá retornar.

 

Na documentação oficial do phpDocumentor você irá encontrar como documentar adequadamente uma classe, método e propriedade, assim como um arquivo e suas dependências: phpDocumentor Manual

 

 

2. Declaração de Propriedades:

A forma que você declarou suas propriedades é funcional, contudo, não é possível comentá-las, assim, dentro de uma classe complexa fica difícil saber o que uma determinada propriedade faz e o tipo dela.

 

É claro que a declaração das propriedades é uma questão pessoal e a forma que você as declarou irá funcionar, mas, se o objetivo é fazer com que seu código seja utilizado por vários desenvolvedores, a documentação das propriedades é algo que precisa ser levado em consideração.

 

 

3. Visibilidade:

Esse é um ponto que confunde a maioria dos desenvolvedores que estão começando com orientação a objetos, contudo, nota-se que você conseguiu compreender o conceito e usou adequadamente, tanto para as propriedades quanto para os métodos.

 

 

4. Padrões de Projeto:

Nesse ponto seu código precisa melhorar muito, a iniciar pelo próprio nome da classe.

 

4.1 DBManager:

Manager refere-se a um "gerente" sendo assim, DBManager deveria ser um objeto que gerencia as conexões e não o próprio objeto de conexão.

 

4.2 Configurações:

A ideia de se utilizar um arquivo de configurações é ótima, contudo, você obrigou seu objeto a ter que descobrir como interpretá-la.

 

"Program to an 'interface', not an 'implementation'." (Gang of Four 1995:18)

 

Quando se inicia em programação orientada a objetos, é comum querer derivar tudo escrevendo versões "bombadas" de qualquer coisa, contudo, conforme se vai estudando você vai começar a perceber que esse tipo de implementação tende a não ser reutilizável e você se verá editando o código original para adaptá-lo a uma situação ou outra.

 

Quando se programa para interfaces, artifícios como Ctrl + C, Ctrl + V deixarão de ser úteis, quando você começar a ter "birra" desse artifício devido às consequencias negativas do uso, você se verá em um outro nível de programação.

 

Os comentários que expus nesse tópico só o foram feitos porque acredito que você tem potencial, eu poderia simplesmente ter dito: "Olha Evandro, ficou muito bom", mas se eu tivesse alguém que não apenas elogiasse e sim criticasse meus códigos de um ano atrás eu com certeza estaria muito mais longe hoje e, exatamente por esse motivo, acredito que, apesar de ter sim ficado bom, seu código ainda pode melhorar mais.

 

Abraços e Sucesso,

João Batista Neto

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bom, hora da réplica.

 

Primeiro que não tinha postado no laboratório porque, apesar de utilizável, ainda não julgo a classe pronta. Se já está a nível do laboratório, me sinto honrado.

 

1. Tenho uma VAGA noção de PHPDocumentor, já cheguei a dar uma olhadinha mas ainda não com a devida atenção.

 

2. Está dizendo para declará-las linha a linha documentadas?

class ... {
/** 
 * @var string $config
 * configurações da classe
*/
private $config;
/**
...

3. http://forum.imasters.com.br/public/style_emoticons/default/joia.gif

 

4. Patterns e dinamização de XML são mesmo meus pontos fracos

4.1 Sei que a classe se trata de um DAO, mas não consegui pensar num nome intuitivo para tal :\

4.2 O objetivo era torná-la flexível de modo que, quem programa apenas para manipulação simples

pudesse fazer uso do arquivo .cfg, enquando quem trabalha com multi SGBD's pudesse interar entre eles

sem maiores dificuldades. Aceito sugestões.

 

No mais, agradeço em muito a atenção dedicada à minha humilde criação ;)

Compartilhar este post


Link para o post
Compartilhar em outros sites

1. Tenho uma VAGA noção de PHPDocumentor, já cheguei a dar uma olhadinha mas ainda não com a devida atenção.

 

Além de facilitar a leitura do código por outros desenvolvedores e permitir que editores como Eclipse possam entender o que cada método de um objeto faz e o que ele retorna, você pode gerar documentações de uma aplicação completa. Foi baseado no JavaDoc, então é um padrão consolidado, vale muito a pena estudá-lo.

 

2. Está dizendo para declará-las linha a linha documentadas?

 

Exatamente.

 

4. Patterns e dinamização de XML são mesmo meus pontos fracos

 

Visite o recentemente criado fórum Modelagem e Design Patterns, inicialmente estou postando os padrões GoF, mas logo estarei postando os padrões de domínio específico e estou preparando uma série de aplicações de exemplo completas (da modelagem até a execução) explicando cada padrão, quando usar, porque usar, seus participantes e as consequencias do uso de cada um.

 

4.1 Sei que a classe se trata de um DAO, mas não consegui pensar num nome intuitivo para tal :\

 

hehehe, muitas vezes, é mais fácil escrever o código mais complexo do mundo do que escolher um simples nome.

 

4.2 O objetivo era torná-la flexível de modo que, quem programa apenas para manipulação simples

pudesse fazer uso do arquivo .cfg, enquando quem trabalha com multi SGBD's pudesse interar entre eles

sem maiores dificuldades. Aceito sugestões.

 

Sim, e você conseguiu fazê-lo simples, contudo, você escreveu o método integralmente focado na implementação, se você estiver diante de uma situação adversa você provavelmente terá que ter outra implementação. A falta de padrão poderá obrigá-lo a ter que reescrever o código ou parte dele, ou ainda pior, ter que cloná-lo (Ctrl + C, Ctrl + V) para uma situação específica.

 

Perceba também que, quando você disse: "pudesse interar", você se referiu a um padrão de projeto chamado Iterator, se além de iterar você tivesse utilizado um outro padrão comportamental chamado Strategy você focaria na interface do objeto e não teria que implementar todo tipo de situação dentro do seu método, assim como não teria que ter que adivinhar o que o parâmetro passado significa. Quando você deixa a responsabilidade da configuração à um objeto específico, você pode ter algorítimos diferentes para situações diferentes como ler um .ini, recuperar as informações de um banco de dados SQLite, ou até conectar a um servidor remoto via qualquer protocolo e recuperar essas informações.

 

No mais, agradeço em muito a atenção dedicada à minha humilde criação

 

;)

Compartilhar este post


Link para o post
Compartilhar em outros sites

OK a 1 e 2 eu consigo resolver sem maiores complicações

 

a 4.1 é uma questão de raciocinar um nome aceitável

 

a 4.2 que eu creio onde seja o assunto mais delicado. Estudando os exemplos de MVC no fórum, cheguei a conclusão que meus métodos privados is_iterateable e turn_in_array devem ser trocados por uma restrição no formato da variável de entrada.

 

mixed $config e mixed $data passam a ser substituídas por Config $config e DbData $data, por exemplo.

Nessas classes, eu defino abstrações de propriedades e métodos que DEVEM estar presentes no valor passado, para que a classe possa trabalhá-lo independentemente do formato de dados/configuração utilizado (arquivo ini, array, objeto, xml, json, cvs etc)

 

Como o objeto(Config e DbData) vai trabalhar os dados presentes nele fica a cargo do utilizador da classe. Estou correto? É esse o raciocínio, sim?

 

Caso a resposta seja afirmativa, surge uma dúvida quanto a padrões de desenvolvimento.

Por 'convenção' espera-se que eu crie uma classe/interface em cada arquivo, separadamente. Mas eu corro o risco do programador separar a Controller (DBManager.php) das Models(Config.php e DbData.php) em diretórios separados e estragar todo o meu include.

 

Devo declarar tudo o que for necessário para o Controller funcionar no mesmo arquivo, ou faço a extração em cada arquivo e deixo a cargo do utilizador da classe setar os require/include de forma correta conforme a árvore dele?

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.