Anteriormente no Jobeet
Aqueles de vocês loucos para abrir seu editor de texto e fazer algo em PHP ficarão felizes em saber que o tutorial de hoje vai nos dar algum desenvolvimento. Iremos definir o modelo de dados do Jobeet, usar um ORM para interagir com o banco de dados e construir o primeiro módulo da nossa aplicação. Mas como o symfony faz muito trabalho para nós, teremos um módulo web totalmente funcional sem escrever muito código PHP.
O Modelo Relacional
As user stories escritas ontem descrevem os principais objetos do nosso projeto: empregos, afiliados e categorias. Aqui está o diagrama de entidade relacionamento correspondente:
Em adição as colunas descritas nas histórias, nós também acrescentamos um campo
created_at
para algumas tabelas. O symfony reconhece esses campos e define o
valor da data e hora atual do sistema quando um registro é criado. O mesmo ocorre para
os campos updated_at
: O seu valor é definido pela data e hora do sistema quando um registro
é atualizado.
O Esquema
Para armazenar os empregos, afiliados e categorias, nós evidentemente precisamos de um banco de dados relacional.
Mas, como o symfony é um framework Orientado à Objetos, vamos manipular objetos sempre que pudermos. Por exemplo, em vez de escrever comandos SQL para recuperar registros do banco de dados, nós preferimos usar objetos.
As informações do banco de dados relacional devem ser mapeadas para um modelo de objeto. Isto pode ser feito com uma ferramenta ORM e, felizmente, o symfony já vem com duas delas: Propel e Doctrine. Neste tutorial, vamos utilizar Doctrine.
O ORM precisa de uma descrição das tabelas e as relações para criar as classes relacionadas. Existem duas maneiras de criar esta descrição do esquema: pela introspecção de um banco de dados existente ou criando-a à mão.
Como o banco de dados ainda não existe e queremos manter o banco de dados do Jobeet
agnóstico, vamos criar o arquivo de esquema à mão, editando o arquivo vazio
config/schema.yml
:
# config/doctrine/schema.yml --- JobeetCategory: actAs: Timestampable: ~ columns: name: type: string(255) notnull: true JobeetJob: actAs: Timestampable: ~ columns: category_id: type: integer notnull: true type: type: string(255) company: type: string(255) notnull: true logo: type: string(255) url: type: string(255) position: type: string(255) notnull: true location: type: string(255) notnull: true description: type: string(4000) notnull: true how_to_apply: type: string(4000) notnull: true token: type: string(255) notnull: true unique: true is_public: type: boolean notnull: true default: 1 is_activated: type: boolean notnull: true default: 0 email: type: string(255) notnull: true expires_at: type: timestamp notnull: true relations: JobeetCategory: onDelete: CASCADE local: category_id foreign: id foreignAlias: JobeetJobs Affiliates: class: JobeetAffiliate local: category_id foreign: affiliate_id refClass: JobeetCategoryAffiliate foreignAlias: JobeetJobs JobeetAffiliate: actAs: Timestampable: ~ columns: url: type: string(255) notnull: true email: type: string(255) notnull: true unique: true token: type: string(255) notnull: true is_active: type: boolean notnull: true default: 0 JobeetCategoryAffiliate: columns: category_id: type: integer primary: true affiliate_id: type: integer primary: true relations: JobeetCategory: onDelete: CASCADE local: category_id foreign: id JobeetAffiliate: onDelete: CASCADE local: affiliate_id foreign: id
tip
Caso você tenha decidido criar as tabelas escrevendo instruções SQL, você pode
gerar o arquivo de configuração schema.yml
correspondente, executando a tarefa
doctrine:build-schema
.
O esquema é a tradução direta do diagrama entidade relacionamento no formato YAML.
O arquivo schema.yml
contém a descrição de todas as tabelas e suas colunas.
Cada coluna é descrita com a seguinte informação:
type
: O tipo da coluna (boolean
,integer
,float
,decimal
,string
,array
,object
,blob
,clob
,timestamp
,time
,date
,enum
,gzip
)notnull
: Defina comotrue
se você deseja que a coluna seja obrigatóriaunique
: Definatrue
se você deseja criar um índice único para a coluna.
note
O atributo onDelete
define o comportamento ON DELETE
das chaves estrangeiras,
o Doctrine suporta CASCADE
, SET NULL
e RESTRICT
. Por exemplo, quando
um registro job
é excluído, todos os registros jobeet_category_affiliate
relacionados
serão automaticamente excluídos pelo banco de dados.
O Banco de Dados
O framework symfony suporta todos os banco de dados que são suportados pelo PDO (MySQL, PostgreSQL, SQLite, Oracle, MSSQL, ...). O PDO é a camada de abstração de dados que vem com o PHP.
Vamos usar o MySQL neste tutorial:
$ mysqladmin -uroot -pmYsEcret create jobeet
note
Sinta-se livre para escolher outro mecanismo de banco de dados se quiser. Não será difícil adaptar o código que iremos escrever pois iremos usar o ORM, que vai escrever o SQL para nós.
Nós precisamos dizer ao symfony para usar este banco de dados para o projeto Jobeet:
$ php symfony configure:database --name=doctrine --class=sfDoctrineDatabase "mysql:host=localhost;dbname=jobeet" root mYsEcret
A tarefa configure:database
tem três argumentos: o
PDO DSN, o nome do usuário e
a senha para acessar o banco de dados. Se você não tem nenhuma senha em seu
servidor de desenvolvimento, basta omitir o terceiro argumento.
note
A tarefa configure:database
armazena a configuração do banco de dados no arquivo
de configuração config/databases.yml
. Ao invés de utilizar esta tarefa, você pode
editar este arquivo à mão.
O ORM
Graças à descrição do banco de dados no arquivo schema.yml
, podemos usar algumas
tarefas Doctrine integradas para gerar os comandos SQL necessários para criar as
tabelas no banco de dados:
$ php symfony doctrine:build-sql
A tarefa doctrine:build-sql
gera comandos SQL no diretório data/sql
,
otimizada para o mecanismo de banco de dados que nós configuramos:
# snippet do data/sql/lib.model.schema.sql CREATE TABLE `jobeet_category` ( `id` INTEGER NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL, PRIMARY KEY (`id`) )Type=InnoDB;
Para realmente criar as tabelas no banco de dados, você precisará executar o
a tarefa doctrine:insert-sql
:
$ php symfony doctrine:insert-sql
tip
Como para qualquer ferramenta de linha de comando, as tarefas do symfony podem ter argumentos e opções.
Cada tarefa vem com uma mensagem de ajuda integrada que pode ser visualizada executando a tarefa
help
:
$ Php symfony ajudar propelir: inserir-sql
A mensagem de ajuda lista todos os possíveis argumentos e opções, fornece os valores padrão para cada um deles e, também, alguns exemplos úteis de utilização.
O ORM também gera classes PHP que mapeiam os registros das tabelas para objetos:
$ php symfony doctrine:build-model
A tarefa doctrine:build-model
gera arquivos PHP no diretório lib/model
que pode ser usado para interagir com o banco de dados.
Pesquisando os arquivos gerados, você provavelmente notou que o Doctrine
gerou três classes por tabela: para a tabela jobeet_job
:
JobeetJob
: Um objeto desta classe representa um único registro da tabelajobeet_job
. A classe está vazia por padrão.BaseJobeetJob
: A classe pai deJobeetJob
. Cada vez que você executadoctrine:build-model
, esta classe é sobrescrita, então todas as personalizações devem ser feitas na classeJobeetJob
.JobeetJobTable
: A classe define métodos que, na maioria, retornam coleções de objetosJobeetJob
. A classe está vazia por padrão. A valores das colunas de um registro podem ser manipulados com um modelo do objeto usando alguns accessors (métodosget*()
) e mutators (métodosset*()
):[php] $job = new JobeetJob(); $job->setPosition('Web developer'); $job->save();
echo $job->getPosition();
$job->delete();
Você pode também definir chaves estrangeiras diretamente vinculando os objetos
$category = new JobeetCategory(); $category->setName('Programming'); $job = new JobeetJob(); $job->setCategory($category);
A tarefa doctrine:build-all
é um atalho para as tarefas que nós executamos
nesta seção e algumas mais. Então, execute esta tarefa agora para gerar formulários e
validadores para as classes do modelo do Jobeet.
$ php symfony doctrine:build-all
Você verá os validadores em ação no final do sia e formulários serão explicados em muitos detalhes no dia 10.
tip
A tarefa doctrine:build-all-reload
é um atalho para a tarefa doctrine:build-all
seguida da tarefa doctrine:data-load
.
Como você verá mais adiante, o symfony faz um autoload (carregamento automático) das classes PHP para você, o que significa
que você nunca precisará usar um require
no seu código. É uma das inúmeras coisas
que o symfony automatiza para o desenvolvedor, mas há um aspecto negativo:
quando você adiciona uma nova classe, você precisará limpar o cache do symfony. Como o
doctrine:build-model
, criou um monte de classes novas, vamos limpar o cache:
$ php symfony cache:clear
tip
A tarefa symfony é composta de um namespace e o nome da tarefa. Cada um pode ser abreviado
desde que não haja ambigüidade com outras tarefas. Então, o seguinte comando
é equivalente ao cache:clear
:
$ php symfony cc
Os Dados Iniciais
As tabelas foram criadas no banco de dados mas não há dados em si. Para qualquer aplicação web, existem três tipos de dados:
Dado Inicial: Dados iniciais são necessários para a aplicação trabalhar. Por exemplo, Jobeet precisa algumas categorias iniciais. Se não, ninguém será capaz de submeter um emprego. Nós também precisamos de usuário administrador para poder fazer o login no aplicação backend.
Dado de Teste: Dados de teste são necessários para a aplicação ser testada. Como um desenvolvedor, você precisa escrever testes para garantir que o Jobeet se comportará como descrito nas user stories, e a melhor forma é escrever testes automatizados. Então, cada vez que você executar seus testes, você precisa limpar o banco de dados com com alguns dados novos para testar.
Dado de usuário: Dados de usuário são criados pelo usuário durante a vida normal da aplicação.
Cada vez que o symfony cria as tabelas no banco de dados, todos os dados serão perdidos.
Para popular o banco de dados com os dados iniciais, poderíamos criar um script PHP,
ou executar alguns SQL com o programa mysql
. Mas à medida que a necessidade é muito comum,
existe uma maneira melhor com o symfony: criar arquivos YAML no diretório data/fixtures/
e usar a tarefa doctrine:data-load
para carregá-los no banco de dados:
# data/fixtures/categories.yml JobeetCategory: design: name: Design programming: name: Programming manager: name: Manager administrator: name: Administrator # data/fixtures/jobs.yml JobeetJob: job_sensio_labs: JobeetCategory: programming type: full-time company: Sensio Labs logo: /uploads/jobs/sensio_labs.png url: http://www.sensiolabs.com/ position: Web Developer location: Paris, France description: | You've already developed websites with symfony and you want to work with Open-Source technologies. You have a minimum of 3 years experience in web development with PHP or Java and you wish to participate to development of Web 2.0 sites using the best frameworks available. how_to_apply: | Send your resume to fabien.potencier [at] sensio.com is_public: true is_activated: true token: job_sensio_labs email: job@example.com expires_at: '2008-10-10' job_extreme_sensio: JobeetCategory: design type: part-time company: Extreme Sensio logo: /uploads/jobs/extreme_sensio.png url: http://www.extreme-sensio.com/ position: Web Designer location: Paris, France description: | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in. Voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. how_to_apply: | Send your resume to fabien.potencier [at] sensio.com is_public: true is_activated: true token: job_extreme_sensio email: job@example.com expires_at: '2008-10-10'
Um arquivo de fixture é escrito em YAML e define os objetos do modelo, rotulado com um
nome único. Esse rótulo é de grande utilidade para associar objetos relacionados sem ter
que definir chaves primárias (que são automaticamente incrementadas e não podem ser definadas).
Por exemplo, a categoria de emprego job_sensio_labs
é programming
, que é
o rótulo dado a categoria 'Programming'.
O arquivo fixture pode conter objetos de um um ou vários modelos.
note
Propel exige que os arquivos de fixture sejam precedidos com números para determinar a ordem em que os arquivos serão carregados. Com o Doctrine isto não é exigido pois todos os fixtures serão carregados e salvos na ordem correta para ter certeza de que chaves estrangeiras serão definidas corretamente.
Em um arquivo de fixture, você não precisa definir todos os valores das colunas. Se você não definir,
o symfony irá usar o valor padrão definido no esquema do banco de dados. E como
symfony usa o Doctrine# para carregar os dados no banco de dados, todos os
comportamentos integrados (como definir as colunas created_at
ou updated_at
), ou o
comportamento personalizado que você possa ter acrescentado as classes do modelo são ativados.
Carregando dados iniciais no banco de dados é tão simples quanto executar a
tarefa doctrine:data-load
:
$ php symfony doctrine:data-load
Veja-o em ação no Navegador
Temos usado a linha de comando interface muito, mas isso não é realmente emocionante, especialmente para um projeto web. Temos, agora, tudo o que é necessário para criar páginas web que interagem com o banco de dados.
Vamos ver como exibir a lista de empregos, como editar uma tarefa existente, e como excluir um emprego. Como foi explicado durante o dia 1, um projeto symfony é feito de aplicações. Cada aplicação é feita de módulos. Um módulo é um conjunto auto-contido de código PHP que representa uma característica da aplicação (módulo da API, por exemplo), ou um conjunto de manipulações que o usuário pode fazer em um modelo de objeto (um módulo de emprego, por exemplo).
Symfony é capaz de gerar automaticamente um módulo para um dado modelo que fornece funcionalidades básicas de manipulação:
$ php symfony doctrine:generate-module --with-show --non-verbose-templates frontend job JobeetJob
A tarefa doctrine:generate-module
gera um módulo job
na aplicação frontend
para o modelo JobeetJob
. Assim como em muitas tarefas do symfony,
alguns arquivos e diretórios serão criados para você no diretório
apps/frontend/modules/job
:
Diretório | Descrição |
---|---|
actions/ | As ações do módulo |
templates/ | Os templates do módulo |
O arquivo actions/actions.class.php
define todas as ações disponíveis para
o módulo job
:
Nome da Ação | Descrição |
---|---|
index | Exibe os registros da tabela |
show | Exibe os campos de um determinado registro |
new | Exibe um formulário para criar um novo registro |
create | Cria um novo registro |
edit | Exibe um formulário para editar um registro existente |
update | Atualiza um registro de acordo com os valores enviados pelo usuário |
delete | Exclui um determinado registro da tabela |
Você pode agora testar o módulo job
no navegador:
http://jobeet.localhost/frontend_dev.php/job
Se você tentar ediar um emprego, você notará que o drop down do Category id
tem uma lista
de todos os nomes das categorias. O valor de cada opção é obtido do método __toString()
.
Doctrine vai tentar fornecer um método __toString()
base adivinhando o nome da coluna
descritiva, como title
, name
, subject
, etc. Se você deseja algo personalizado
então você terá que adicionar seus próprios métodos __toString()
como abaixo:
// lib/model/JobeetJob.php class JobeetJob extends BaseJobeetJob { public function __toString() { return sprintf('%s at %s (%s)', $this->getPosition(), $this->getCompany(), $this->getLocation()); } } // lib/model/JobeetAffiliate.php class JobeetAffiliate extends BaseJobeetAffiliate { public function __toString() { return $this->getUrl(); } }
Você agora pode criar e editar empregos. Procure deixar em branco um campo obrigatório, ou tente informar uma data inválida. É isso mesmo, o symfony criou regras de validação básica pela introspecção do esquema do banco de dados.
Vejo você amanhã
E é tudo por hoje. Já avisei na introdução. Hoje, nós mal escrevemos código PHP
mas temos um módulo web funcionando para o modelo job
,
pronto para ser refinado e personalizado. Lembre-se, nenhum código PHP também significa nenhum bug!
Se você ainda tiver alguma energia sobrando, sinta-se livre para ler o código gerado para o módulo e o modelo e tentar compreender como ele funciona. Se não, não se preocupe e durma bem, pois amanhã, vamos falar sobre um dos paradigmas mais utilizados nos frameworks web, o MVC design pattern.
Os código escrito hoje está disponível no repositório SVN do Jobeet com a tag
release_day_03
(http://svn.jobeet.org/tags/release_day_03/
).
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.