Back-End

7 jul, 2009

Crud usando Symfony 1.0

Publicidade

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.