2008-01-09 11:15:42
tags: 

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 22)

                

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 22)

Para ver os artigos anteriores desta série, clique aqui.

Olá amigos. Estamos de volta depois de muita farofa, bacalhau, doce de leite, maionese, pamonha, queijo cavalinha, uma nova geladeira que não chega, livros e muita alegria. Espero que todos tenham passado dias maravilhosos nesse fim-de-ano e que possam ter aproveitado esses dias pra curtir a família e os amigos!

Mas chegou a hora de voltar para o batente. Antes que vocês comecem a mandar os já tradicionais emails "Cadê o 22! Queremos o 22!" eu já estou escrevendo mais um capítulo desta série singela, que procura explicar os conceitos mais enrolados de orientação a objetos em uma linguagem que todos aqui possam facilmente entender. E, pra ser bem honesto, os emails cobrando o 22 na verdade já começaram a chegar, então a hora é essa. Não estou reclamando de vocês de forma alguma, caros leitores! A pressão ajuda! Obrigado a todos que cobram, que agradecem, que mandam correções e puxões de orelha, sua contribuição faz disso aqui um lugar melhor.

Ah, o Natal. Que loucura que é o Natal. Falar sobre oop aqui no Digitalminds sempre é divertido porque eu tenho que bolar metáforas interessantes pra que o assunto não fique chato no primeiro parágrafo.  Então, como não poderia deixar de ser, o artigo de hoje está em clima de Natal. Não, eu não estou maluco, eu sei que o Natal já passou. Mas a segunda semana de janeiro não tem muita coisa interessante pra nos oferecer, a não ser que você conte o Dia de Reis.

O DIA DE REIS! É isso! Meu Deus como pude esquecer! Muito bem, senhores, esqueçam o natal. É hora de falar de Belchior (ou Melchior), Gaspar e Baltazar.
Não, senhores, não estamos falando de Belchior, nem de Gaspar, ou mesmo de Baltazar. Estamos falando de três magos persas, sacerdotes de Zaratrustra, homens sábios que conheciam profundamente a astrologia e que vislumbraram um sinal - na noite do dia 25 de dezembro do ano 1, souberam pelo brilho da estrela de Belém que um Rei nascera. E para comprovar que a wikipedia é sempre uma diversão, veja este parágrafo:

A melhor descrição dos reis magos foi feita por São Beda, o Venerável (673-735), que no seu tratado “Excerpta et Colletanea” assim relata: “Melchior era velho de setenta anos, de cabelos e barbas brancas, tendo partido de Ur, terra dos Caldeus. Gaspar era moço, de vinte anos, robusto e partira de uma distante região montanhosa, perto do Mar Cáspio. E Baltazar era mouro, de barba cerrada e com quarenta anos, partira do Golfo Pérsico, na Arábia Feliz”.

Uau. Sábios, caldeus, Ur, Pérsia. Agora que consegui a atenção total e irrestrita de vocês, futuros reis magos da OOP, e cobri todos de Incenso e Mirra, é hora de falar de uma classe importantíssima que ficou faltando no nosso grande esquema das coisas: a venerável classe ModelCollection.

Lembram que optamos por simplificar as funcionalidades da classe Model, fazendo com que ela realizasse operações somente em registros únicos no banco de dados? Muito bem, senhores, a classe ModelCollection aparece para nos salvar nos casos em que precisamos trabalhar com conjuntos de registros. Aqui é bom repetir o aviso: esta implementação é diferente da utilizada nos frameworks que utilizam o pattern ActiveRecord, como o Cake. Nestes frameworks todas as operações com registros e conjuntos de registros são feitas pela classe Model e suas subclasses. Assim, um objeto da classe model típico destes frameworks tem funções tanto para buscar, editar, salvar e deletar um registro quanto para buscar conjuntos de registros. Isso faz com que o objeto da classe que estamos usando não tenha um mapeamento direto com os dados que estão no banco. Isso me incomoda muito, pois eu gosto de pensar no objeto como uma coisa, uma representação virtual dos dados que estão no banco. Pra mim é estranho usar o modelo para retornar uma lista de registros. A nossa implementação vai claramente mapear um registro no banco a um objeto da classe correspondente, e um conjunto de registros a uma coleção de objetos da classe correspondente. Exemplo: nossa classe Post. Teremos no banco a tabela Posts, com os campos title e text, por exemplo. Para acessar e alterar  o campo de título de um post em nosso framework vamos seguir os seguintes passos:

  1. Criar um novo objeto da classe Post fazendo $post = new Post();
  2. Carregar os dados do registro que queremos alterar com id número $id  usando $post->loadById($id);
  3. Mudar o título usando $post->setTitle("Novo Título!");
  4. Salvar o post com $post->save();
Para criar um novo post, é simples:
  1. Criar um novo objeto da classe Post fazendo $post = new Post();
  2. Mudar o título usando $post->setTitle("Título do Novo Post!");
  3. Mudar o texto usando $post->setText("Texto do Post!");
  4. Salvar o post com $post->save();
Nessa implementação vamos poder acessar as variáveis do objeto usando getters normalmente, por exemplo, $post->getTitle(); Podemos também criar vários atalhos: o construtor do Post pode receber  o Id diretamente e já carregar os dados automaticamente. Podemos implementar a interface ArrayAccess para permitr coisas como print $post['title'] sem nenhuma dificuldade. Enfim. A coisa é boa. Aguardem que vem coisa ótima por aí.
Mas até aí, nenhuma novidade. O que muda é a forma de trabalhar com conjuntos de dados. No Cake, esse trabalho seria feito por funções na classe Post que retornariam Arrays contendo vários ítens. E isso deixa tudo confuso, no final das contas. O que proponho é o seguinte:
  1. Criar um novo objeto da classe PostCollection fazendo $collection = new PostCollection();
  2. Carregar o conjunto de registros usando uma das várias funções disponíveis, por exemplo $collection->loadAll();
Muito simples. E agora temos um objeto da classe PostCollection carregado de registros, com métodos super úteis para manipular os dados, paginar, enfim, uma belezinha. E vamos implementar uma coisa sensacional: a interface iterator, disponível na fantástica biblioteca SPL, proveniente de UR, na Pérsia! Mas isso já é assunto para o próximo episódio. Até lá!


2007-12-04 02:20:34
tags: 

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 21)

Para ver os artigos anteriores desta série, clique aqui.

Olá amigos. Em nosso último encontro finalizamos nossa primeira análise das classes principais do MVC: Model, View e Controller. Sei que muitos de vocês estão ansiosos para começar a codificar mas ainda temos bastante mufa pra queimar antes de começar a digitar código php!

Bom, pra começar, um pouco da história do cinema: quem não se lembra de Sean Connery como o policial irlandês em Os Intocáveis ? E de Kevin Spacey, como o bobalhão "Verbal" em Os Suspeitos ? E de Gene Hackman como o xerife mau, muito mal de Os Imperdoáveis ? Muito bem, o que esses três papéis interpretados por estes grandes atores têm em comum? As três interpretações foram premiadas com Oscars de melhor ator coadjuvante. E por quê todo este papo cinematográfico? É que hoje vamos falar de uma classe coadjuvante que é fundamental em nosso quase-framework: nosso amigo Dispatcher.

Muito bem, como já falamos algumas vezes, nosso framework é bastante esperto: ele vai permitir que acessemos métodos de nossos controllers simplesmente interpretando uma URL. Por exemplo, para acessar o método view() do controller BlogPost para ver o post com o id 19 nossa url seria

http://nossoblog.com.br/blogpost/view/19

Ok, mas quem transforma uma coisa na outra? Quem chama o que? Como isso funciona na prática?

Senhores, eu vos apresento a classe Dispatcher, encarregada de interpretar as urls de nossa aplicação e executar os métodos apropriados nos controllers corretos. Ela também vai checar se o controller e o método requisitados realmente existem, e vai apresentar mensagens de erro em caso negativo. Por fim, a classe vai separar corretamente os parametros do request, enviando um array já certinho para o método que foi chamado.


Uma outra função muito interessante de nossa classe Dispatcher vai ser interpretar extensões; sim, amigos, vamos fazer com que a extensão definida na url defina o layout e o template a ser utilizado, seguindo uma estrutura de arquivos pré-determinada. Exemplo: se ao invés da url acima tivessemos alguma coisa como

http://nossoblog.com.br/blogpost/view/19.xml

Nosso framework, ao invés de utilizar o layout em /layouts/default/blogpost/view.php e o template /templates/default/blogpost/view.php utilizaria o layout /layouts/xml/blogpost/view.php e o template /templates/xml/blogpost/view.php. Deu pra sacar o poder? Assim vc pode criar outputs diferentes pra toda a sua aplicação sem nenhum estresse. Bonito demais. Pra colocar o sistema disponível em wap, bastaria fazer um layout e um template, no caso /templates/wml/view.php e /layouts/wml/view.php e utilizar os códigos wml ao invés de html dentro deles. Eu adoro isso.

Você acaba de perceber a beleza de uma solução MVC. A lógica fica totalmente separada da apresentação, o que faz com que seja fácil fazer apresentações totalmente diferentes com o mesmo conteúdo. Sweet!

Vamos então à descrição da classe?

Class Dispatcher


Propriedades

$url

String. Propriedade que irá conter a url requisitada.


$controller

Objeto do tipo Controller. Esta propriedade irá conter uma nova instância do controller requisitado. No nosso exemplo, BlogPost.

$method

String. Método a ser executado no controller requisitado. O método será view(), no nosso exemplo.


$parameters

Array com os parâmetros enviados na requisição via get ou post. No exemplo acima, ele teria uma entrada $parameters["default"]=>"19" já que não nomeamos este parâmetro no request. Poderíamos, se nós fossemos chatos, ter montado a url assim:

http://nossoblog.com.br/blogpost/view/id/19


e aí nosso array seria montado na forma $parameters["id"]=>"19"

O framework vai ser esperto o suficiente pra perceber quando temos só o valor ou o par nome/valor e vai resolver tudo isso pra nós.

Métodos


parseURL()

O método parseURL() interpreta uma URL segundo os padrões combinados e popula as propriedades do nosso Dispatcher. Na prática, ele vai separar os pedacinhos da url, dividindo a string em partes. Em primeiro lugar, ele remove a string relativa ao protocolo (http:// ou https://). Depois, o domínio. Ficamos então com a estrutura

controller/método/parametros.extensao

Aí fica facil separar tudo, não?


parseParams()
O método parseParams() faz o mesmo que a função acima, só que para os parâmetros enviados por get e post. Tudo o que estiver entre um ? e o ponto da extensão será considerado parâmetro get. Também será considerado parâmetro get tudo o que vier depois da action requisitada, no caso de nossos exemplos acima "19" e "id"=>"19". Tudo o que vier no $_POST vai ser incluído também em nosso array $parameters.

Repare que pra simplificar as coisas não vamos considerar a diferença entre get e post em nosso array $parameters; será igual fazer um request por get e post. Juntaremos tudo no array, sem distinção.

2007-11-13 02:37:51
tags: 

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 19)

Para ver os artigos anteriores desta série, clique aqui.

Muito bem, caros amigos, voltamos à série que explica os conceitos transcendentais de programação orientada a objetos numa linguagem que todos podem facilmente entender. Este seu autor agradece novamente a todos pelos e-mails e comentários, são eles que nos incentivam a continuar sempre.

No último capítulo começamos a descrever nossas classes, notadamente a classe base Model, de fundamental importância em nosso quase-framework MVC. A fim de continuar nossa tarefa, proponho uma pequena reorganização de nosso estimado acrônimo: gostaria de falar neste artigo da classe base Controller, antes de falar da classe View. O motivo é simples: acredito que vai facilitar o entendimento se deixarmos para falar da classe View depois. Como o PHP é praticamente uma linguagem de construção de templates, posso afirmar de forma geral que todos que já trabalharam com PHP vão entender mais facilmente o conceito de Views.

Vamos lá, então? Vocês ainda se lembram do artigo no qual falamos que a url requisitada para o browser iria definir os modelos e ações a serem realizadas? Muito bem, só pra lembrar, nossas urls terão o seguinte formato:

http://www.meusite.com.br/modelo/acao/parametros

Nosso controller, então, vai saber direitinho em qual modelo aplicar uma determinada ação com certos parâmetros. Um exemplo seria:

http://www.meublog.com.br/blogposts/add/

Nesse caso, primeiramente nosso controller iria instanciar um objeto da classe BlogPost, que é filha da classe Model e já herdou tudo o que precisa para fazer as operações no banco de dados.

Repare que para facilitar vamos utilizar sempre o nome dos controllers no plural - alguns de vocês devem ter estranhado o fato de um controller BlogPostS instanciar na verdade um objeto da classe BlogPost (sem o S). O motivo é bem simples: o controller BlogPosts irá cuidar tanto da criação/edição/visualização/exclusão de um único BlogPost quanto da visualização de coleções de BlogPosts. Quando trabalhamos com um único registro, um único artigo de nosso blog, ele irá utilizar a classe BlogPost. Quando precisarmos trabalhar com coleções ou listas de artigos, como por exemplo uma listagem paginada, ele irá utilizar a classe BlogPostCollection.

Bom, vamos continuar: em seguida, nosso controller de BlogPosts iria executar seu método add. Este método iria checar se existe algum post http de formulário de dados do usuário.

Em caso negativo, ele instancia um objeto da classe View e imprime o template adequado, que claramente será um formulário que submete seus dados para a mesma URL,

http://www.meublog.com.br/blogposts/add

O template adequado, nesse caso, é o arquivo add.tpl, que fica dentro de uma pasta BlogPosts, que por sua vez vai ficar dentro de uma outra pasta Views, em um lugar seguro de nosso servidor que não precisa nem estar disponível publicamente. Repararam como até mesmo a organização dos arquivos é bem resolvida dentro de um framework? Só precisamos criar templates com os mesmos nomes que nossas ações e colocá-los nos folders com os mesmos nomes de nossos controllers para que a mágica aconteça.

Bom, imaginando que o usuário tenha preenchido o formulário acima e o tenha submetido, nosso controller recebe os dados, popula o objeto instanciado da classe BlogPost que está na propriedade $model com os dados que vieram do formulário, faz a validação executando o método $model->validate() e se estiver tudo bem, salva os dados executando $model->save() e mostra uma mensagem de sucesso usando o método flash(). Esse método imprime uma mensagem na tela durante alguns segundos e em seguida redireciona o usuário para outra página qualquer, no nosso caso provavelmente a lista de posts.

Deu pra entender?

Class Controller

Propriedades

$model

A propriedade $model vai armazenar o modelo no qual nosso controller irá operar as ações desejadas. Em nosso exemplo, ele armazenaria uma instância da classe BlogPost.

$modelcollection

A propriedade $modelcollection vai armazenar uma instancia da coleção relacionada ao modelo requisitado a fim de fazer as operações com coleções ou listas. No nosso caso, uma instância da classe BlogPostCollection.

$action

$action vai guardar a ação solicitada. No nosso exemplo, add. Para que a mágica funcione, nosso controller deverá ter um método add(). Para cada ação deverá existir um método correspondente em nosso controller; ações que não existem serão tratadas como erro.

$parameters

A propriedade $parameters guarda todos os parâmetros extras do request. Por exemplo, se estivéssemos vendo uma lista de Posts, poderíamos usar paginação, e o número da página viria como um parâmetro:

http://www.meublog.com.br/posts/list/page=3

$othermodels

$othermodels é uma propriedade que vai nos permitir instanciar objetos de outros modelos que não somente os da classe requisitada no request. Por exemplo, poderíamos querer utilizar as informações do usuário que está adicionando o post a fim de gravar no banco quem fez a última alteração. Assim, precisaríamos também de um objeto da classe User.

$view

Instancia da classe View que irá imprimir na tela os templates correspondentes.

 

Métodos

flash($message,$url,$seconds)

O método flash() mostra o texto de $message durante alguns segundos ($seconds) e em seguida redireciona para a url especificada. Muito útil para mostrar uma mensagem de sucesso ou fracasso de alguma ação.

redirect($url)

O método redirect() é muito simples: ele simplesmente redireciona o processamento para a url desejada.

Por hoje é só, pessoal. Voltamos a qualquer momento com mais orientação a objetos para vocês!

 

 

2007-10-09 04:59:38
tags: 

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 18)

Para ver os artigos anteriores desta série, clique aqui.

Olá Pessoal, estamos de volta com a série que mostra alguns princípios de programação orientada a objetos precisamente a cada rand (1,32768) dias!

Muito bem, senhoras e senhores. Acho que chegou a hora da gente "objetivar" um pouco e listar aqui nossas classes e seus métodos: até eu, depois de algum tempo sem escrever, me perdi! Já falamos livremente sobre os modelos, controllers e views de nosso quase-framework; agora é importante colocar no papel. Digo, no banco de dados. Enfim, vocês entenderam.

Então vamos começar com a classe Model, base para todos os nossos modelos:

Classe Model

Atributos 

$primarykey

String. Nome da chave primária do banco de dados. O default é id, mas pode ser qualquer nome desde que seja inicializado ao criar o modelo.

$table

String. Nome da tabela à qual o modelo será mapeado. O default é o nome do modelo.

$data

Array. O array $data vai conter os dados do modelo, mapeando cada campo com seu valor.

$fields

Array. Lista dos campos da tabela relacionada ao modelo, associado ao tipo e tamanho de cada campo. O default é obtido diretamente do banco de dados.

$validationstatus

Array. O array $validationstatus contém a situação de validação de cada campo do modelo. O método Validate() popula este array com a situação de cada um dos campos, (true/false) de acordo com as regras de validação de cada um.

 

Métodos

LoadById($id)

Carrega os dados do registro cuja chave primária é $id no array $data do modelo.

Save()

Salva os dados do modelo no banco. Antes executa a função ValidateFields() para garantir que os dados são válidos. Se a chave primária no modelo estiver vazia, ele cria um novo registro. Se estiver preenchida, ele atualiza os dados do registro que contém este id.

DeleteById($id)

Remove da tabela o registro com o id $id. Bem simples. O único perigo aqui é se a gente não configurar corretamente a tabela para lidar com o efeito cascata, ou seja, se a gente deixar alguém apagar um registro que é referenciado por outros registros. Por exemplo, se eu apago um post que tem vários comentários relacionados, precisamos remover todos eles, certo? A boa notícia, pra quem ainda não sabe, é que o banco cuida disso sozinho pra nós.

ValidateFields()

Valida cada um dos campos de acordo com suas regras de validação. Como a classe Model é uma classe genérica, ou melhor dizendo, abstrata, vamos fazer aqui uma validação também genérica, de acordo com as características dos campos da tabela. Assim, vamos validar, por exemplo, se um determinado campo é texto, e se seu tamanho está dentro do tamanho estipulado na descrição da tabela. Para validações mais específicas, vamos reescrever a função ValidateFields() em cada um dos nossos modelos.

Reparem, caros leitores, que nosso objetivo aqui é clareza e simplicidade. Poderiamos ter chamado a função LoadById simplesmente de Load; Mas a idéia é deixar o uso da função realmente claro; Percebam que já na função Save isso já não é necessário pois vamos salvar o estado do modelo no banco, sem a necessidade de nenhum campo específico estar definido. Para salvar um novo registro vamos simplesmente salvar um modelo que tenha sua variável $id vazia.

Uma diferença bastante grande desta proposta em relação ao Cake é a divisão de trabalho: como o Cake usa o pattern ActiveRecord, tem muita coisa dentro da classe Model. Aqui nós estamos dividindo este trabalho com a classe ModelCollection, que irá fazer todo o trabalho de banco de dados quando estivermos lidando com grupos de registros, ou coleções.

That's all, Folks! Mandem seus comentários, dúvidas e xingamentos e até a próxima!

 

2007-07-03 04:58:16
tags: 

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 17)

Para ver os artigos anteriores desta série, clique aqui.

Olá pessoal. Obrigado por todos os comentários e emails! Estou tendo um prazer enorme escrevendo essa série, e fico ainda mais feliz quando vejo que realmente tem gente curtindo.

Mas vamos ao que realmente interessa. No nosso último encontro expliquei que existem diferentes formas, ou patterns, para lidar com buscas e coleções de objetos, mas que eu ainda não estava satisfeito com aquilo que conheço. Como já disse em outras oportunidades, aprendi o pouco que sei de PHP nos becos escuros da internet, entre latões de lixo e scripts procedurais escritos por sabe-se lá quem. Não tenho, portanto, nenhuma pretensão de conhecer todos os sutras da programação orientada a objetos. Mas, outro dia, eu tive uma idéia que achei interessante, nada original, mas um pouco diferente das duas implementações que mostrei pra vocês.

Um dos design patterns mais interessantes (e, para mim, mais difíceis de entender) que conheço chama-se Iterator. Devo ter lido as definições e exemplos umas 1000 vezes sem entender direito como usar essa coisa. Pra quem vem de um passado procedural, especialmente usando uma linguagem como o PHP que lida muito bem com arrays, é muito difícil entender o benefício de usar os tais iterators.

Os iterators, juntamente com um outro pattern chamado Collection, vão formar a base do nosso novo desenho. O que estou pensando é relativamente simples: assim como temos a classe base Model, para operações com modelos, quero criar uma classe ModelCollection, que vai cuidar das operações com coleções de modelos. Dessa forma, temos um paralelo interessante: assim como podemos carregar as informações do modelo fazendo $model->load e assim por diante, poderemos carregar as informações de coleções fazendo $modelcollection->load.

O que eu gosto nesse desenho é que tudo fica claramente definido: um $model é sempre um objeto, enquanto que os $modelcollections são sempre coleções de objetos. Uma função de um $model jamais vai retornar uma coleção de objetos; uma $modelcollection jamais vai retornar um objeto (caso uma busca retorne apenas um resultado, ainda assim será uma coleção com apenas um objeto).

Parêntesis: uma das coisas que me deixa maluco no PHP é o fato de você poder misturar absolutamente tudo - uma mesma função pode retornar uma string, um array, um objeto... É o que os estudiosos chamam de loosely typed language, uma linguagem que não exige a definição dos dados usados pelas variáveis. Claro que essa característica pode ser usada para o bem: muitas implementações interessantes usam essa característica para flexibilizar e agilizar o desenvolvimento. Mas com o tempo, depois de passar anos da minha vida tentando decifrar códigos de scripts PHP por aí, eu afirmo categoricamente: tentar entender um script ou classe de alguma outra pessoa escrita em uma linguagem loosely typed é muito mais difícil.

Mas por que esse parêntesis tão grande? Porque no desenho que estou propondo vamos sempre lidar com objetos; Porque as Collections nos oferecem possibilidades muito, mas muito interessantes mesmo pra trabalhar com objetos e coleções de classes diferentes de forma transparente.

Não entendeu? Bom, vamos aos nossos exemplos então. Imagine que temos dois modelos completamente diferentes. Tipo "Post" e "Carro". Logicamente, dentro do nosso desenho, vamos ter os modelos $post e $carro, além das collections $postcollection e $carrocollection. Onde queremos chegar? Bem, como tanto a classe PostCollection quanto a classe CarroCollection são "filhas" da classe ModelCollection temos a certeza que estas subclasses implementam as mesmas funções. E por que isso é lindo? Porque eu posso agora implementar, por exemplo, a classe HtmlHelper, cuja função buildTable aceita qualquer ModelCollection e imprime uma tabela html. Pra usar, basta fazer $htmlhelper>buildTable($postcollection) ou $htmlhelper->buildTable($carrocollection).

Sacaram a beleza? Esse é só um exemplo do que podemos fazer com as collections e os iterators, mas o importante é ver que esse design faz com que qualquer coleção se comporte de forma igual, pra que a gente possa implementar coisas genéricas que funcionam pra qualquer uma delas. A coisa que imediatamente vêm à mente é que essa classe ModelCollection vai ter um método genérico pra fazer paginação, um pra fazer ordenação dos dados, e assim por diante.

Quando nossas classes estiverem prontas, olha só o que vamos precisar fazer pra imprimir uma tabela html:

(controller)

/* A função ModelController::loadPage() carrega a página indicada no $_GET['page'] e no $_GET['pagesize']

já na ordem indicada por $_GET['orderby'] e $_GET['direction'] */

$this->postcollection->loadPage();

(view)

$htmlhelper->buildTable($postcollection); // imprime a tabela

$htmlhelper->createPreviousPageLink($postcollection); //cria o link para a próxima página

$htmlhelper->createNextPageLink($postcollection); //cria o link para a próxima página

E acabou. Não é legal isso?

Até a próxima!

2007-06-05 04:32:35
tags: 

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 16)

Para ver os artigos anteriores desta série, clique aqui.

Olá amigos, estamos de volta. Semana de muitas emoções pra quem gosta de Tênis: Roland Garros é sempre uma festa. Este seu humilde autor confessa que, em sua tenra juventude, chegou a disputar alguns torneios; Lembro-me claramente de ganhar uma cesta com catchup, mostarda, mini-pepinos, azeitonas e mais alguns outros enlatados da empresa patrocinadora de um dos torneios de que participei. Minha melhor colocação, 3o colocado. Pensando bem, acho que fiz bem em não seguir a carreira.

Mas, deixando Jankovics, Vaidisovas, Sharapovas e Ivanovics de lado por alguns momentos, quero voltar nossa atenção novamente para os modelos do MVC. Muito bem amigos, hoje vamos tentar definir o que todos os nossos modelos devem minimamente fazer. Nosso objetivo hoje é listar os métodos da nossa classe Model, a classe base para todos os modelos que criarmos. Não tenho a pretensão de fazer um estudo teórico detalhado; dentro de nossa filosofia PHP de Rua® o que estamos buscando é somente o básico, o essencial.

Então, caros amigos, quais seriam estas funções essenciais, compartilhadas por todos os modelos que vamos criar? Lembrando nossas conversas anteriores, o modelo é um ajudante, uma classe que só existe para ler e gravar informações em nosso banco de dados, e sua estrutura de dados será sempre mapeada à estrutura de uma tabela que existe no BD.

Por exemplo, se criarmos um modelo Tenista, que possua as variáveis $id$nome$ranking, é certo que teremos uma tabela que guardará esta informação, que pra facilitar (nomes têm poder, lembrem-se disso) chamaremos de Tenistas. Essa tabela terá, minimamente, os campos id, nome e ranking. Sacou?

Estamos, de fato, mapeando uma classe à uma tabela, e instâncias da classe a registros da tabela. Essa técnica é chamada de ORM, Object-Relational Mapping. A coisa é mais interessante ainda: como estamos falando de OOP, em algum de nossos métodos vamos bolar um jeito de fazer esse mapeamento da forma mais simples e genérica possível, pra que não tenhamos que criar queries sql específicas para cada novo modelo que criamos.

Vejam a beleza dessa solução, caros leitores! Quando terminarmos a classe Model, qualquer novo objeto de uma classe que a estenda poderá ser salvo ou lido direto do banco sem precisar que nós façamos uma query específica!

Sendo assim, creio que já ficou bastante claro que precisamos das funções save() e load() em nosso modelo-base. A gente não precisa gastar muitos neurônios pra perceber que também precisamos da função delete() além da função __construct() que todos as classes já nos oferecem para criar novos objetos.

Nesse ponto começa uma saudável polêmica: onde devem ficar as funções para buscar objetos? Se eu quiser pegar todas as tenistas que estão no banco, pra quem eu devo pedir? Em alguns frameworks MVC, como o CakePHP, estas funções ficam na classe Model mesmo. Então vocë encontra funções como findAll(), findById(), findByName() e coisas assim, dentro da classe Model.

Pra ser muito sincero, eu acho meio esquisito pedir pra uma tenista buscar as outras tenistas. É assim que fica no CakePHP:

$tenista = new Tenista();

$todasastenistas = $tenista->findAll();

Cara, eu acho isso muito esquisito. A semantica da coisa, sabe? Acho que é um pouco de preciosismo, eu sei. Adoro o Cake, estou realmente impressionado com sua facilidade de uso, mas acho o design da classe model um pouco confusa.

Outros frameworks e ORMs fazem diferente. Criam uma classe só pra fazer isso. Um gnomo, lembram-se? Então, o cara faz o seguinte:

$gnomo = new DbManager();

$todasastenistas = $gnomo->findAll('tenista');

 Nesse caso, a classe DbManager tem todos os tais métodos pra retornar arrays buscados no banco. Hm. Acho um pouco melhor, mas ainda acho estranho não termos uma classe mais esperta, que possamos estender para criar comportamentos diferentes caso a gente precise. A resposta está próxima, caros amigos. Mas não posso perder a chance de continuar no próximo capítulo, não é mesmo?

Até a próxima.

2007-05-02 05:25:23
tags: 

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 15)

Para ver os artigos anteriores desta série, clique aqui.

Olá meus caros. De volta. "Que você viva em tempos interessantes". Esta antiga maldição chinesa (!?) explica muito bem a sensação que toma conta deste seu humilde autor. Muito a fazer, muito a compreender, pouco tempo. Desculpas à parte, retorno à esta série para falar mais um pouco sobre Exceptions, um conceito fundamental que talvez ajude alguns de vocês a poupar um tanto desse tão precioso tempo; afinal, como bem disse Benjamin Franklin, o tempo perdido nunca mais será encontrado.

Muito bem, senhores, o que faz nossas Exceptions tão especiais? Para entender, precisamos pensar na forma como gerenciamos erros em nossas aplicações. Ou melhor, precisamos pensar na forma como lidamos com erros em nossas vidas. Talvez isso complique as coisas, mas falar sobre a vida sempre é mais interessante, não?

Joãozinho trabalha numa fábrica de automóveis. (é um exemplo, ok? Não quero entrar em discussões morais sobre trabalho infantil, capitalismo, revolução industrial, etc e tal. Tudo bem, é um exemplo estúpido. Podemos ir em frente?) Joãozinho aperta parafusos na linha de montagem, e sua supervisora é a Mariazinha. O chefe da Mariazinha é o Aristides, que fica lá em cima no escritório.

Bom, você sabe que numa fábrica todo tipo de problema pode acontecer. Alguém pode esquecer de mandar o parafuso para o Joãozinho, o parafuso que foi mandado pode estar com defeito, o próprio Joãozinho pode vacilar e não apertar o parafuso... Sabe lá o que pode acontecer. Diante desse cenário, como você faz para gerenciar todos os erros que podem acontecer? Dá uma lista de IFs infinita para o Joãozinho para que ele mesmo resolva tudo? "Se o parafuso estiver com defeito, corra até a sala do Aristides e aperte o botão para parar as máquinas". "Se você dormir no ponto e não apertar o parafuso, corra até a sala do Aristides e aperte o botão para parar as máquinas", e assim por diante? Hmm... não sei se vocês estão entendendo, mas já está dando preguiça só de pensar em fazer essa lista. E a preguiça, amigos, é o nosso sentido-aranha, é nosso superpoder psiônico que avisa quando alguma coisa está esquisita no ar. Imagina se a Mariazinha tiver 50 funcionários sob sua supervisão, vai ter gente correndo pra tudo o que é lado, a sala do Aristides vai ficar totalmente destruída, e produção que é bom nada.

Apresento-lhes, então, as Exceptions, e seus companheiros inseparáveis Try, Catch e Throw. Vamos olhar para o problema acima de um outro ângulo. Imagine se a gente pudesse dizer o seguinte pro pessoal da fábrica:

Olha, Joãozinho, você vai tentar apertar o parafuso. Qualquer problema que você tiver, escreve num papel, amassa até virar uma bola e arremessa pra Mariazinha. A Mariazinha pega a bola, e se não for um problema que ela saiba resolver, amassa de novo e arremessa pro Aristides. Se ele também não souber resolver, a gente para a linha de produção na hora.

Tentar... Pegar... Arremessar... Vocês... sacaram? (se você viu Warriors, na versão legendada, está rolando no chão de rir nesse momento). [Agora relendo esse artigo me parece claro que o criador deste conceito gostava muito de baseball, ou cricket] Se vocês ainda não sacaram, vamos escrever um pouco de (pseudo)código:

/*
* Created on 02/05/2007
*
*/

class Apertador {

    function apertarParafuso(){

        echo "parafuso apertado!";

    }

    function oopa($descricao){

        echo "Apertador: oopa! Deu pau! Segura aí supervisor!";
        throw new Exception($descricao); // amassa e joga a bolinha pra cima!

    }

}



class Supervisor{

    function mandaApertar(){

        try {

            $joaozinho = new Apertador();

            $joaozinho->apertarParafuso();

            $joaozinho->oopa("Parafuso com defeito");

        }

        catch (Exception $e){
            echo "Supervisor: xi! Exception! Não sei o que fazer! Toma aí chefe!";
            throw $e; // peguei a bolinha, mas não sei o que fazer com ela... manda pra cima!

        }

    }

}



class Chefe{

    function vamosTrabalhar(){

        try {

        $mariazinha = new Supervisor();

        $mariazinha->mandaApertar();

        } catch (Exception $e) {
            
            echo "Chefe: Para tudo! Quero saber o que aconteceu aqui!"
		echo $e;
            die("Parando as máquinas!");

        }

    }

}


// Muito bem, agora a gente contrata o Aristides:


$aristides = new Chefe();

$aristides->vamosTrabalhar();

?>

 

Rode esse script e veja o resultado. Que interessante! Basta o Joãozinho mandar o erro pra cima que todos na cadeia (as classes que chamam a classe Apertador, neste caso) recebam aquela Exception, graças ao uso do comando Try. Tudo o que roda dentro de um bloco Try é monitorado, e caso uma Exception seja "jogada" ou "arremessada" pra cima o controle vai para o bloco Catch imediatamente abaixo.

Perceba também que ao gerarmos uma Exception, ela já é criada com um monte de informações importantes automaticamente: em qual arquivo e linha que o erro aconteceu, um Trace completo passando por todo o código até o Catch que "pegou" aquela Exception e muito mais. Para ver um bom resumo, basta mandar imprimir a variável que contém a Exception; mas você também pode acessar as propriedades do objeto separadamente usando métodos como $e->getMessage(), $e->getLine(), $e->getFile() entre outras. Não é bom isso?

Com as exceptions nosso trabalho passa a ser somente colocar os Throws nos locais onde erros acontecem, deixando o tratamento desses erros para classes mais acima na hierarquia. Mesmo que você não trate estas Exceptions com blocos Try/Catch a coisa funciona: você pode deixar a exception subir na hierarquia até chegar ao topo, pois toda exception que chega ao topo sem ser "pega" gera um erro fatal. Então, caros amigos, vocês podem trabalhar inicialmente só colocando throws no código, e, ao final, refinar o tratamento distribuindo blocos try/catch pelas classes da hierarquia. Muito bonito.

Mas tem uma coisa ainda mais bonita. Sim, amigos, a classe Exception pode ser estendida. É uma classe como outra qualquer. Isso quer dizer que no nosso caso você poderia criar a exception DefeitoParafusoException, que já conteria a mensagem de erro e mais outras informações que você necessita sem precisar nunca mais digitar. Exemplo:

 

class DefeitoParafusoException extends Exception {
     function __construct(){
        parent::__construct();
        $this->message = "Defeito no parafuso!";
    } 
} 

 

Sacaram? Agora toda vez que você criar uma nova Exception do tipo DefeitoParafusoException a mensagem já será colocada por default na variável $this->message da Exception em questão! Imagine o que você pode fazer com isso. Mas ainda tem mais. Imagine agora que você não quer parar a produção simplesmente por causa de um parafuso com defeito. Muito bem, então vamos fazer com que os Supervisores comecem a "Pegar" este tipo de Exception para não incomodar o chefe:

 

 class Supervisor{

    function mandaApertar(){

        try {

            $joaozinho = new Apertador();

            $joaozinho->apertarParafuso();

            $joaozinho->oopa("Parafuso com defeito");

        }

        catch (DefeitoParafusoException $e){
            echo "Supervisor: é só um maldito parafusinho, diabos! Continuem!";
        }
        
        catch (Exception $e){
            echo "Supervisor: xi! Exception! Não sei o que fazer! Toma aí chefe!";
            throw $e; // peguei a bolinha, mas não sei o que fazer com ela... manda pra cima!

        }

    }

}

 

pra ver isso funcionando, precisamos alterar a função Apertador::oopa() para mandar uma DefeitoParafusoException:

 

 function oopa(){

        echo "Apertador: oopa! Deu pau! Segura aí supervisor!";
        throw new DefeitoParafusoException(); // não precisa mais da descrição...
} 

 

E pronto. Vocês perceberam que podemos utilizar vários blocos de catch um embaixo do outro? Essa é a beleza desta solução. Podemos começar com um tratamento de erros bem genérico, e depois ir especializando com novas subclasses, sempre lembrando de colocar as subclasses mais específicas, neste caso a DefeitoParafusoException nos blocos anteriores, deixando o catch genérico (Exception $e) por último.

Por hoje é só, pessoal, dúvidas, reclamações, elogios e doações são bem-vindas. Grande abraço a todos.