PD
Pensando em objetos, não em SQL para construir uma aplicação
Fechado
Frequentemente pessoas me escrevem perguntando como realizar determinada consulta SQL usando o Adianti Framework, na maioria das vezes envolvendo várias tabelas. Imagino que essa dúvida ocorre por que muitas pessoas estão acostumadas a escrever aplicações de negócio fortemente baseadas em SQL e a grande maioria dos exemplos usando o framework são baseados em objetos, atributos, métodos e seus relacionamentos. Apenas um exemplo demonstra como executar uma query manual usando o framework (a seguir, apontarei qual é).
O primeiro software que desenvolvi em PHP, o SAGU (Sistema Aberto de Gestão Unificada), lá pelos idos de 2000 era fortemente baseado em instruções SQL. Escrevíamos SQL para inserir, alterar, excluir dados nas tabelas e também para buscar informações de tabelas diferentes para gerar relatórios. Mas nos dias atuais eu considero essa forma de trabalhar extremamente onerosa, visto que uma simples alteração em uma estrutura de uma tabela central implica em ajustes em dezenas de instruções SQL espalhadas pelo código-fonte, que usam aquela tabela. Além disso, o uso em demasia de SQL não privilegia os conceitos básicos da orientação a objetos como o reuso. Se uma regra de negócio pertence à um objeto (Ex: Cálculo do valor da multa de um título), deve pertencer à ele (na forma de um método), e não estar replicada em diversas instruções SQL ao longo do sistema.
O Adianti Framework permite realizar queries manuais facilmente, mas devemos evitar usá-las diretamente. Claro que em alguns casos é necessário, mas não para operações básicas (inserção, alteração, exclusão, listagem). O exemplo (www.adianti.com.br/doc-framework-Persistence-Setup-SetupTransaction) demonstra como executar uma query manual usando o framework. Vamos pensar e uma consulta básica para buscar dados de pessoas e de cidades. Ela ficaria mais ou menos como está listado a seguir:
O Adianti Framework trabalha com o conceito de um Active Record, que é um objeto que se comporta como um registro do banco de dados, oferecendo métodos básicos para persistência (store, load, delete). Além disso, o Adianti Studio Professional (IDE de desenvolvimento), oferece métodos para geração das classes Active Record já com métodos de relacionamento entre objetos (associação, agregação, composição). Assim, ao manipularmos um objeto, além de possuirmos acesso aos métodos básicos, temos acesso a objetos relacionados a este. É desafiador mudar a lógica de raciocínio para sair do SQL e pensar em termos de objetos, mas é possível.
Na figura a seguir temos a figura de um diagrama mostrando um modelo de objetos UML construído no Astah.
A partir do Adianti Studio Professional, podemos converter este modelo em PHP, onde o Studio irá gerar as classes de persistência (Active Record), bem como os métodos de relacionamento com demais objetos do sistema.
Como vimos, uma classe Active Record oferece métodos básicos de persistência (store, load, delete). Estes métodos são disponíveis por meio da superclasse TRecord, da qual todas Active Record do sistema são filhas. No exemplo a seguir, temos uma classe para manipular uma tabela de pessoas.
A partir desta classe, podemos construir métodos para acessar informações ou mesmo objetos relacionados. Tais métodos são construídos automaticamente pelo Adianti Studio Pro, por meio da importação do modelo UML, ou mesmo por meio de Wizards (Assistentes). No exemplo a seguir, estamos acessando informações da cidade relacionada. Ao acessarmos o atributo "->cidade", automaticamente o método get_cidade() é disparado, retornando a instância (objeto cidade) relacionado. Então, podemos acessar demais informações, como o nome da cidade, ou mesmo o nome do estado vinculado àquela cidade, desde que na classe Cidade, exista o método get_estado().
O Adianti Studio Pro também gera métodos reversos para obter objetos em relacionamentos de associação. No caso de um objeto Pessoa que possui uma associação com Cidade, além de gerar o método para obter a cidade a partir da pessoa, o Studio também gerará um método para obter todas as pessoas de uma cidade, da seguinte forma:
Assim, para obter todas pessoas de uma cidade, bastaria:
A partir do diagrama em anexo (que faz parte das vídeo-aulas: www.adianti.com.br/framework-videoaulas), podemos importar o modelo UML pelo Adianti Studio Pro (www.adianti.com.br/studio-pro) e passar a programar diretamente da maneira a seguir, sem a necessidade de implementar tais métodos, que serão gerados automaticamente.
Demonstração do uso de métodos gerados automaticamente pelo Studio Pro:
Além disso, construímos listagens (datagrids) para exibir informações de uma tabela, e frequentemente listamos informações vinculadas, como neste exemplo (www.adianti.com.br/framework_files/tutor/index.php?class=CustomerDat). Neste momento, seguidamente as pessoas me escrevem perguntando como fazer Joins entre as tabelas. E eu digo que na maioria das vezes isso não é necessário, basta acessar o objeto associado. Ao construirmos uma datagrid, indicamos os nomes das colunas a serem exibidas na datagrid. Para exibir uma coluna vinculada (pertencente à outra tabela), podemos simplesmente usar a sintaxe indicada no terceiro addQuickColumn() do exemplo a seguir:
Ao indicarmos "cidade->nome" automaticamente o framework busca o atributo nome da cidade vinculada àquela pessoa. Isso somente é possível se tivermos o método get_cidade(), que irá retornar a cidade vinculada.
A próxima dúvida de quem me escreve geralmente diz respeito ao desempenho. Certamente construirmos um SQL com Joins tem desempenho superior à buscar cada objeto relacionado sempre que ele for necessário. Mas para tal questionamento, costumo responder que as listagens no framework são todas paginadas, o que significa que em uma datagrid, estaremos buscando no máximo 10 objetos relacionados por vez, o que é muito pouco. Além disso, caso isto ainda seja um problema ao escalarmos a aplicação com um número de usuários muito grande, recomendo realizar cache de objetos usando o APC (assunto que será abordado em artigo posterior), que apresenta resultados incríveis.
Por fim, existe um caso em que nenhuma abordagem anteriormente citada funciona muito bem que é na criação de relatórios. Ao criarmos relatórios geralmente precisamos mesmo cruzar uma quantidade grande de tabelas, e utilizar muitas vezes recursos avançados do banco de dados (subconsultas, pl/sql, joins malucos, dentre outros). Nestes casos, o APC não tem efeito algum, pois ele trata de cache de objetos, não de consultas. Mas mesmo para esse caso, existe uma solução que iremos propor no próximo artigo.
O primeiro software que desenvolvi em PHP, o SAGU (Sistema Aberto de Gestão Unificada), lá pelos idos de 2000 era fortemente baseado em instruções SQL. Escrevíamos SQL para inserir, alterar, excluir dados nas tabelas e também para buscar informações de tabelas diferentes para gerar relatórios. Mas nos dias atuais eu considero essa forma de trabalhar extremamente onerosa, visto que uma simples alteração em uma estrutura de uma tabela central implica em ajustes em dezenas de instruções SQL espalhadas pelo código-fonte, que usam aquela tabela. Além disso, o uso em demasia de SQL não privilegia os conceitos básicos da orientação a objetos como o reuso. Se uma regra de negócio pertence à um objeto (Ex: Cálculo do valor da multa de um título), deve pertencer à ele (na forma de um método), e não estar replicada em diversas instruções SQL ao longo do sistema.
O Adianti Framework permite realizar queries manuais facilmente, mas devemos evitar usá-las diretamente. Claro que em alguns casos é necessário, mas não para operações básicas (inserção, alteração, exclusão, listagem). O exemplo (www.adianti.com.br/doc-framework-Persistence-Setup-SetupTransaction) demonstra como executar uma query manual usando o framework. Vamos pensar e uma consulta básica para buscar dados de pessoas e de cidades. Ela ficaria mais ou menos como está listado a seguir:
SELECT p.id, p.nome, p.endereco, c.id, c.nome
FROM pessoas p, cidades c
WHERE p.cidade_id = c.id
O Adianti Framework trabalha com o conceito de um Active Record, que é um objeto que se comporta como um registro do banco de dados, oferecendo métodos básicos para persistência (store, load, delete). Além disso, o Adianti Studio Professional (IDE de desenvolvimento), oferece métodos para geração das classes Active Record já com métodos de relacionamento entre objetos (associação, agregação, composição). Assim, ao manipularmos um objeto, além de possuirmos acesso aos métodos básicos, temos acesso a objetos relacionados a este. É desafiador mudar a lógica de raciocínio para sair do SQL e pensar em termos de objetos, mas é possível.
Na figura a seguir temos a figura de um diagrama mostrando um modelo de objetos UML construído no Astah.
A partir do Adianti Studio Professional, podemos converter este modelo em PHP, onde o Studio irá gerar as classes de persistência (Active Record), bem como os métodos de relacionamento com demais objetos do sistema.
Como vimos, uma classe Active Record oferece métodos básicos de persistência (store, load, delete). Estes métodos são disponíveis por meio da superclasse TRecord, da qual todas Active Record do sistema são filhas. No exemplo a seguir, temos uma classe para manipular uma tabela de pessoas.
- <?php
- class Pessoa extends TRecord
- {
- const ...
- const ...
- public function get_cidade() {... }
- public function get_categoria() {...}
- }
- ?>
A partir desta classe, podemos construir métodos para acessar informações ou mesmo objetos relacionados. Tais métodos são construídos automaticamente pelo Adianti Studio Pro, por meio da importação do modelo UML, ou mesmo por meio de Wizards (Assistentes). No exemplo a seguir, estamos acessando informações da cidade relacionada. Ao acessarmos o atributo "->cidade", automaticamente o método get_cidade() é disparado, retornando a instância (objeto cidade) relacionado. Então, podemos acessar demais informações, como o nome da cidade, ou mesmo o nome do estado vinculado àquela cidade, desde que na classe Cidade, exista o método get_estado().
- <?php
- $pessoa = new Pessoa(5);
- print $pessoa->cidade->nome;
- print $pessoa->cidade->estado->nome;
- ?>
O Adianti Studio Pro também gera métodos reversos para obter objetos em relacionamentos de associação. No caso de um objeto Pessoa que possui uma associação com Cidade, além de gerar o método para obter a cidade a partir da pessoa, o Studio também gerará um método para obter todas as pessoas de uma cidade, da seguinte forma:
- <?php
- class Cidade extends TRecord
- {
- // ...
- public function getPessoas()
- {
- $criteria = new TCriteria;
- $criteria->add(new TFilter('cidade_id', '=', $this->id));
- return Pessoa::getObjects( $criteria );
- }
- }
- ?>
Assim, para obter todas pessoas de uma cidade, bastaria:
- <?php
- $cidade = new Cidade(5);
- print_r ($cidade->getPessoas());
- ?>
A partir do diagrama em anexo (que faz parte das vídeo-aulas: www.adianti.com.br/framework-videoaulas), podemos importar o modelo UML pelo Adianti Studio Pro (www.adianti.com.br/studio-pro) e passar a programar diretamente da maneira a seguir, sem a necessidade de implementar tais métodos, que serão gerados automaticamente.
Demonstração do uso de métodos gerados automaticamente pelo Studio Pro:
- <?php
- $turma = new Turma(10);
- print $turma->disciplina->nome;
- print $turma->disciplina->curso->nome;
- foreach ($turma->getMatriculas() as $matricula)
- {
- print $matricula->aluno->nome;
- print $matricula->aluno->cidade->nome;
- print $matricula->aluno->cidade->estado->nome;
- }
- ?>
Além disso, construímos listagens (datagrids) para exibir informações de uma tabela, e frequentemente listamos informações vinculadas, como neste exemplo (www.adianti.com.br/framework_files/tutor/index.php?class=CustomerDat). Neste momento, seguidamente as pessoas me escrevem perguntando como fazer Joins entre as tabelas. E eu digo que na maioria das vezes isso não é necessário, basta acessar o objeto associado. Ao construirmos uma datagrid, indicamos os nomes das colunas a serem exibidas na datagrid. Para exibir uma coluna vinculada (pertencente à outra tabela), podemos simplesmente usar a sintaxe indicada no terceiro addQuickColumn() do exemplo a seguir:
- <?php
- $this->datagrid = new TQuickGrid;
- $this->datagrid->addQuickColumn('Código', 'id', 'left', 190);
- $this->datagrid->addQuickColumn('Nome', 'nome', 'left', 190);
- $this->datagrid->addQuickColumn('Cidade', 'cidade->nome', 'left', 190);
- ?>
Ao indicarmos "cidade->nome" automaticamente o framework busca o atributo nome da cidade vinculada àquela pessoa. Isso somente é possível se tivermos o método get_cidade(), que irá retornar a cidade vinculada.
A próxima dúvida de quem me escreve geralmente diz respeito ao desempenho. Certamente construirmos um SQL com Joins tem desempenho superior à buscar cada objeto relacionado sempre que ele for necessário. Mas para tal questionamento, costumo responder que as listagens no framework são todas paginadas, o que significa que em uma datagrid, estaremos buscando no máximo 10 objetos relacionados por vez, o que é muito pouco. Além disso, caso isto ainda seja um problema ao escalarmos a aplicação com um número de usuários muito grande, recomendo realizar cache de objetos usando o APC (assunto que será abordado em artigo posterior), que apresenta resultados incríveis.
Por fim, existe um caso em que nenhuma abordagem anteriormente citada funciona muito bem que é na criação de relatórios. Ao criarmos relatórios geralmente precisamos mesmo cruzar uma quantidade grande de tabelas, e utilizar muitas vezes recursos avançados do banco de dados (subconsultas, pl/sql, joins malucos, dentre outros). Nestes casos, o APC não tem efeito algum, pois ele trata de cache de objetos, não de consultas. Mas mesmo para esse caso, existe uma solução que iremos propor no próximo artigo.
muito shoe a materia pablo parabens novamente
E quanto a os relacionamentos de muitos para muitos em que se tem uma tabela intermediária? um exemplo conhecido seria do sistema template ERP em que um usuário pode pertencer a vários grupos e um grupo pode ter vários usuários, mas na listagem de usuário aparecem todos os usuários mas não aparece o grupo em que ele está vinculado. Tentei fazer uma listagem para exibir apenas usuários de um determinado grupo e não consegui, gostaria de saber qual seria a melhor maneira para fazer isso: Listar apenas usuário que estão no mesmo grupo que o usuário logado. Obrigado
Anderson,
Para trazer uma coluna relacionada, você pode implementar um método como get_group(), que retorna o objeto Grupo vinculado. E usar "group->name" na datagrid.
Já para fazer filtros sobre informações vinculadas, você terá de implementar usando subquery. Para ter uma ideia, dê uma olhada nesse exemplo, em que temos uma listagem de clientes, com um filtro por nome de cidade:
www.adianti.com.br/framework_files/tutor/index.php?class=CustomerDat
Claro que se o filtro fosse pelo código da cidade (combo, etc), era só fazer um igual, sem necessidade de subquery.
Para pegar o usuário logrado é fácil:
$user = SystemUser::newFrom( TSession::getValue('login') );
Com base nesse objeto, você tem então o usuário logado e todas suas propriedades.
Att,
Pablo
Entendi obrigado vou implementar no meu sistema.
No caso se eu precisar ordenar pelo campo do "join"?
No meu caso está assim:
Estou ordenando por MET_FM_CODIGO, mas na verdade gostaria de ordenar pela descrição (que está em outra tabela) não pelo código.
Grato,
Nilson,
Daí você pode usar view:
www.adianti.com.br/forum/pt/view_879?relatorios-com-queries-complexa
Att,
Boa tarde!
Parabéns Pablo pelo trabalho!
Gostaria de saber como eu seto uma category para o custumer?
Também estou enfrentando essa dificuldade de ordenar por um campo da associação,
tipo, quero selecionar as vendas ordenada pela empresa onde o cliente trabalha.
Imagine ter que criar view para cada necessidade de selecionar com ordenação por campo da tabela associada.
no symfony tem innerJoin e LefJoin, é bem util nesses casos de ordenar, ou mesmo filtrar pela associação..
Um dia será implementado ??
E como pode ser feito com o TDataGridColumn (ao invés do quick)?
Eu ordenaria pelo índice do campo ao invés do nome.
Ex: SELECT id, nome FROM cliente WHERE id = 10 ORDER BY 2
Muito boa essa matéria Pablo. Parabéns!!!
Ajudou-me a resolver um problema que tinha para concatenar campos no grid, porém, usando esse o mesmo exemplo do link www.adianti.com.br/framework_files/tutor/index.php?class=CustomerDat, como faria para filtrar pelo campo "{city->state->name}" que está na coluna "City"? Se digito "SP" (sem as aspas) não retorna nenhum registro, porém, existem vários no grid que aparecem com "(SP)".
Um grande abraço para a galera toda do Adianti.
Muito boa essa matéria Pablo. Parabéns!!!
Ajudou-me a resolver um problema que tinha para concatenar campos no grid, porém, usando esse o mesmo exemplo do link www.adianti.com.br/framework_files/tutor/index.php?class=CustomerDat, como faria para filtrar pelo campo "{city->state->name}" que está na coluna "City"? Se digito "SP" (sem as aspas) não retorna nenhum registro, porém, existem vários no grid que aparecem com "(SP)".
Um grande abraço para a galera toda do Adianti.
Prezados bom dia.
Gostaria de ordernar uma consulta usando critéria pelo nome de um objeto relationado, como no exemplo abaixo:
$param['order'] = 'cidade->estado->nome';
$param['direction'] = 'asc';
$criteria->setProperties($param);
Agradeço colaborações.
Salustiano Sampaio