+3

CRUD Genéricos em PHP. 3ª Parte: Retrieve (de vários objetos)

criado por Marcio Alexandre em 23/06/2010 10:51am
Mais uma vez vamos nós...

Utilizando o mesmo princípio de antes, sem foco no desempenho, e utilização de SQL dinâmicas para suportar o conceito genérico em OO PHP.

Observando os mesmo conceitos dos outros dois artigos [Create e Retrieve (de um objeto) ]. Continuemos agora com o Retrieve (ou as famosas ‘consultas’) com o retorno de vários objetos (utilizando vetores).

Acredito, que este será o artigo mais complexo, mas nada que não venhamos entender, tendo paciência e atenção.

Tornarei a repetir alguns conceitos teóricos, para deixar este artigo independente dos demais.

Como já sabemos, para cada objeto temos a estrutura que receberá cada dado referente à sua tabela no banco, ou seja, este objeto tem uma estrutura semelhando à tabela do banco. Por questão de organização e segurança, deixei as “classes CRUDs” dentro do objeto ‘banco’. E nos objetos (DAO) nós importaremos as classes e alimentaremos os parâmetros (aqui estará o segredo de manipulação das wheres).

Vamos lá, trazendo o código referente a um funcionário (funcionarioDAO.php), já no padrão de acesso às classes genéricas:

class funcionario{
private $nome_tabela    =       'tb_funcionario';
private $camposInsert   =       'fun_nome,fun_data';
var $id_funcionario;
var $fun_nome;          
var $fun_data;
public function setDados($id_funcionario,$fun_nome,$fun_data){
        if (isset($id_funcionario)){ $this->id_funcionario = $id_funcionario ; }else{$this->id_funcionario = null; }
        $this->fun_nome         =       $fun_nome       ;
        $this->fun_data         =       $fun_data       ;
}
public function getId(){ return $this->id_funcionario;}
public function getNome()       { return $this->fun_nome; }
public function getDataBr()     { /*modelo brasil (00/00/0000 00:00)*/ $temp = explode(' ',$this->fun_data); $temp_data = explode('-',$temp[0]); $data = $temp_data[2].'/'.$temp_data[1].'/'.$temp_data[0]; $hora = $temp[1];  return $data.' '.$hora ; }
public function getDataEn()     { return $this->fun_data; }

public function getNomeTabela() { return $this->nome_tabela;     }
public function getNomeCampos() { return $this->camposInsert; }
public function getValorCampos()        { return "'".$this->fun_nome."','".$this->fun_data."' "; }

public function Salvar($objeto,$db){ $db->insertObjectToDB($objeto); }
public function Atualizar($objeto,$db){ $db->updateObjectToDB($objeto); }
public function Pegar($fields,$values,$db){ return $db->selectDataDb($this->getNomeTabela(),$fields,$values);}
public function PegarTodos($fields,$values,$db){ return $db->selectDatasDb($this->getNomeTabela(),$fields,$values); }
public function Deletar($fields,$values,$db){ $db->DeleteDataDb($this->getNomeTabela(), $fields,$values); }
}

Observe que adicionamos mais duas variáveis privadas, no objeto, uma que traz o nome da tabela no banco, e a outra trazendo os fields (campos), em sequência como utilizamos nos inserts em sql. Também inserimos duas funções para trazer os valores dessas duas variáveis. Tudo deve ser analisado, inclusive segurança.

Utilizaremos, portanto, nas camadas superiores a função ‘PegarTodos’.
Explicando os parâmetos, ‘$fields’ e ‘$values’ são vetores que trazem os campos e seus valores, com os quais filtraremos nossos dados no banco (refiro-me à where). Aqui está um dos pontos principais do artigo, pois sendo assim, a where pode ser determinada pelo desenvolvedor sem problemas, e pode ser 01 ou mais filtros, basta informar campo e valor, respectivamente, nos vetores.

Após enviar os parâmetros para função PegarTodos, a função do banco selectDatasDb as recebe, adicionando a função da própria classe getNomeTabela(), e retorna um valor (que iremos abordar mais adiante).

Nos objetos, conforme vemos no exemplo acima, sempre teremos a função getNomeTabela() que retorna nossa variável privada $nome_tabela, previamente alimentada com o nome da tabela no banco a que se refere. Isso torna cada objeto responsável pela sua tabela no banco. No caso, o objeto funcionarioDAO.php consulta só, e somente só, a tabela ‘tb_funcionario‘, e assim sucessivamente, como por exemplo clienteDAO.php acessaria somente a tabela ‘tb_cliente’. Observe a importância de manter o nome da tabela nos demais arquivos.

Ok. Já explicamos a comunicação do objeto consigo mesmo, na chamada da função PegarTodos. E vimos que até agora não existe qualquer diferença com o artigo anterior de pegar apenas um objeto em específico.

Agora vamos ver a comunicação da aplicação com o objeto. Faremos aqui uma combo (HTML) básica, alimentaremos o objeto funcionárioDAO com todos os dados do banco, passando os filtros, e traremos todos eles para nossa aplicação:

<html>
<body>
<?php 
require('uses/banco.php');
$db = new Banco();
?>
<select name="id_funcionario">
<?php
require('uses/funcionario_.php');
$func = new funcionario();
	$objeto	= $func->PegarTodos($fields,$values,$db);
	foreach($objeto as $m => $obj){ ?>
      <option value="<?php echo $obj->getId(); ?>"><?php echo $obj->getNome(); ?></option>
        <?php } ?>
</select>
</body>
</html>

Até aqui nenhuma novidade. Importamos as classes banco e funcionario. Agora alimentaremos o vetor $fields e $value para filtrar os dados do select dentro do objeto. Neste exemplo, estamos querendo trazer todos os funcionários existente na base, por isso os fields e values estão vazios, porque não precisa de where específica. Lembro também que caberia aqui QUALQUER filtro, caso houvesse necessidade, ok?! Sem nenhum problema.

Agora vamos entrar na classe do banco e entender. É extremamente parecido com o artigo anterior, porém agora estamos trazendo um ‘list’ de objetos.

<?php
class Banco{
	private $local;
	private $user;
	private $senha;
	private $msg0;
	private $msg1;
	private $nome_db;
	private $db;
	public function __construct(){
		$this->local 	=	'localhost';
		$this->user  	=	'root';
		$this->senha 	=	'';
		$this->msg0  	=	'Conexão falou, erro: '.mysql_error();
		$this->msg1  	=	'Não foi possível selecionar o banco de dados!';
		$this->nome_db 	=	'db_exitosistemas';
	}
public function abrir(){
		$this->db = mysql_connect($this->local,$this->user,$this->senha) or die($this->msg0);
		mysql_select_db($this->nome_db,$this->db) or die($this->msg1);
	}

	public function fechar(){
		//analisar se o mysql_close precisa ser colocado numa variável
		$closed = mysql_close($this->db);
		$closed = NULL;
	}

	public function BuscaValor( $campo, $tb_name, $fields_where, $values_where ){ 
		$qtd = count($fields_where);
		if ($qtd != count($values_where)){ // verifica o numero de fields e values ?>
			<script language="javascript">
				alert('Função: selectDataDb. Erro: Quantidade de campos diferente da quantidade de valores');
			</script>
		<?php  }else{
		// Monta a string SQL=====================
		$sql = "select ".$campo." from ".$tb_name;
		if ($qtd != 0 ) {
			for ($j=1;$j<=$qtd;$j++){
				if ($j == 1) { $sql .= ' where '; }//garante que o where entre caso tenha algum parametro
				$sql .= $fields_where[$j].' = '.$values_where[$j];		
				if ($j<$qtd )  { $sql .= ' and '; }
			}
		} //=======================================
			$res = mysql_query($sql) or die ($sql .mysql_error());
			$linha = mysql_fetch_array($res);
			return $linha[$campo]; // retorna o valor do campo especifico, com os parametros enviados (podendo ser nenhum ou vários)
		}//else
	}//public function
	
	public function selectDatasDb( $tb_name, $fields_where, $values_where){  // nome da tabela, vetores: campos(fields), valores(values) da consulta
		$obj = explode('_',$tb_name);
		$qtd = count($fields_where);
		$db = new Banco();
		$db->abrir();

		// <Monta a string SQL> =====================
		$sql = "SELECT id_".$obj[1]." FROM ".$tb_name;
		if ($qtd >= 1 ) {
			for ($j=1;$j<=$qtd;$j++){
				if ($j == 1) { $sql .= ' WHERE '; }//garante que o where entre caso tenha algum parametro
				$sql .= $fields_where[$j].' = '.$values_where[$j];		
				if ($j<$qtd )  { $sql .= ' AND '; }
			}
		} //==================== </Monta a string SQL>

		$query = mysql_query($sql) or die ($sql .mysql_error());
		$h = 0; // inicializamos a variável
// Laço que trará TODOS os objetos já alimentados
	while ($linha = mysql_fetch_array($query)){
			$objDTO = new $obj[1];
$fields_where[$qtd+1] = 'id_'.$obj[1];
			$values_where[$qtd+1] = $linha[$fields_where[$qtd+1]];
			$fields = mysql_list_fields('db_exitosites',$tb_name); // informa os fields/campos da tabela do banco
			$columns = mysql_num_fields($fields); //conta o número de campos
			$new_objeto[$h] = clone $objDTO; //Aqui declaramos um novo objeto que é clone de nosso DAO, no momento sendo apenas um DTO.
			for ($i = 0; $i < $columns; $i++) {
				$fields_name 	= mysql_field_name($fields, $i);
				$fields_values = $this->BuscaValor(mysql_field_name($fields, $i),$tb_name,$fields_where,$values_where);
				$new_objeto[$h]->$fields_name = $fields_values;
			}
			$h += 1; //chamamos o próximo objeto
		} //fecha o while, terminando nosso vetor de objetos.
		$db->fechar();
		return $new_objeto; //retorna todos os objetos populados
	}
?>

Ok! Aqui está a tão temida classe. Não se assuste pelo tamanho e pela quantidade de laços de repetição (mais do que do artigo anterior). Vamos entender...(eu fiz alguns comentários no código, talvez ajude).

A função selectDatasDb() recebe os parâmetros enviados pela função PegarTodos em FuncionarioDAO. Simples assim! Nenhuma novidade...

Sabemos que ao acessar o banco, precisamos ‘criar’ um objeto de retorno de dados (obedecendo ao Data Access Object). Sendo assim, minha classe tem que entender qual objeto será carregado após o select no banco. Como fazer minha função entender qual objeto o banco irá popular? Agora vai a importância de manter a nomenclatura da tabela do nosso banco, nas demais partes do sistema (nome do objeto, da classe, e por aí vai...).

Ao acessar a função selectDatasDb(), o nome da tabela é ‘quebrada’ (ultizando o ‘explode’ do php), para só então, o sistema entender qual objeto será carregado.

  $obj = explode('_',$tb_name);
  $new_objeto = new $obj[1];

Nisso tornamos genérica nossa classe. Qualquer objeto pode ser carregado. E sabendo que o objeto carrega em si o nome da própria tabela a que se refere, é automático.

Aqui montaremos nosso select para saber os registros (linhas) que existem no banco, e colhemos o id de cada um, para só então colhermos os fields e seus valores, e posteriormente popular o objeto:

		// <Monta a string SQL> =====================
		$sql = "SELECT id_".$obj[1]." FROM ".$tb_name;
		if ($qtd >= 1 ) {
			for ($j=1;$j<=$qtd;$j++){
				if ($j == 1) { $sql .= ' WHERE '; }//garante que o where entre caso tenha algum parametro
				$sql .= $fields_where[$j].' = '.$values_where[$j];		
				if ($j<$qtd )  { $sql .= ' AND '; }
			}
		} //==================== </Monta a string SQL>

Após isso, enquanto houver registro no banco (linhas ou tuplas), que nós determinamos no while:

while ($linha = mysql_fetch_array($query)){

então ‘varremos’ os dados de cada linha, lembrando que alimentamos mais uma vez os fields e os values dinamicamente. Afinal precisamos dizer qual registro precisamos obter, e passamos por parâmetro o id colhido no select acima:

$fields_where[$qtd+1] = 'id_'.$obj[1];
$values_where[$qtd+1] = $linha[$fields_where[$qtd+1]];

Buscar o nome é simples, utilizaremos a função do mysql: mysql_field_name. Onde informamos a posição da Field. Para entender melhor: se numa tabela eu tenho ‘nome,endereco,contato,sexo’, nome seria a posição: 1, endereco: 2, contato: 3, e assim sucessivamente.

		$fields = mysql_list_fields('db_exitosites',$tb_name); // informa os fields/campos da tabela do banco
			$columns = mysql_num_fields($fields); //conta o número de campos
			$new_objeto[$h] = clone $objDTO; //Aqui declaramos um novo objeto que é clone de nosso DAO, no momento sendo apenas um DTO.
			for ($i = 0; $i < $columns; $i++) {
				$fields_name 	= mysql_field_name($fields, $i);
				$fields_values = $this->BuscaValor( mysql_field_name( $fields, $i),$tb_name,$fields_where,$values_where);
				$new_objeto[$h]->$fields_name = $fields_values; //aqui populamos com todos os campos e valores o objeto.
			}

Para buscar o valor, precisaremos de uma função de auxílio. Senão o código fica muito complicado. E é aqui que fica a construção de nossa SQL Dinâmica.

Desta vez o nosso ‘for’ ficará dentro de outro laço de repetição ‘while’. Que populará a quantidade de objetos referente às tublas da tabela no banco. Conforme já explicado !

Envia-se o nome do campo desejado, o nome da tabela, e os vetores que construirão nossas wheres.

$this->BuscaValor(mysql_field_name($fields, $i),$tb_name,$fields_where,$values_where);

Entraremos então na função BuscaValor.
Logo na entrada cuidaremos de nosso filtro do select. Quantas cláusulas formarão a where? É necessário saber, e já tratamos isso logo no início.

		$qtd = count($fields_where);

Validamos se a quantidade de fields e seus valores correspondem no IF abaixo.

		if ($qtd != count($values_where)){ // verifica o numero de fields e values ?>
			<script language="javascript">
				alert('Função: selectDataDb. Erro: Quantidade de campos diferente da quantidade de valores');
		</script>
	<?php  }else{

Caso esteja tudo ok. Prosseguimos para a construção real do select.

		// Monta a string SQL=====================
		$sql = "select ".$campo." from ".$tb_name;
		if ($qtd != 0 ) {
			for ($j=1;$j<=$qtd;$j++){
				if ($j == 1) { $sql .= ' where '; } //garante que o where entre caso tenha algum parâmetro
				$sql .= $fields_where[$j].' = '.$values_where[$j];		
				if ($j<$qtd )  { $sql .= ' and '; }
		}
		} //=======================================

A variável $sql recebe o texto concatenado com os novos valores das variáveis. O IF garante que nossa where receberá algo para ser trabalhado. O laço de repetição varre o vetor, até a quantidade de campos e valores existe no vetor, trazendo TODOS os que fora passados. O primeiro IF depois do For, garante que o a palavra ‘where’ seja concatenada na string $sql, e logo após é concatenado os campos e valores respectivamente. O próximo IF é somente para concatenar a palavra ‘and’ à string citada, caso haja mais campos e valores a serem concatenados.
Nenhum segredo até então. É só substituir o nomes que costumamente é usando nos SQL de consulta, por variáveis que receberão os valores em tempo de execução. E nisso consiste a idéia principal do artigo.

A função 'BuscaValor' é idêntica a utilizada na função do artigo anterior, que traz apenas um objeto populado. A modificação (entre trazer um objeto ou um vetor com todos os objetos) aqui é apenas dentro do ‘SelectDatasDb’, que varre todos os registros existentes no banco.
Ainda dentro do ‘SelectDatasDb’, para concluir os processos, fechamos o banco e retornamos o vetor com todos os objetos populados:

		$db->fechar();
		return $new_objeto; //retorna todos os objetos populados

Pronto! Um pouco complexo, mas factível. =)

O Desempenho aqui deve cair, mas temos uma grande vantagem, na hora da manutenção não precisamos cair nas funções dento do objeto do banco. Basta nos preocupar com o carregamento do objeto desejado e à alimentação dos vetores que farão as 'wheres'. Percebam que a ‘regra de negócio’ fica numa camada muito acima, e numa forma de comunicação nada trivial.

Bom pessoal, sou Marcio Alexandre. Encerro aqui mais este artigo. Talvez tenha ficado algo ainda mais confuso, devido ao tamanho. Qualquer dúvida, é só entrar em contato.

E mais uma vez, agradeço a atenção, e espero ter contribuído com mais este conhecimento à comunidade OO PHP.

Próximo artigo: Update. =)

Comentários:

Mostrando 1 - 10 de 11 comentários
Michael Colla disse:
Muito interessante os 3 artigos, acompanhando pra aprender um pouco mais. No aguardo dos próximos! Valeu!
05/06/2012 4:57pm (~4 anos atrás)

Marcio parabéns pelos artigos. Procurei em vários lugares e nenhum foi tão bom como os teus. Aguardamos ansiosos a continuação.

Muito obrigado.
23/02/2012 4:57pm (~4 anos atrás)

Leticiah, o 4 tah na metade aqui... mas confesso que a preguiça tah maior, mas em breve retomo e publico. Obrigado pelo incentivo.
25/01/2012 12:54pm (~4 anos atrás)

Mazza, a mesma pergunta em todos os artigos... aheuahaeuaehuaeu Tah de brincadeira, né?! Já lhe respondi no primeiro artigo.
25/01/2012 12:54pm (~4 anos atrás)

Leticiah disse:
Olá Marcio Alexandre,

Gostaria de saber se os artigos CRUD Genéricos em PHP - parte 4 e 5 já foram publicados. Achei muito interessante este artigo. Parabéns pela iniciativa e continue repassando seus skills de php pra nós iniciantes na linguagem, está ajudando muito!!

Abraços!

Leticiah
14/10/2011 9:47am (~5 anos atrás)

Bom dia!

Temos em algum local estes exemplo funcionando ?
27/09/2011 8:39am (~5 anos atrás)

Link para o artigo posterior-> Retrieve (trazendo uma coleção de objetos): http://www.phpbrasil.com/artigo/ajN7IQp9AcZd/crud-genericos-em-php-3a-parte-retrieve-de-varios-objetos
28/03/2011 2:57pm (~5 anos atrás)

Bruno Moraes, não diria "complicated", diria "painful" hehehehe. O que expus aqui são estruturas mais complexas, pouco utilizadas pela maioria que desenvolve em PHP, que por sua vez se limita a scripts básicos. Aqui estamos em nível mais avançados, por assim dizer...
28/03/2011 2:56pm (~5 anos atrás)

Joaquim, isso é uma boa prática, mas não muito performático, ok?! Mas sem dúvida, a camada de negócio se restringe à alimentação de vetores, não nos preocupando com 'n' comandos sql. Facilita bastante. Contudo, muitas empresas não são adeptos de SQLs dinâmicos. Aí fica a critério de cada um. Té mais!
16/07/2010 9:26am (~6 anos atrás)

Ainda não estudei profundamente estes tres artigos, mas na leitura dá para saber o precioso trabalho que foi postado para a comunidade, era exatamente o que eu precisava neste momento. Sou profundamente agradecido. Parabéns.
15/07/2010 1:33pm (~6 anos atrás)

Novo Comentário:

(Você pode usar tags como <b>, <i> ou <code>. URLs serão convertidas para links automaticamente.)