Sep 28 2008

Validar relacionamento belongsTo no CakePHP

Category: CakePHP, PHPvbmendes @ 11:39

Uma prática que eu acho bastante interessante é verificar se realmente um registro existe no banco antes de associá-lo a outro. Por exemplo, suponha que se têm dois models:

/app/models/pessoa.php
class Pessoa extends AppModel {
    var $name = "Pessoa";
}

/app/models/aluno.php
class Aluno extends AppModel {
    var $name = "Aluno";
    var $belongsTo = array(
        'Pessoa' => array(
            'className' => 'Pessoa',
            'foreignKey' => 'pessoa_id'
        )
    );
}

Eu quero que sempre que eu adicionar um aluno, o sistema valide se a pessoa a qual eu estou associando este aluno esteja cadastrada no banco. Ou seja, quero garantir que o aluno estará vinculado a uma pessoa válida.

Para isso eu criei uma função de validação que coloquei no meu app_model.php. Veja:

function checkBelongsTo($data, $relationName){
	$field = $this->belongsTo[$relationName]['foreignKey'];
	$search[$this->{$relationName}->primaryKey] = $data[$field];
	return $this->{$relationName}->find('count',array('conditions' => $search));
}

Vamos analisar o código. A primeira linha da função determina qual o campo que irá conter a foreign key, no nosso caso pessoa_id. A segunda linha gera um array de condições no formato:

Array[primaryKey => valor]

Onde primaryKey é obtido diretamente do model associado, e valor é o id do registro ao qual se quer fazer a relação. Na terceira linha é feita uma busca ao banco para saber quantos registros existem que satisfaçam essa condição, o que, obedecendo a condição de que a chave primária deve ser única, irá retornar 0 ou 1. O resultado desta consulta será o retorno da função.
Feito isso, basta adicionar a condição de validação ao model:

/app/models/aluno.php
class Aluno extends AppModel {
    var $name = "Aluno";
    var $validate = array(
        'pessoa_id' => array(
            'rule' => array('checkBelongsTo','Pessoa'),
            'message' => 'Pessoa inexistente.'
        );
    );
    var $belongsTo = array(
        'Pessoa' => array(
            'className' => 'Pessoa',
            'foreignKey' => 'pessoa_id'
        )
    );
}

Perceba que eu adicionei a regra de validação checkBelongsTo passando como parametro o nome do relacionamento entre aluno e pessoa.

Espero ter sido claro. Qualquer dúvida comente.

Tags: , ,


Mar 10 2008

Evitando múltiplos posts com session

Category: PHPvbmendes @ 17:52

Muitos desenvolvedores já se depararam com a situação de que usuários impacientes enviam formulários dezenas de vezes, e isso realmente incomoda, pois aparecem vários registros idênticos.

Uma forma de solucionar tal problema seria usando session. No momento do envio, salva algo que identifique(que chamarei de identidade) o formulário na SESSION, e sempre que for enviar algo, verifica se a identidade da session bate com a do fomulário enviado, se bater, o sistema impede que o formulário seja enviado novamente.

No caso de formulários de e-mail, eu particularmente uso como identidade a mensagem do e-mail criptografada por md5, visto que reduz significativamente o tamanho do que será guardado na session.

Bom, vamos aos códigos:

session_start();
$md5Mensagem = md5($_POST['mensagem']);
if(isset($_SESSION['md5Mensagem'] && $md5Mensagem == $_SESSION['md5Mensagem']){
echo 'Esta mensagem já foi enviada';
} else {
if(ENVIA_MENSAGEM){
$_SESSION['md5Mensagem'] = $md5Mensagem;
echo 'Mensagem enviada com sucesso';
} else echo 'Mensagem não foi enviada';
}

O código acima está bem simplificado, eu encapsulei o processo de enviar a mensagem (função mail, gravar no banco, ou qualquer outro método) por fugir do escopo deste post.

O código verifica se existe algum md5Mensagem na session, e se ele é igual ao md5 da mensagem enviada, se sim, ele informa que a mensagem já foi enviada, se não, o sistema tenta fazer o envio, se der certo ele salva o md5 na session, se não, ele apenas informa que não deu certo.

Bom, é isso, qualquer dúvida, postem comentários que responderemos.

Tags: ,


Feb 11 2008

Mensagens de Erro com Session em PHP (serialize)

Category: PHPvbmendes @ 09:49

Pra quem pensa que a variável global SESSION é apenas para autenticação de usuários, aqui vai mais uma. O objetivo principal da SESSION, assim como dos COOKIES é enviar variáveis calculadas em uma página para outra página.

Irei apresentar aqui um sistema simples de mensagens de erro e confirmação com o uso da SESSION.

Primeiramente vou apresentar uma classe que eu usei para armazenar as mensagens:

<?
class Mensagem {
    public static $ERRO=1,$SUCESSO=2;

    private $mensagem;
    private $tipo;

    public function __construct($mensagem,$tipo){
        $this->mensagem = $mensagem;
        $this->tipo = $tipo;
    }

    public function getMensagem(){
        return $this->mensagem;
    }

    public function getTipo(){
        return $this->tipo;
    }

    public static function tipoToString($tipo){
        if($tipo == 1) return 'erro';
        else if($tipo == 2) return 'sucesso';
    }
}
?>

Pra quem já mexe com orientação a objetos, a classe acima é simples. Uma classe com 2 variáveis estáticas($ERRO e $SUCESSO) que seriam os tipos de mensagens, uso isso mais para definir o CSS que irá acompanhar a mensagem. Tem duas variáveis, uma para a mensagem em si, e outra para o tipo da mensagem, um construtor que define as variáveis, e funções para obter os valores das variáveis.

O uso é simples, vamos ver um caso abaixo:

Temos a página de formulário simplificada (form.php) da seguinte forma:

<?
if(isset($_SESSION['mensagens'])){
    $mensagens = unserialize($_SESSION['mensagens']);
    unset($_SESSION['mensagens']);
} else {
    $mensagens = array();
}

if(isset($_SESSION['pessoa'])){
    $pessoa = unserialize($_SESSION['pessoa']);
    unset($_SESSION['pessoa'];
} else {
    $pessoa = new Pessoa();
}

?>

<ul class="mensagens">
    <?= foreach($mensagens as $mensagem): ?>
    <li><p class="<?=Mensagem::tipoToString($mensagem->getTipo())?>"><?=$mensagem->getMensagem()?></p>  </li>
    <? endforeach; ?>
</ul>
<form action="insert.php" method="post">
    <input name="nome" value="<?=$pessoa->getNome()?>" type="text" />
    <input name="idade" value="<?=$pessoa->getIdade()?>" type="text" />
    <input type="submit" />
</form>

No código acima, criamos um formulário solicitando nome e idade. As primeiras linhas são código PHP, atente para a função unserialize, ela desserializa os dados que vieram da SESSION, visto que a mesma não suporta objetos. Pegamos da SESSION as mensagens, e apagamos da SESSION para que a mensagem só apareça uma vez. Fazemos o mesmo com um objeto do tipo pessoa, o qual armazena os dados que já foram enviados previamente, evitando que o usuário preencha o formulário novamente.

Abaixo vem uma lista, com todas as mensagens que estavam na SESSION. Atente para a função tipoToString, da classe mensagem, que eu criei apenas para definir o atributo class do parágrafo da mensagem.

Em seguida vem o formulário pegando todos os values do objeto pessoa que estava na SESSION.

Abixo teremos o script que irá processar este formulário (submit.php):

<?

$nome = $_POST['nome'];
$idade = $_POST['idade'];

$pessoa = new Pessoa();

if(!$nome) $mensagens[] = new Mensagem("O nome não foi informado",Mensagem::$ERRO);
else $pessoa->setNome($nome);

if(!is_numeric($idade)) $mensagens[] = new Mensagem("A idade deve ser um número!",Mensagem::$ERRO);
else $pessoa->setIdade($idade);

if(count($mensagens)){
    $_SESSION['mensagens'] = serialize($mensagens);
    $_SESSION['pessoa'] = serialize($pessoa);
    header("Location: form.php");
}

if(cadastra no banco){
    $mensagens[] = new Mensagem("Pessoa cadastrada com sucesso!",Mensagem::$SUCESSO);
} else {
    $mensagens[] = new Mensagem("Ocorreu um erro durante o cadastramento da pessoa!",Mensagem::$ERRO);
}

header("Location: form.php");

?>

No código acima, pegamos os dados do formulário através do POST e verificamos se os dados validam de acordo com algumas regras, caso contrário colocamos, em um array de mensagens, uma mensagem de erro. Depois de feita a validação, verificamos se existem mensagens de erro, caso positivo, serializamos as mensagens e a pessoa que possui alguns dados já cadastrados e colocamos na SESSION, fazendo o redirecionamento para o formulário, de forma que o usuário possa retificar seus erros.

Caso tudo ocorra dentro do normal, armazenamos uma mensagem de sucesso na SESSION, e redirecionamos para o formulário, para que o usuário efetue novo cadastro. Poderia redirecionar para a lista de cadastrados, ou qualquer outro lugar, contanto que o script tenha os códigos que lessem a mensagem de sucesso, mas para simplificar o meu post, eu criei apenas duas páginas.

Espero que isso ajude, e qualquer dúvida, comenta.

Tags: ,