Quero que meu código interfira em todas as operações com o banco, para que tenha controle total do fluxo de informações.
No DAO convencional, a partir do Value Object a classe faz intermédio da relação Usuário => Código => Banco, em outras palavras, a partir dos setters, o fluxo de informações que o usuário envia para o banco pode ser manipulado de forma centralizada antes de ser inserido.
O problema é que os getters do DAO convencional só retornam o que o usuário inseriu, ou seja, Código <= Usuário, sem nenhuma relação com os dados que de fato estão no banco de dados.
** Os exemplos serão em PHP 5.3.x **
Exemplo:
<?php
class UserVO {
private $usuario, $senha, $nome_completo;
public function setSenha( $senha )
{
$this->senha = $senha; //aqui há uma interferência entre o que o usuário coloca para $senha e o que é armazenado em $this->senha
}
//... Outros setters ...
public function getSenha( )
{
return $this->senha; // aqui retorna basicamente o que foi armazenado em $this->senha, e não o que foi de fato armazenado no BANCO DE DADOS
}
//... Outros getters ...
}
?>
Mas aí você me diz: Jota, tudo que eu já vi até hoje com o padrão DAO/VO acabava em "[query] WHERE senha = $instancia->getSenha()", que no fim das contas torna o que foi armazenado no banco exatamente o valor de $this->senha, logo uma outra camada para lidar com esses dados seria inútil, é só manipular o UserVO::getSenha(), não precisa de outra classe para isso.
Aí eu até concordo com você! Mas isso só ocorre enquanto a classe UserVO estiver instanciada e a senha setada. Se em algum outro lugar eu quiser resgatar os dados do banco para mostrar ao usuário, não terei controle para manipular a relação Banco de Dados => Usuário, e é agora que entra o diferencial do SDAO: Ele transforma o Banco de Dados => Usuário em Banco de Dados => Código => Usuário, dessa forma o programador tem total controle para manipular, se quiser, não só o fluxo de dados para setar valores e inserir no BD, mas também o fluxo de dados no resgate de valores de forma criteriosa ou não.
Mas você, insistente e ainda sem entender direito, faz uma réplica: Mas Jota! Manipular dados resgatados do BD pode ser feito na forma procedural quando estiver aplicando as classes que criou!
Então eu nem te respondo direito... No procedural, o mínimo possível. Centralize tudo. Se quiser mudar o critério no resgate de dados, com SDAO você muda apenas os getters de uma classe, enquanto se fizesse em procedural ia ter que alterar todos os lugares em que resgatou dados.
Enfim, o SDAO é para ter total controle do fluxo de dados entre o que é mostrado e o que é inserido, e de forma persistente. Pense numa situação em que o usuário setou para um campo o valor VAR1, foi armazenado VAR2 no BD e é necessário resgatar VAR1 de novo quando for acessado esse valor (ou até uma genérica VAR3). Para isso existe o SDAO.
Como implementar?
Para implementar o SDAO, são adicionadas duas finas camadas ao DAO do convencional DAO/VO. Uma para lidar com as alterações no BD e outra para lidar com o resgate de dados.
A convenção de nomes é DBG para a camada de resgate de informações (DataBase Getter) e DBS para a de alterações (DataBase Setter).
Mas elas são só subdivisões, unidas na classe SDAO (UserSDAO, por exemplo), que serve como Factory (ver Factory Pattern) para selecionar como a classe vai trabalhar.
Exemplo:
Vamos supor que queremos que o usuário entre com uma senha, ela seja mascarada pelo script, inserida ainda mascarada no Banco de Dados e, na hora de resgatar, a voltamos ao estágio natural para comparar com a senha inserida pelo usuário.
(Obs.: É somente um exemplo, pode-se atingir o mesmo objetivo mascarando o que o usuário inseriu e comparando com a do BD, mas não vem ao caso)
Cria-se, primeiro, uma classe Value Object (nesse caso não estou aproveitando o princípio central que ela tem da imutabilidade, mas ainda assim é muito importante):
<?php
class UserVO { // transforma $senha em $senha1 (algo diferente de $senha) nos setters e retorna, nos getters, a $senha1 (ainda mascarada)
private $id, $usuario, $senha, $nome_completo;
public function setSenha( $senha )
{
$this->senha = mascarar($senha); //aqui há uma interferência entre o que o usuário coloca para $senha e o que é armazenado em $this->senha
}
//... Outros setters ...
public function getSenha( )
{
return $this->senha; // aqui retorna basicamente o que foi armazenado em $this->senha, e não o que foi de fato armazenado no BANCO DE DADOS
}
//... Outros getters ...
}
?>
Agora criaremos a camada que intermediará a relação Usuário => Banco de Dados, ou seja, as inserções/alterações/deleções.
<?php
class UserDBS // usuario DataBaseSetter -> classe para lidar com inserções no BD || insere $senha1 definidas nos getters de UsuarioVO
{ // para sumarizar, colocaremos aqui só a parte de inserção
private $user, $dealer;
public function __construct(UserVO $user )
{
$this->dealer = new Connection();
$this->user = $user;
}
public function registrar()
{
$sql = "INSERT INTO usuarios(nome, senha, nome_completo) VALUES (:nome, :senha, :nome_completo)";
$sth = $this->dealer->prepare( $sql );
$sth->bindParam(":nome", $this->user->getNome(), PDO::PARAM_STR);
$sth->bindParam(":senha", $this->user->getSenha(), PDO::PARAM_STR);
$sth->bindParam(":nomefunc", $this->user->getNomeCompleto(), PDO::PARAM_STR);
if ( $sth->execute() )
{
return true;
} else
{
throw new Exception("Houve algum erro na hora de cadastrar o usuário.");
}
}
}
?>
Nesse momento, criaremos a camada que intermediará a relação Banco de Dados => Usuário, ou seja, o DBG:
<?php
class UserDBG extends UserDBS //Usuario DataBaseGetter -> pegar dados do BD || retorna $senha1 como $senha, através dos getters
{ // estende UserDBS somente para servir de 'ponte' para que UsuarioSDAO estenda UserDBG e UsuarioDBS, pelo fato do PHP nao permitir que uma classe estenda outras duas
// essa estensão só é necessária para que a Factory UserSDAO possa utilizar o método estatico UserDBG::listar()
private $id, $nome, $nome_completo, $senha, $dealer, $user;
public function __construct(UserVO $user)
{
$this->dealer = new Connection();
$this->user = $user;
}
public function resgatar()
{
$id = (int) $this->user->getId();
$sth = $this->dealer->prepare("SELECT * FROM `usuarios` WHERE id = :id");
$sth->bindParam(":id", $id, PDO::PARAM_INT);
if ( $sth->execute() )
{
$vals = $sth->fetchAll(PDO::FETCH_ASSOC);
$vals = $vals[0];
$this->id = $vals['id'];
$this->nome = $vals['nome'];
$this->nome_func = $vals['nome_completo'];
$this->senha = $vals['senha'];
return array("id" => $this->getId(), "nome" => $this->getNome(), "nome_func" => $this->getNomeFunc(), "senha" => $this->getSenha(), "nivel" => $this->getNivel());
} else
{
throw new Exception("Houve um erro durante a pesquisa de usuários.");
}
}
public static function listar() // estática e desvinculada da instância pelo fato de não utilizar nada do UserVO, pois não é criteriosa,
{ // retorna todos os usuários sem distinção.
$sql = "SELECT id FROM usuarios";
if ( !isset( self::$dealer ) )
{
$dealer = new Connection();
}
else
{
$dealer = &self::$dealer;
}
$sth = $dealer->prepare( $sql );
if( $sth->execute() )
{
$res = array();
$vals = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach( $vals as $key)
{
$vo = new UsuarioVO();
$vo->setId( $key['id'] );
$a = new UserDBG( $vo );
$res[] = $a->resgatar();
}
return $res;
}
else
{
throw new Exception( "Houve um erro durante a listagem de usuários." );
}
}
// ATENÇÃO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Os getters daqui são a grande diferença do SDAO, e são totalmente diferentes dos getters de UserVO. Mas por quê?
// Porque os getters de UserVO retornam a variável do fluxo Usuário => Banco de Dados, me dando liberdade para manipular,
// mas os getters de UserDBG permitem a manipulação do fluxo Banco de Dados => Usuário, modificando, se for da vontade do programador, o valor // resgatado do valor retornado.
// No exemplo, eu não estou modificando, mas não é sobre isso que se trata. Não se trata do fato de 'VOU FAZER' ou 'NÃO VOU FAZER', e sim da
// tranquilidade que 'POSSO FAZER SE EU QUISER' lhe proporciona.
public function getId( )
{
return $this->id;
}
public function getNome( )
{
return $this->nome;
}
public function getSenha( )
{
return desmascarar($this->senha); // Como dito.s
}
public function getNomeCompleto( )
{
return $this->nome_completo;
}
}
?>
Aqui, criamos a Factory UserSDAO:
<?php
class UserSDAO extends UserDBG // para UserSDAO poder utilizar os métodos desvinculados da instância (static) para si mesmo
{
public static function setModo( $mode, UserVO $vo )
{
$mode = strtolower( $mode ); // tornar case insensitive
switch ( $mode )
{
case "getter": // caso queira interferir na cama de resgate de dados
return new UserDBG($vo);
break;
case "setter": // caso queira interferir na cama de inserção/alteração/deleção
return new UserDBS($vo);
break;
default:
throw new Exception( "Houve um erro durante a definição do modo DAO." );
break;
}
}
}
// Exemplo de uso:
//Procurando algum usuário pelo ID:
///////////////////////////////////////////////////
$vo = new UserVO();
$vo->setId(7);
$a = UserSDAO::setModo('getter', $vo);
var_dump($a->resgatar());
///////////////////////////////////////////////////
// Listando todos os usuários (justificando os extends):
////////////////////////////////////////////////////////////
var_dump(UserSDAO::listar());
////////////////////////////////////////////////////////////
// Cadastrando usuários:
////////////////////////////////////////////////////////////
$vo = new UserVO();
$vo->setNome("Jota");
$vo->setSenha(123);
$vo->setNomeCompleto("Jota Vicente Romualdo Júnior");
$a = UserSDAO::setModo('setter', $vo);
$a->registrar();
////////////////////////////////////////////////////////////
?>
Então, aí está a pattern que eu idealizei. Espero que gostem e implementem! Qualquer dúvida ou dica, mande para jotavrj@gmail.com.
Obrigado!