Ir para conteúdo

Arquivado

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

Bruno Augusto

RowDataGateway

Recommended Posts

Com base nessa resposta do Henrique Barcelos, acho que consegui compreender a motivação do RowDataGateway.

 

Mas eu não estou conseguindo implementá-lo.

 

Eu optei por centralizar as operações de CRUD num Gerenciador de Tabela. Com ele, consigo fazer um SELECT simples em três linhas:

 

$em = new DB\Table\Manager( new Models\SomeTable( $conn ) );

$em -> select() -> from( array( 'sometable' ) );

var_dump( $em -> fetchAll() );

Desconsiderando como $conn é criado, claro ;)

 

Mas não sei exatamente onde o RowDataGateway deveria entrar. Seria nos método que trazem os dados de volta, no meu caso, fetch() e fetchAll()?

 

Pelo sim e pelo não, meu Gerenciador é construído com uma instância de Table, que além de receber a conexão como mostrado acima, tem alguns accessors, dentre eles um que me retorna as propriedades dinamicamente criadas (com __set)

 

Supondo que o RowDataGateway entre no fetch() (um único registro) e eu queira fazer um UPDATE, como eu passaria para meu Gerenciador uma instância de Row se ele espera Table?

 

Será que eu deveria ter não ter uma classe Row e sim popular diretamente a própria Table?

 

Do jeito que eu fiz todas as operações básicas funcionam perfeitamente, mas esse comentário me deixou meio receoso de estar fazendo errado.

 

E pra variar, não entendo o que Martin Fowler escreve. <_<

Compartilhar este post


Link para o post
Compartilhar em outros sites

Você complicou as coisas aé, misturou TableDataGateway com RowDataGateway ahuahauau. =)

 

RowDataGateway significa que um objeto tem controle apenas sobre um registro no bando de dados.

Ex:

$personGateway = $personFinder->find( 1 );
$personGateway->name; //Bruno
$personGateway->surname; // NULL
$personGateway->surname = 'Augusto';
$personGateway->update();

 

Enquanto no TableDataGateway uma classe separada que mantém esse controle:

$person = $personGateway->find( 1 );
$person->name; //Bruno
$person->surname; // NULL
$person->surname = 'Augusto';
$personGateway->update( $person->id, $person->name, $person->surname );

 

Só que você misturou algumas outras patterns no teu código, não sei se de forma intencional, ou não.

Compartilhar este post


Link para o post
Compartilhar em outros sites

O uso do pattern TableDataGateway é excelente. Pelo que entendo deste padrão, deixa seu SQL "totalmente" encapsulado, permitindo que você só forneça os valores para os objetos, e chama o método, que irá pegar esses valores dos objetos, como no exemplo do amigo acima. Na verdade,este exemplo acho que é TableDataGateway e Ative Record (me corrijam se estiver errado ^^).

Compartilhar este post


Link para o post
Compartilhar em outros sites

RowDataGateway, então, deveria ser usado como retorno do método fetch(), certo?

 

Se sim, e quanto ao fetchAll(), em que há vários registros? Se não, clarifique por favor.

 

Não vejo sentido nesse método find(). Sei pra que ele serve mas é muito vago. Prefiro mil vezes, na classe da tabela, criar N accessors para cada find(0 diferente (findById, findByGroup, findByName...).

 

Mas todo mundo fala que isso é errado, que dessa forma o Controller deveria conhecer a Model.

 

Tem certos paradigmas da Orientação a Objetos que, ao meu ver, devem ser ignorados senão o programador acaba ficando doido.

 

E eu não misturei padrões. Até onde eu sei não existe um padrão Entity/Table Manager. Eu só criei essa classe para evitar um TableDataGateway gigantesco.

Compartilhar este post


Link para o post
Compartilhar em outros sites

RowDataGateway, então, deveria ser usado como retorno do método fetch(), certo?

Sim.

 

Se sim, e quanto ao fetchAll(), em que há vários registros? Se não, clarifique por favor.

Mesma coisa, seguindo o exemplo que passei, iria retornar uma coleção de objetos PersonGateway.

 

Não vejo sentido nesse método find(). Sei pra que ele serve mas é muito vago. Prefiro mil vezes, na classe da tabela, criar N accessors para cada find(0 diferente (findById, findByGroup, findByName...).

 

Mas todo mundo fala que isso é errado, que dessa forma o Controller deveria conhecer a Model.

Neste caso você está criando uma especie de Repositório, apesar de que não pode ser chamado Repositório.

Primeiramente porque o Repositório deve ser criado no topo de uma camada de mapeamento de dados para o Banco de Dados, no teu caso tudo está reunido em apenas um local. Você teria que separar essas duas camadas.

Se você implementar um Repositório, o ideal seria você implementar também uma camada de serviços na tua aplicação, para assim os Controllers não precisarem conhecer como o dado é persistido, e nem obtido.

 

E eu não misturei padrões. Até onde eu sei não existe um padrão Entity/Table Manager. Eu só criei essa classe para evitar um TableDataGateway gigantesco.

:seta: Data Mapper

 

 

Seria interessante a opinião de alguém mais informado, estou dando minha opinião aqui, mas posso está apenas te confundindo mais ainda. kk

Compartilhar este post


Link para o post
Compartilhar em outros sites

Putz, quase perdi o tópico :P

 

Certo, agora ao invés de meu fetch() retornar um array ou objeto (dependendo do fetchStyle), ele retorna um objeto Row que nada mais é do que um ArrayObject com construtor ligeiramente modificado para que as propriedades possam ser acessadas como índices de array também (coisa que o PHP não faz nativamente).

 

No caso de múltiplos registros a serem retornados (fetchAll) retorno um RowSet, este sim um pouco mais elaborado para que o próprio RowSet possa ser iterado ao invés de obter o iterador associado (não gosto disso).

 

Mas e daí? Como que eu vou executar ações de UPDATE e DELETE?

 

Vejo em muitos scripts, métodos save() e delete() dentro das respectivas classes correspondentes à essas minhas.

 

Mas não vejo isso como certo pois se Row representa um único registro (e um RowSet um conjunto de), adicionando tais métodos nelas eu estaria violando a SRP das classes.

 

Veja se acompanha meu raciocínio. Elas (classes) REPRESENTAM um registro, e por adicionar tais métodos seria o mesmo que dizer que os registros podem se auto-atualizar ou auto-deletar.

 

Ou não?

 

Minha idéia é fazer com que APENAS a classe Table Manager executasse as operações de CRUD com base nos dados do objeto Table passado para ele.

 

Ah! Antes que eu me esqueça. Durante essa implementação alterei a primeira linha do código mostrado no primeiro post para:

 

$em = new DB\Table\Manager( $conn, new Models\SomeTable );

Isso porque apenas o Gerenciado usa o objeto de conexão, não a tabela.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Opa, cheguei bem na hora...

 

Então, meu caro Bruno Augusto, ao que me parece, você está se esquecendo de algo muito importante:

Os objetos TableRowGateway utilizam os objetos TableDataGateway para realizar as operações de CRUD.

Tanto que no exemplo que postei no outro tópico, o construtor de TableRowGateway recebe um objeto TableDataGateway.

 

Veja se acompanha meu raciocínio. Elas (classes) REPRESENTAM um registro, e por adicionar tais métodos seria o mesmo que dizer que os registros podem se auto-atualizar ou auto-deletar.

Indiretamente, eles fazem isso mesmo, mas toda a lógica que atua sobre os dados está em TableDataGateway, não em TableRowGateway.

 

Aí você se pergunta: pra que um TRG então? Não é muito melhor utilizar somente arrays associativos?

Uma coisa muito bacana que eu não tenho vergonha nenhuma em dizer que copiei, kibei, tabufei, etc, etc, etc do ZF é o conceito de dirty/clean data.

 

Quando você traz os dados do banco, eles são 'limpos', ou seja, estão validados e sanitizados.

Quando você altera algum dos dados com entradas fornecidas pelo cliente, eles se tornam 'sujos' e precisam de validação/sanitização.

 

Além disso, com essa separação, numa operação de update, por exemplo, eu atualizo apenas os campos 'sujos'. Os campos 'limpos' que continuam 'limpos' não foram atualizados, dessa forma, eu faço uma atualização sob demanda.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Seria certo, então, afirmar que essa minha classe que nomeei como Table Manager (Table\Manager, na verdade) é um TableDataGateway?

 

E em adicionando os métodos update() e delete() nas classes Row e RowSet apenas como "ponte" para os respectivos métodos do Table Manager, previamente injetado no construtor de tais classes, estaria eu me equivocando em algo?

 

Mais ou menos assim, já que não implementei ainda:

 

class Manager {

   // ...

   public function fetch( $fetchStyle = NULL ) {

       /**
        * Ilustrativamente apenas, Table::execute() supostamente retornaria
        * os dados a partir do fetchAll() do Driver
        *
        * Não é de verdade, afinal tem o Statement no meio ^^
        */
       $data = $this -> execute();

       $this -> source =& $data;

       return new Row( $this, $data );
   }

   // ...
}

class Row implements Countable, Iterator {

   private $manager;

   private $source = array();

   public function __construct( Manager $manager, $source ) {

       $this -> manager =& $manager;

       if( ! is_null( $source ) ) {

           /**
            * Em um método separado a origem dos dados pode ser
            * array, Iterator ou IteratorAggregate.
            *
            * E opcional pois mesmo sem resultados um objeto Row deve
            * ser retornado
            */
           $this -> setSource( $source );
       }
   }

   public function setSource() {
       //Omitido
   }

   /**
    * Simplesmente duas pontes.
    *
    * Manager::update() e Manager::delete() detectam automaticamente os campos "limpos"
    * com base nas novas informações setadas à Manager::$source via Manager::fetch()
    */
   public function update() {
       return $this -> manager -> update();
   }

   public function delete() {
       return $this -> manager -> delete();
   }
}

O que acha(m)?

Compartilhar este post


Link para o post
Compartilhar em outros sites

É bem por aí mesmo...

$this -> manager =& $manager;

Está tentando manter retrocompatibilidade com o PHP4?

Se não, isso é totalmente desnecessário. Objetos em PHP são passados por valor-referência.

O que isso significa?

 

Bom, aqui vai um exemplo bem tosco, não estou inspirado hoje:

<?php
class Foo {
public $bar = 'Bazzinga!';
}

function alterFoo(Foo $foo){
$foo->bar = 'LOL!';
}

function anotherAlterFoo(Foo $foo) {
$foo = new Foo();
$foo->bar = 'Doooorgas, manolo!';
}

function onceMoreAlterFoo(Foo $foo) {
$foo2 = $foo;
$foo2->bar = 'IRIAIRAIRIAIRIA';
}

$foo = new Foo();
echo $foo->bar . PHP_EOL;

alterFoo($foo);
echo $foo->bar . PHP_EOL;

anotherAlterFoo($foo);
echo $foo->bar . PHP_EOL;

onceMoreAlterFoo($foo);
echo $foo->bar . PHP_EOL;

 

Saída:

Bazzinga!

LOL!

LOL!

IRIAIRAIRIAIRIA

Quando um objeto é passado como parâmetro no PHP, sua referência também é passada. Entretanto, não temos passagem por referência. Me explico.

Observe a chamada da primeira função:

alterFoo($foo);

Fazendo

$foo->bar = 'LOL!';

estou alterando o objeto Foo original, setando o valor da propriedade bar.

 

Entretanto, olha o que acontece na segunda função:

$foo = new Foo();
$foo->bar = 'Doooorgas, manolo!';

Observe a atribuição da primeira linha. Quando eu a fiz, quebrei o 'link' que havia com o objeto passado por parâmetro, não existe mais referência ao objeto original.

 

Note que o link não é quebrado quando uma nova variável faz referência ao objeto passado:

$foo2 = $foo;
$foo2->bar = 'IRIAIRAIRIAIRIA';

 

A esse comportamento, damos o nome de passagem de parâmetros por valor-referência.

O parâmetro será uma referência até que seja feita uma atribuição onde o parâmetro encontra-se no lado esquerdo, ou seja, recebendo um novo valor.

 

Importante: isso é válido somente para objetos, não para tipos primitivos, que são passados por cópia.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Eu sei Henrique, mas não sei se você lembra de um tópico em que eu comentei uma coisinha ou outra sobre a tabela interna de símbolos do PHP e tal.

 

Então, resumindo é basicamente para que num caso como esse:

 

class Foo() {}

class Bar {

   public function __construct( Foo $foo ) {}
}

$foo = new Foo;

$bar = new Bar( $foo );

Não sejam criadas, nessa tabela de símbolos, duas alocações para o objeto Foo e sim apenas uma, como se eu tivesse feito:

 

$bar = new Bar( new Foo );

Não sei até onde essa micro-otimização é verdade ou mito, mas se eu entendi isso certo, desde que li sempre passei a fazer assim.

 

Ninguém ainda me convenceu do contrário, então... :rolleyes:

 

Mas o que eu queria mesmo era uma resposta para a questão levantada no post #8.

 

Tenho medo quando soltam um "é bem por aí". Fica parecendo a nova propaganda da NET.

 

P: Você usa TableDataGateway?

R: Uso, mas não é bem TableDataGateway, mas é tipo TableDataGateway

 

Perfeccionismo pra quem não entende do assunto é soda.

Compartilhar este post


Link para o post
Compartilhar em outros sites
Então, resumindo é basicamente para que num caso como esse:

 

class Foo() {}

class Bar {

   public function __construct( Foo $foo ) {}
}

$foo = new Foo;

$bar = new Bar( $foo );

Não sejam criadas, nessa tabela de símbolos, duas alocações para o objeto Foo e sim apenas uma, como se eu tivesse feito:

 

$bar = new Bar( new Foo );

Não sei até onde essa micro-otimização é verdade ou mito, mas se eu entendi isso certo, desde que li sempre passei a fazer assim.

Não estou no meu PC, mas vou testar isso quando chegar em casa...

 

Cara, se você seguir o que te expliquei não tem erro... Padrões não são livros de regras, eles apenas descrevem uma POSSÍVEL solução com alguma POSSÍVEL implementação.

Se você faz do seu jeito, com a sua nomenclatura, mas segue a ideia, não tem nada de errado...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olhando por esse ângulo, é bem verdade. Tenho de perder esse "medo" de tentar usar um padrão e acabar implementando ele de forma errada por não entender os exemplos encontrados.

 

Quanto ao teste, agradeço pela disposição. Eu só imagino como testar.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Cara, fiz alguns testes aqui... Não existe duplicação do objeto, só da referência, que na verdade é um endereço de 4 bytes... o impacto disso é mínimo, pra não dizer inexistente...

Ou seja, você NÃO PRECISA dessa construção para objetos, só para tipos primitivos e arrays... Ainda assim, considero meio desnecessário...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Certo, o argumento é válido. Mas eu não chego a me distrair com micro-otimizações.

 

Distração seria batalhar em cima do quê, como e o porquê tal micro-otimização deve ser aplicada ou não.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Vamos retomar o curso do tópico...

 

Fiz da forma como havia levantado no post #8 e, para a classe Row, que só manipula um único registro, a questão de como fazer os condicionais foi relativamente fácil, bastou reproduzir as mesmas condicionais do SELECT que a originou.

 

E, também por ser referente à um único registro, setar as alterações a serem atualizadas é simples.

 

Mas e quanto ao RowSet, que conceitualmente se refere à vários registros?

 

A questão do condicional, tranquilo, o procedimento é o mesmo (ou quase). Mas como definir as alterações?

 

Tipo, um esquema resumido da Row:

 

$row = $em -> fetch();

$row -> campo = 'Novo Valor';

$row -> update();

Essa "mágica" é graças ao ArrayObject utilizado pela Row.

 

Mas para RowSet, tenho:

 

$rowset = $em -> fetchAll();

E daí pra frente... travou :P

 

Não posso setar da mesma forma que na Row, pois não existe a "mágica" da ArrayObject.

 

Eu até tenho um método que me retornar o RowSet como array, mas ainda assim é um array, de índices numéricos.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Ahn? Não entendi...

Rowset é basicamente um array de Rows.

Faça assim:

$rowset = $em -> fetchAll();foreach($rowset as $row) {
  $row->method(args);
}

 

Você pode fazer algo como

$rowset->get(0)->set('campo', 'valor');

Ou coisas assim...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Certo, uma pequenina coisa então:

 

Se uma query retornar um objeto RowSet, com múltiplos registros, ao fazer uma atualização não seria o mesmo que dizer que TODOS esses registros devem ser atualizados para com o novo valor?

 

Caso de Estudo: Num Bug Report, quero definir que todos os Bugs com mais de 3 meses sejam marcados como Arquivados:

 

$em = new DB\Table\Manager( $conn, new Models\Bugs );

$em -> select( 'status' ) -> from( array( 'bugs' ) ) -> where( 'created > ?', strtotime( '-3 months' ) );

$data = $em -> fetchAll();

$data -> status = 3; // 1 = Aberto / 2 = Fechado / 3 = Arquivado

$data -> update();

Nesse contexto, perfeito, afinal todos a query retornará exatamente aquilo que esperamos.

 

Mas deixa eu complicar as coisas mudando um pouquinho cenário.

 

Imagina um cadastro de clientes os quais podem adquirir um determinado serviço e eu quero atualizar o status de pagamento de uma determinada pessoa mas, pelo fato de o serviço ser apenas uma commodity, eu não exijo CPF das pessoas e, por coincidência, tenho dois clientes com mesmo nome (homônimos), sendo que um deles pagou e o outro não?

 

Nesse caso seria correto fazer o update assim, "às cegas" ou eu teria mesmo que delegar uma View intermediando a ação de UPDATE (talvez com alguns checkboxes)?

Compartilhar este post


Link para o post
Compartilhar em outros sites
Imagina um cadastro de clientes os quais podem adquirir um determinado serviço e eu quero atualizar o status de pagamento de uma determinada pessoa mas, pelo fato de o serviço ser apenas uma commodity, eu não exijo CPF das pessoas e, por coincidência, tenho dois clientes com mesmo nome (homônimos), sendo que um deles pagou e o outro não?

Então sua modelagem tá errada... Você precisa ter uma chave-primária...

 

Eu nunca vi nenhuma implementação de Rowset que possuísse métodos save ou delete, mas não é uma má ideia...

 

$data -> status = 3; // 1 = Aberto / 2 = Fechado / 3 = Arquivado

$data -> update();

Sua intenção aí é setar o valor 3 para o status de TODOS os elementos do Rowset???

 

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Não cara, você não entendeu direito.

 

Eu SEMPRE tenho uma chave-primária, acho até que sempre a crio meio que por inércia. :lol:

 

Mas esse exemplo fictício (sério) suponhamos que eu não saiba o ID do usuário, e sim apenas o nome.

 

Faço um SELECT tendo a cláusula WHERE como o nome dele e coincidentemente (essa palavra um dia mata o programador) ao invés de eu ter como retorno uma Row tenho um RowSet, com dois registros (homônimos).

 

Como ambas respeitam a mesma interface, a invocação dos métodos é uniforme.

 

Se eu buscasse por algum documento único, como o CPF, não haveria esse problema e eu poderia simplesmente fazer um UPDATE assim, às cegas.

 

Mas nesse exemplo específico eu deveria MESMO intermediar o SELECT e o UPDATE com uma View adicional, para que manualmente abrisse cada um dos dois perfis e comparasse, talvez pelo endereço, quem é fulano e quem é beltrano?

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.