Tutorial Askeet dia 2: definindo o modelo de dados
Anteriormente
Durante o 1º dia deste longo mas interessante tutorial, vimos como instalar o framework, setar uma nova aplicação e preparar o ambiente de desenvolvimento. A propósito, o código da aplicação gerado durante o primeiro dia está disponível no repositório SVN do askeet em:
http://svn.askeet.com/
Os objetivos do segundo dia são definir o que será o resultado final em termos de funcionalidade, desenhar o modelo de dados, e começar a codificar. Isto irá incluir a geração do mapeamento objeto-relacional, em seguida usá-lo para criar, recuperar e atualizar registros no banco de dados como uma aplicação scaffolding.
Temos um monte de coisas pra ver. Vamos lá.
O projeto revelado
O que você quer saber? Esta é uma pergunta interessante. Existem várias questões como:
- O que eu devo fazer à noite com minha namorada?
- Como eu posso gerar tráfego no meu blog?
- Qual é o melhor framework para desenvolver uma aplicação web?
- Qual é o restaurante mais confortável em Paris?
- Qual é a resposta para a vida, universo ou tudo que existe no nosso planeta?
Todas estas questões não têm apenas uma resposta e a melhor delas é uma questão de opinião. Indo direto ao ponto, a questão é que perguntas que possuem apenas uma resposta (do tipo, quanto é 1 + 1) são menos interessantes mas são as únicas que podem ser resolvidas na web. Isto não é justo.
Conheça o askeet. Um website dedicado a ajudar pessoas a responder suas perguntas. Mas quem responderá a sua lista de perguntas? Qualquer um. E todo mundo estará apto a rankiar as respostas dos outros, com isso as melhores respostas terão mais visibilidade. Como o número de questões tende a aumentar, fica praticamente impossível organiza-las em categorias e sub-categorias, por isso o criador da questão será apto a inserir palavras-chave (tags) com qualquer palavra que quiser, ex.: web 2.0 internet desenvolvimento. "A la" del.icio.us. É claro que a popularidade das tags será representada pela tag bubble. Se alguém quiser acompanhar as respostas de uma questão em particular, ainda terá a possibilidade de escrever no feed RSS da mesma. Eventualmente o backend é necessário para moderar questões e respostas indevidas.
Então você deve perguntar: Eu nunca vi um site como este na web? Bem, se você realmente já tivesse visto, nós nos enganamos, mas se você se reportar para faqts, eHow, Ask Jeeves ou sites similares com intuitos semelhantes (perguntas e respostas colaborativas) será pouco provável encontrar AJAX, RSS ou tags. Estamos falando de uma aplicação web 2.0.
O melhor negócio do askeet é que não é apenas um simples website, é uma aplicação que qualquer um pode baixar, instalar em casa ou na intranet de uma empresa, além de poder adicionar novas características caso haja demanda. O código fonte será disponibilizado numa licença open-source. Seu setor de recursos humanos está procurando por um sistema de gestão do conhecimento? Você quer manter ativos todos os detalhes que você teve que aprender para resolver os problemas do seu carro? Você não deve desenvolver um FAQ (Frequently Asked Questions) no seu site? Não procure mais, use o askeet. Bem, isto existirá até no máximo o Natal.
Onde começar?
Então por onde começar uma aplicação symfony? Tudo depende apenas de você. Você pode escrever histórias, planejar um jogo e encontrar parceiros para usar uma metodologia como o XP, ou escrever uma especificação detalhada do que será o site, junto com sketch de todos os objetos, estados, interações e descrição do modelo se você for um fã do padrão UML.
Mas este tutorial não é muito mais que o desenvolvimento da aplicação apenas, então comece com o modelo de dados relacional básico, e vá adicionando novas características uma por uma. O que precisamos é uma aplicação que possa ser usada no dia-a-dia, não um projeto gigante que nunca diz nada. Num mundo ideal, devemos escrever uma unidade de teste (test unit) para qualquer nova funcionalidade, mas honestamente não temos tempo pra isso. Um dia poderemos nos dedicar aos testes, por isso não se preocupe e continue lendo.
Para este projeto, usaremos o banco MySQL com o tipo mais novo de estrutura de tabela dele, a InnoDB, para gozar de vantagens como restrições de integridade e suporte a transações. Nós poderíamos usar o SQLite num primeiro passo para evitar setar um ambiente real de banco e assim economizar tempo. Isto requerirá apenas algumas poucas mudanças no arquivo 'databases.yml', que nós iremos deixar para você investigar e exercitar.
Modelo de dados
Modelo Relacional
Obviamente, existirá as tabelas de 'pergunta' e 'resposta'. Precisaremos da tabela de 'usuários' e guardaremos os interesses dos mesmos por uma especifica questão na tabela de 'interesses'. Finalmente precisaremos de uma tabela de relevância para guardar o nível de utilidade de cada resposta votada pelos usuários.
Usuários deverão ser identificados quando quiserem adicionar uma questão, votar em uma determinada resposta ou declarar interesse por uma questão específica. Usuários não precisam ser cadastrados para adicionar uma resposta, mas suas respostas precisam, para serem distinguidas , estar ligadas a um usuário. Caso o usuário insira uma resposta sem se identificar, a mesma será automaticamente ligada a um usuário anônimo chamado 'Anonymous Coward'. É fácil de entender visualizando o diagrama das entidades mostrado abaixo:
Perceba que declaramos um campo created_at para cada tabela. Symfony reconhece este campo e seta seu valor como a hora atual do sistema quando um registro é criado. O mesmo acontece com o campo updated_at, seu valor é definido quando um campo é atualizado.
schema.xml
O modelo relacional tem que ser traduzido para um arquivo de configuração e assim o symfony compreender. Este é o propósito do arquivo schema.xml, localizado no diretório askeet/config/.
Existem duas maneiras de escrever este arquivo: na mão ou de de um banco previamente criado. Vamos ver a primeira solução. Primeiramente, precisamos renomear o exemplo instalado por padrão:
$ svn rename config/schema.xml.sample config/schema.xml
A sintaxe do schema.xml, é explicada detalhadamente no website do Propel, e é relativamente simples: é um arquivo XML, no qual tags <table> contêm <column>, <foreign-key> and <index>. Uma vez que você escreve um, você está apto a escrever praticamente todos eles. Aqui está o schema.xml correspondente ao modelo relacional discrito previamente:
[xml]
<?xml version="1.0" encoding="UTF-8"?>
<database name="propel" defaultIdMethod="native" noxsd="true">
<table name="ask_question" phpName="Question">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="user_id" type="integer" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="title" type="longvarchar" />
<column name="body" type="longvarchar" />
<column name="created_at" type="timestamp" />
<column name="updated_at" type="timestamp" />
</table>
<table name="ask_answer" phpName="Answer">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="question_id" type="integer" />
<foreign-key foreignTable="ask_question">
<reference local="question_id" foreign="id"/>
</foreign-key>
<column name="user_id" type="integer" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="body" type="longvarchar" />
<column name="created_at" type="timestamp" />
</table>
<table name="ask_user" phpName="User">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="nickname" type="varchar" size="50" />
<column name="first_name" type="varchar" size="100" />
<column name="last_name" type="varchar" size="100" />
<column name="created_at" type="timestamp" />
</table>
<table name="ask_interest" phpName="Interest">
<column name="question_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="ask_question">
<reference local="question_id" foreign="id"/>
</foreign-key>
<column name="user_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="created_at" type="timestamp" />
</table>
<table name="ask_relevancy" phpName="Relevancy">
<column name="answer_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="ask_answer">
<reference local="answer_id" foreign="id"/>
</foreign-key>
<column name="user_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="score" type="integer" />
<column name="created_at" type="timestamp" />
</table>
</database>
Note que o nome do banco é setado no arquivo do propel. Este é o parâmetro usado para conectar a camada do Propel ao framework symfony. O verdadeiro nome do banco será definido no arquivo de configuração databases.yml (veja abaixo).
Existe outra forma de criar o schema.xml se você já tiver um banco de dados criado. Se você for familiar com uma ferramenta de design gráfico do banco, você preferirá construir o schema a partir do banco MySQL criado. Antes disso, você precisa apenas editar o arquivo propel.ini localizado no diretório askeet/config/ e inserir as configurações de conexão do seu banco:
propel.database.url = mysql://username:password@localhost/databasename
...onde username, password, localhost and databasename são na verdade as configurações do seu banco. Você pode chamar o comando propel-build-schema (from the askeet/ directory) para gerar o schema.xml a partir do banco:
$ symfony propel-build-schema
Note: algumas ferramentas permitem que você construa o banco graficamente (por exemplo Fabforce's Dbdesigner) e assim gerar o
schema.xmlmais facilmente (with DB Designer 4 TO Propel Schema Converter).
Contrução do modelo objeto (Object Model)
Para usar a InnoDB engine, uma linha tem que ser adicionada ao arquivo propel.ini que está no diretório askeet/config/:
propel.mysql.tableType = InnoDB
Uma vez o arquivo schema.xml construído, você pode gerar o modelo baseado em objetos a partir do modelo relacional. No symfony, o mapeamento objeto relacional é manipulado pelo propel, mas encapsulado dentro do comando symfony:
$ symfony propel-build-model
Este comando (você deve chama-lo a partir do diretório raiz do projeto askeet) irá gerar as classes correspondentes às tabelas definidas no schema, junto com o antecessor cs padrão. Você pode ver o código gerado no diretório askeet/lib/model/om/. Existem duas classes por tabela, se você quiser mais detalhes confira o model chapter do livro do symfony. Essas classes serão substituídas toda vez que você usar o build-model (o que vai acontecer várias vezes durante o projeto. Então se você precisar adicionar métodos ao modelo de objetos, você tem que modificar os arquivos localizados na pasta askeet/lib/model/ - estes herdam dos da /om.
O banco de dados
Conexão
Agora que o symfony tem um modelo de objetos do banco, está na hora de conectar seu projeto ao banco MySQL. Primeiro você tem que criar o banco no servidor MySQL:
$ mysqladmin -u youruser -p create askeet
Agora você abre o arquivo de configuração askeet/config/databases.yml. Se esta for a sua primeira vez no symfony, você descobrirá que os arquivos de configuração do symfony são escritos em YAML. A sintaxe é bem simples, mas existem algumas restrições gerais, entre elas: nunca use tabulação, sempre use espaços. Uma vez que você saiba isto, você está pronto para editar o arquivo e inserir as configurações do seu banco abaixo da categoria all::
all:
propel:
class: sfPropelDatabase
param:
phptype: mysql
host: localhost
database: askeet
username: youruser
password: yourpasswd
Se você quer saber mais sobre os arquivos de configuração YAML, leia configuration in practice chapter do livro do symfony.
Contrução
Se você não escreveu o schema.xml na mão, você provavelmente já tem os relacionamentos de suas tabelas de alguma forma. Você pode então passar essa parte.
Para vocês que são "fans do teclado", aqui está a surpresa: Você não precisa criar as tabelas e colunas no banco MySQL. Você já havia feito isto no schema.xml, então o symfony construirá todos os comandos SQL que você precisa:
$ symfony propel-build-sql
Este comando cria o schema.sql no diretório askeet/data/sql/. Use-o como comandos SQL no MySQL (para isso, utilize o comando abaixo para ler o arquivo e automaticamente jogar todos os comandos no banco que você criou no passo anterior):
$ mysql -u youruser -p askeet < data/sql/schema.sql
Teste o acesso aos dados via CRUD
É sempre bom ver se o modelo funcionou normalmente ou se precisa de alguns ajustes. Até agora seu browser não foi usado em nenhum momento, e ainda iremos começar a criar a aplicação web...
Então vamos criar um conjunto básico de templates e ações para manipular os dados da tabela de questões 'question'. Isto irá permitir criar algumas questões e mostra-las.
No diretório askeet/, digite:
$ symfony propel-generate-crud frontend question Question
Este comando gera o scaffolding para o módulo question na aplicação frontend, baseada no modelo de objeto Question do Propel que basicamente criar, recupera, atualiza e deleta registros (CRUD - Create, Retrieve, Update and Delete). Não se confunda: A scaffolding não é a aplicação final, mas a estrutura básica que você poderá desenvolver novas características, adicionar regras de negócio e customizar o look and fell (visual).
A lista de todas as ações criadas pelo CRUD é mostrada abaixo:
- list: mostra todos os registros da tabela
- index: volta para a lista
- show: mostra todos os campos de um determinado registro
- edit: mostra um formulário para criar um novo registro ou editar um já existente
- update: modifica o registro de acordo com parâmetros passados pela requisição, depois remete para o a ação ''show''
- delete: exclui um determinado registro na tabela
Você pode encontrar mais sobre essas ações no capítulo scaffolding chapter do livro do symfony.
No diretório askeet/apps/frontend/modules/, veja o novo módulo question que foi criado e navegue pelo seu código.
Toda vez que você adicionar uma nova classe para ser auto-carregada, não se esqueça de limpar o cache (para recarrega-lo):
$ symfony cc frontend config
Você pode testar a requisição acessando no browser a URL:
http://askeet/question
Vá em frente, e brinque um pouco com isso. Adicione novas questões, liste-as, e delete-as. Se tudo funcionar corretamente, significa que o modelo e a conexão com o banco estão corretos. Este é um excelente teste de funcionalidade.
Nos vemos amanhã
Você não escreveu uma linha de código PHP, e mesmo assim já tem uma aplicação simples funcionando. Não é mal para um segundo dia de estudos. Amanhã você começará usando processos batch, e aprenderá como extender o modelo.
Agora que você já sabe que tipo de aplicação será feita, você já está apto a imaginar características adicionais para ela. Sinta-se a vontade para sugerir qualquer coisa através da lista de discussão askeet mailing-list, a idéia mais popular virá no 21º dia do calendário.
Sinta-se a vontade para navegar pelo código do tutorial de hoje (tag release_day_2) no:
http://svn.askeet.com/tags/release_day_2