IBM
Canais iMasters

PHP + MySQL

Crud usando Symfony 1.0

Dando continuidade à série sobre artigos do framework symfony, essa semana montarei um CRUD sem utilizar o admin generator. Será criado um sistema de agenda de contatos de telefones.

Tudo será feito de forma manual e vocês verão que não é nenhum bicho de sete cabeças. Qualquer dúvida quanto à instalação, confira o artigo anterior.

Requisitos:

Versão do framework utilizada: 1.0.18 (http://www.symfony-project.org/)

IDE utilizada: Netbeans 6.7 (http://www.netbeans.org)

Siga os procedimentos abaixo:

Crie a pasta onde ficará o projeto:

mkdir imasters

Verifique se o symfony está setado no path (sendo que o /opt/php/bin é onde meu php tá rodando).

No linux:

PATH=$PATH:/opt/php/bin; export PATH

No Windows:

Defina em variáveis de ambiente no campo PATH o caminho do PHP. Exemplo:

C:\\xampplite\\php

Dentro da pasta imasters, criaremos o projeto do symfony (será criada toda a estrutura). Os comandos abaixo são executados no terminal.

symfony init-project imasters

Criar a aplicação frontend:

symfony init-app frontend

O banco de dados que iremos utilizar será o mesmo do artigo anterior.

Abaixo, o SQL para criação do BD e das tabelas.

CREATE SCHEMA IF NOT EXISTS `exemplo01` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ;
USE `exemplo01`;

CREATE TABLE IF NOT EXISTS `exemplo01`.`contato` (
`id` INT NOT NULL AUTO_INCREMENT ,
`nome` VARCHAR(100) NULL ,
PRIMARY KEY (`id`) )
ENGINE = InnoDB;


CREATE TABLE IF NOT EXISTS `exemplo01`.`tipo` (
`id` INT NOT NULL AUTO_INCREMENT ,
`tipo` VARCHAR(45) NULL ,
PRIMARY KEY (`id`) )
ENGINE = InnoDB;


CREATE TABLE IF NOT EXISTS `exemplo01`.`telefone` (
`id` INT NOT NULL AUTO_INCREMENT ,
`tipo_id` INT NULL ,
`contato_id` INT NULL ,
`ddd` VARCHAR(2) NULL ,
`fone` VARCHAR(20) NULL ,
PRIMARY KEY (`id`) ,
INDEX `fk_telefone_contato` (`contato_id` ASC) ,
INDEX `fk_telefone_tipo` (`tipo_id` ASC) ,
CONSTRAINT `fk_telefone_contato`
FOREIGN KEY (`contato_id` )
REFERENCES `exemplo01`.`contato` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_telefone_tipo`
FOREIGN KEY (`tipo_id` )
REFERENCES `exemplo01`.`tipo` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;

Configure a conexão com o banco de dados nos arquivos: config/databases.yml e config/propel.ini

databases.yml

all:
propel:
class: sfPropelDatabase
param:
phptype: mysql
hostspec: localhost
database: exemplo01
username: root
password:
encoding: utf8
persistent: false

propel.ini

Configurar as linhas abaixo, onde exemplo01 é o nome do bd que criamos.

propel.database.createUrl  = mysql://root@localhost/
propel.database.url = mysql://root@localhost/exemplo01

Vamos gerar o schema.yml (mapeamento objeto-relacional) e os models:

Para gerar o schema.yml:

symfony propel-build-schema

Para gerar os modelos:

symfony propel-build-model

Criar os módulos:

symfony init-module frontend contato

symfony init-module frontend tipo

symfony init-module frontend telefone

e criar as actions necessárias para o funcionamento da aplicação:

apps/frontend/modules/contato/actions/actions.class.php

class contatoActions extends sfActions {


/**
* Exibe os contatos cadastrados
*/
public function executeIndex() {

$c = new Criteria();
$c->addAscendingOrderByColumn(ContatoPeer::NOME);
$this->contatos = ContatoPeer::doSelect($c);
}


/**
* Edita/Novo Contato
*/
public function executeEditar() {

if($this->getRequestParameter('id')) {
$this->contato = ContatoPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($this->contato);
$this->acao = 'Editar Contato';
}
else {
$this->contato = new Contato();
$this->acao = 'Novo Contato';
}
}


/**
* Atualiza os dados do contato
*/
public function executeAtualizar() {
if($this->getRequestParameter('id')) {
$contato = ContatoPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($contato);
}
else {
$contato = new Contato();
}

$contato->setNome($this->getRequestParameter('nome'));
$contato->save();

$this->forward('contato', 'index');
}


/**
* Exclui um contato
*/
public function executeExcluir()
{
$contato = ContatoPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($contato);
$contato->delete();
$this->forward('contato', 'index');
}

}

apps/frontend/modules/telefone/actions/actions.class.php

class telefoneActions extends sfActions
{

/**
* Exibe os telefones cadastrados
*/
public function executeIndex() {

$c = new Criteria();
$c->addAscendingOrderByColumn(TelefonePeer::CONTATO_ID);
$c->addAscendingOrderByColumn(TelefonePeer::TIPO_ID);
$this->telefones = TelefonePeer::doSelect($c);

}


/**
* Edita/Novo Telefone
*/
public function executeEditar() {

if($this->getRequestParameter('id')) {
$this->telefone = TelefonePeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($this->telefone);
$this->acao = 'Editar Telefone';
}
else {
$this->telefone = new Telefone();
$this->acao = 'Novo Telefone';
}

//contatos
$c = new Criteria();
$c->addAscendingOrderByColumn(ContatoPeer::NOME);
$this->contatos = ContatoPeer::doSelect($c);

//tipos
$c = new Criteria();
$c->addAscendingOrderByColumn(TipoPeer::TIPO);
$this->tipos = TipoPeer::doSelect($c);
}


/**
* Atualiza os dados do telefone
*/
public function executeAtualizar() {
if($this->getRequestParameter('id')) {
$telefone = TelefonePeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($telefone);
}
else {
$telefone = new Telefone();
}

$telefone->setTipoId($this->getRequestParameter('tipo_id'));
$telefone->setContatoId($this->getRequestParameter('contato_id'));
$telefone->setDdd($this->getRequestParameter('ddd'));
$telefone->setFone($this->getRequestParameter('fone'));
$telefone->save();

$this->forward('telefone', 'index');
}


/**
* Exclui um telefone
*/
public function executeExcluir() {
$telefone = TelefonePeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($telefone);
$telefone->delete();
$this->forward('telefone', 'index');
}

}

apps/frontend/modules/tipo/actions/actions.class.php

class tipoActions extends sfActions {

/**
* Exibe os tipos cadastrados
*/
public function executeIndex() {

$c = new Criteria();
$c->addAscendingOrderByColumn(TipoPeer::TIPO);
$this->tipos = TipoPeer::doSelect($c);
}


/**
* Edita/Novo Tipo
*/
public function executeEditar() {

if($this->getRequestParameter('id')) {
$this->tipo = TipoPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($this->tipo);
$this->acao = 'Editar Tipo';
}
else {
$this->tipo = new Tipo();
$this->acao = 'Novo Tipo';
}
}


/**
* Atualiza os dados do tipo
*/
public function executeAtualizar() {
if($this->getRequestParameter('id')) {
$tipo = TipoPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($tipo);
}
else {
$tipo = new Tipo();
}

$tipo->setTipo($this->getRequestParameter('tipo'));
$tipo->save();

$this->forward('tipo', 'index');
}


/**
* Exclui um tipo
*/
public function executeExcluir() {
$tipo = TipoPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($tipo);
$tipo->delete();
$this->forward('tipo', 'index');
}
}

Agora vamos criar os templates referentes às actions:

apps/frontend/modules/contato/templates/indexSuccess.php

<?php include_partial('global/menu') ?>

<?php echo button_to('Novo contato', 'contato/editar') ?>

<table>
<thead>
<tr>
<td>Id</td>
<td>Nome</td>
<td>Opções</td>
</tr>
</thead>

<tbody>
<?php
if($contatos) { ?>
<?php foreach($contatos as $contato) { ?>
<tr>
<td><?php echo $contato->getId() ?></td>
<td><?php echo $contato->getNome() ?></td>
<td><?php echo link_to('editar', 'contato/editar?id=' . $contato->getId() ) ?>  
<?php echo link_to('excluir', 'contato/excluir?id=' . $contato->getId(), '?confirm=Deseja mesmo excluir?' ) ?></td>
</tr>
<?php
}
}
else { ?>
<tr>
<td colspan="3">Nenhum contato cadastrado!</td>
</tr>
<?php } ?>
</tbody>
</table>

apps/frontend/modules/contato/templates/editarSuccess.php

<?php include_partial('global/menu') ?>

<?php use_helper('Object') ?>

<?php echo form_tag('contato/atualizar') ?>
<?php echo object_input_hidden_tag($contato, 'getId') ?>

<table>
<thead>
<tr>
<td colspan="2"><?php echo $acao ?></td>
</tr>
</thead>

<tbody>
<tr>
<td>Nome:</td>
<td><?php echo object_input_tag($contato, 'getNome') ?></td>
</tr>

<tr>
<td colspan="2"><?php echo submit_tag('Gravar') ?>  
<?php echo button_to('Cancelar', 'telefone/index', array('confirm'=>'Deseja mesmo cancelar?')) ?></td>
</tr>
</tbody>
</table>

apps/frontend/modules/telefone/templates/indexSuccess.php

<?php include_partial('global/menu') ?>

<?php echo button_to('Novo telefone', 'telefone/editar') ?>

<table>
<thead>
<tr>
<td>Id</td>
<td>Contato</td>
<td>Tipo</td>
<td>Fone</td>
</tr>
</thead>

<tbody>
<?php
if($telefones) { ?>
<?php foreach($telefones as $telefone) { ?>
<tr>
<td><?php echo $telefone->getId() ?></td>
<td><?php echo $telefone->getContato()->getNome() ?></td>
<td><?php echo $telefone->getTipo()->getTipo() ?></td>
<td><?php echo link_to('editar', 'telefone/editar?id=' . $telefone->getId() ) ?>  
<?php echo link_to('excluir', 'telefone/excluir?id=' . $telefone->getId(), '?confirm=Deseja mesmo excluir?' ) ?></td>
</tr>
<?php
}
}
else { ?>
<tr>
<td colspan="4">Nenhum telefone cadastrado!</td>
</tr>
<?php } ?>
</tbody>
</table>

apps/frontend/modules/telefone/templates/editarSuccess.php

<?php include_partial('global/menu') ?>

<?php use_helper('Object') ?>

<?php echo form_tag('telefone/atualizar') ?>
<?php echo object_input_hidden_tag($telefone, 'getId') ?>

<table>
<thead>
<tr>
<td colspan="2"><?php echo $acao ?></td>
</tr>
</thead>

<tbody>
<tr>
<td>Contato:</td>
<td><?php echo select_tag(
'contato_id',
objects_for_select(
$contatos,
'getId',
'getNome',
$telefone->getContatoId(),
'include_custom=--Selecione--'
)
) ?></td>
</tr>

<tr>
<td>Tipo:</td>
<td><?php echo select_tag(
'tipo_id',
objects_for_select(
$tipos,
'getId',
'getTipo',
$telefone->getTipoId(),
'include_custom=--Selecione--'
)
) ?></td>
</tr>

<tr>
<td>DDD:</td>
<td><?php echo object_input_tag($telefone, 'getDdd', array('size'=>'2')) ?></td>
</tr>

<tr>
<td>Telefone:</td>
<td><?php echo object_input_tag($telefone, 'getFone', array('size'=>'15')) ?></td>
</tr>

<tr>
<td colspan="2"><?php echo submit_tag('Gravar') ?>  
<?php echo button_to('Cancelar', 'telefone/index', array('confirm'=>'Deseja mesmo cancelar?')) ?>
</td>
</tr>
</tbody>
</table>

apps/frontend/modules/tipo/templates/indexSuccess.php

<?php include_partial('global/menu') ?>

<?php echo button_to('Novo tipo', 'tipo/editar') ?>

<table>
<thead>
<tr>
<td>Id</td>
<td>Tipo</td>
<td>Opções</td>
</tr>
</thead>

<tbody>
<?php
if($tipos) { ?>
<?php foreach($tipos as $tipo) { ?>
<tr>
<td><?php echo $tipo->getId() ?></td>
<td><?php echo $tipo->getTipo() ?></td>
<td><?php echo link_to('editar', 'tipo/editar?id=' . $tipo->getId() ) ?>  
<?php echo link_to('excluir', 'tipo/excluir?id=' . $tipo->getId(), '?confirm=Deseja mesmo excluir?' ) ?></td>
</tr>
<?php
}
}
else { ?>
<tr>
<td colspan="3">Nenhum tipo cadastrado!</td>
</tr>
<?php } ?>
</tbody>
</table>

apps/frontend/modules/tipo/templates/editarSuccess.php

<?php include_partial('global/menu') ?>

<?php use_helper('Object') ?>

<?php echo form_tag('tipo/atualizar') ?>
<?php echo object_input_hidden_tag($tipo, 'getId') ?>

<table>
<thead>
<tr>
<td colspan="2"><?php echo $acao ?></td>
</tr>
</thead>

<tbody>
<tr>
<td>Nome:</td>
<td><?php echo object_input_tag($tipo, 'getTipo') ?></td>
</tr>

<tr>
<td colspan="2"><?php echo submit_tag('Gravar') ?>  
<?php echo button_to('Cancelar', 'tipo/index', array('confirm'=>'Deseja mesmo cancelar?')) ?></td>
</tr>
</tbody>
</table>

apps/frontend/templates/_menu.php

<div id="menu">
<ul>
<li><span><?php echo link_to('Contato', 'contato/index') ?></span></li>
<li><span><?php echo link_to('Tipo', 'tipo/index') ?></span></li>
<li><span><?php echo link_to('Telefone', 'telefone/index') ?></span></li>
</ul>
</div>

Alterar em apps/frontend/config/routing.yml a action que será executada em primeira instância.

homepage:
url: /
param: { module: default, action: index }

mudar para:

homepage:
url: /
param: { module: contato, action: index }

Criei um arquivo chamado _menu.php que será o menu do sistema e será incluído em todas as páginas. Nos templates, para efetuar a inclusão utilizaremos o include_partial(). Os partials são pedaços de códigos reutilizáveis, são armazenados na pasta templates/. Um arquivo partial SEMPRE começa com sublinhado (_), isso ajuda a distiguir os arquivos, já que eles ficam no mesmo diretório. Ele pode ser incluído no mesmo módulo, em outro módulo, ou no diretório global templates/.

Edite o arquivo apps/frontend/config.yml para alterar os dados do projeto como title, description e keywords. No caso do nosso projeto, deixei da seguinte maneira:

de:

metas:
title: symfony project
robots: index, follow
description: symfony project
keywords: symfony, project
language: en

para:

metas:
title: Crud Symfony - iMasters
robots: index, follow
description: Exemplo de criaçao de um crud manual
keywords: crud, symfony, imasters
language: en

Limpe o cache do symfony

symfony cc

Vamos abrir o browser e ver como está ficando o nosso "pequeno" sistema. Veja que está sem layout mas está funcional, incluindo, alterando e excluindo os dados.

http://127.0.0.1/imasters/web/frontend_dev.php/

Último passo será mexermos no CSS para criar uma apresentação mais amigável. Abra o arquivo: web/css/main.css

ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input
{ margin: 0; padding: 0; }
a img,:link img,:visited img { border: none; }

ol, ul, li {list-style: none;}

a
{
text-decoration: none;
}

a:hover
{
text-decoration: underline;
}

body, td
{
margin: 0;
padding: 0;
font-family: Arial, Verdana, sans-serif;
font-size: 11px;
}

body
{
padding: 20px;
}


/*MENU*/
#menu{background:#666666;height:24px;}
#menu li{float:left;position:relative;}
#menu li a { color:#fff;}
#menu li span{display:block; font:bold 14px arial;line-height:24px;width:122px; text-align:center; background:#8F8F8F; cursor:pointer;margin-left:10px}
#menu li ul{overflow:hidden;position:absolute;z-index:10000;}
#menu li ul li{float:none;display:block; font:bold 12px arial;line-height:18px;width:120px; text-align:center; background:#fff; cursor:pointer;margin-left:10px; border:1px solid #CCCCCC; margin-top:2px;}
#menu li ul li a{ color:#8F8F8F;}

/*TABELAS*/
thead tr{background: #A2090C; }
thead td{text-align:center;color:#fff;font:bold 11px arial;padding:3px 0;}
tbody td{padding:3px;}
table {width:100%; margin-top:15px; *border-collapse: collapse;}
table .nivel_2{background:#ccc;}
table .nivel_2 td{color:#000;text-align:left; padding:3px;}
table tbody td{border-bottom:1px solid #ccc;}
table tbody td.center{text-align:center}

Veja como ficou o sistema após as alterações no CSS:

Lista os contatos.Lista os contatos.

Novo contatoNovo contato

Novo telefoneNovo telefone

Links úteis:

http://www.symfony-project.org/book/1_0/

Download do sistema

Para efetuar o download dos arquivos, clique AQUI

ATENÇÃO

Se for utilizado o arquivo acima, não se esqueça de mudar o arquivo config/config.php que indica o caminho do symfony.

Semana que vem tem mais. Espero que vocês tenham gostado!

Não deixe de nos enviar críticas ou sugestões para o próximo assunto, afinal, a coluna é de vocês.


Comente também

8 Comentários

Maurício Vinicius de Oliveira Santos
Maurício Vinicius de Oliveira Santos

Eu achei muito boa a matéria.
Na minha opinião, o Zend é bem mais fácil e menos trabalhoso de trabalhar.
Só a instalação do Symfony já é chata pra kct :(
Fora a documentação e forums do Zend que é mais vasto, pois conseguimos achar bem mais documentações e foruns de discussão sobre o Zend do que sobre o Symfony(Profundamente triste).
Não estou defendendo o Zend, estou passando apenas minha opinião.
Gostaria muito que o symfony cresça muito mais do que esta crescendo.

Marcelo Rodrigues
Marcelo Rodrigues

Acho que você foi infeliz no comentário, talvez por que não conheça bem. Não defendeu, mas acabou defendendo assim mesmo. Eu sou usuário dos dois frameworks, tenho mais conhecimento do Zend, embora trabalhe hoje com o Symfony, e minha visão é oposta em alguns sentidos. A documentação do Symfony não é escassa, mas não quer dizer que não tenha, tem excelentes tutoriais no próprio site e e fóruns especializados de altíssimo nível. Além disso, boa parte dos vários frameworks MVC em php tem um componente ou outro que são fortemente baseados no que o Symfony já fazia, justamente por que o Symfony é bem mais antigo e muito mais maduro que o Zend em certos aspectos. Para uma aplicação simples, o Symfony é muito mais ágil com suas linhas de comando e plugins, que praticamente desobriga o desenvolvedor de perder tempo configurando bootstraps e controllers como ainda acontece no Zend. Além disso, o Symfony não tem instalador, é simplesmente mandar instalar via PEAR, ou seja, ele faz isso sozinho ou então simplesmenet fazer download e colocar na pasta e pronto, não tem segredo. A arquitetura de plugins dele então é coisa de sonho que o Zend ainda tem que comer muito arroz com feijão para chegar próximo, e olha que já conheço o Zend desde a versão 0.3.

Quanto a matéria, acredito que o Júlio foi infeliz na adoção da versão do framework, que hoje já está na 1.2 e possui muito mais facilidades. Além disso, o componente de banco adotado, o Propel, não é mais recomendado pelo próprio Symfony. Hoje, o Doctrine, que esse sim é o melhor em abstração de banco, sem sombra de dúvidas, é o componente oficial. Além disso, ele é muito mais simples de se fazer qualquer coisa com banco. Enfim, de qualquer forma, valeu a intenção.

Júlio César Martini
Júlio César Martini

Olá Marcelo,
Obrigado pelo comentário mas acabei optando por prosseguir com a versão 1.0 devido ao artigo anterior. No futuro espero fazer um artigo sobre a versão 1.2 assim poderemos até fazer comparações pois no 1.2 a parte de formulário pode ser montada usando widgets. Aliás o symfony tb já está para liberar a versão 1.3. :)

Maurício Vinicius de Oliveira Santos
Maurício Vinicius de Oliveira Santos

Olá Marcelo...vlw pela resposta :)
então...eu estou usando o ZF há pouco tempo...e sinceramente eu tentei o Symfony pela primeira vez, mas não tive muito sucesso pq eu achei bem mais documentação e forum de discussão sobre o Zend...
talvez eu tenha procurado em lugares errados.
Mesmo assim eu me identifiquei mais com o Zend, mas espero um dia poder utilizar o symfony em algum projeto.

Carlos Costa
Carlos Costa

Eu achei o artigo infeliz. Primeiro que o Symfony faz CRUD automático, segundo que a matéria tem 1000 linhas de código e 20 de explicação. Outro fato foi usar uma versão antiga do framework. Não sou usuário do Symfony, mas este tutorial também não ajuda a ser. Não mancha a história do Júlio César, maior contribuinte do PHP que eu conheço, mas que foi infeliz, foi.

Marcelo Rodrigues
Marcelo Rodrigues

Leia a primeira linha do artigo, ele mesmo afirmou que ia fazer o CRUD manualmente. O intuito foi justamente não fazer de forma automatizada para que os que não sabem compreendam como funciona o processo de CRUD e não a geração do CRUD, que é sinceramente, não vejo vantagem a não ser de gerar sujeira de código.

Neido Tavares
Neido Tavares

Olha cara, concordo com o Marcelo Rodrigues, se você não esta gostando e tambem não sabe ler, então faça o seu.
Há e Júlio César você me ajudou muito cara com esse tutorial.

Ricardo Nunes
Ricardo Nunes

Muito bom esse tutorial, me ajudou muito, parabens !!!

Qual a sua opinião?

Comentários considerados ofensivos serão moderados.
KingHost

Parceiros

IBM
PagSeguro
Internet Innovation
Dialhost
HostNet
Tecla
KingHost
DotStore
Dinamize