O modelo de um sistema é composto por vários objetos, cada um com sua resposabilidade e comportamento, e vamos nessa série tentar abordar um por um, e o primeiro tipo de objeto que vamos ver são as entidades.
Entidades são objetos de dominio que são identificados unicamente por uma identidade. Para exemplificar, vamos considerar um sistema simples de cadastro de clientes.
Olhando este modelo é fácil de identificar que não pode existir clientes duplicados, e sendo assim, podemos considerar logo de cara que Cliente é uma entidade dentro do modelo. Para identificar cada um unicamente precisamos de uma identidade, e para isso podemos utilizar o CPF ou CNPJ, que irá nos garantir que os clientes são diferentes independente se existe algum outro com o mesmo nome, pai, mãe, etc… (acha impossivel? ), sendo assim, consideramos que dois clientes são o mesmo se e somente se a identidade dos dois forem iguais. Cada cliente pode ter um ou mais endereços. Se estes endereços forem utilizados apenas para correspondência ou coisas afins, não tem porque ter uma identidades, agora se ele for utilizado para gerar relatórios de vendas, ou coisas do gênero, é importante que tenha uma identidade bem definida.
Como implementar uma entidade?
Para exemplificar, vamos utilizar a classe de cliente pessoa fisica, que possui as seguintes caracteristicas:
- Possui o CPF como identidade, onde este campo é obrigatório; (ps. Antigamente (muito antigamente) casais podiam compartilhar o mesmo CPF, mas não vamos considerar isso neste exemplo)
- Armazena o nome separado do sobrenome, onde os dois campos são obrigatórios;
- Armazena a data do cadastro do cliente, tambem obrigatória;
- Cada cliente possui um valor de bonus onde ele pode trocar por produtos, inicializando com zero.
Implementar isso é muito simples! Basta criar uma classe que armazena todos esses valores! Vamos lá:
<?php class ClientePessoaFisica { private $cpf; private $nome; private $sobrenome; private $dataCadastro = date(); private $valorBonus = 0; // Trabalho facilitado com http://www.shuchow.com/gettersetter.html public function getCpf() { return $this->cpf; } public function getNome() { return $this->nome; } public function getSobrenome() { return $this->sobrenome; } public function getDataCadastro() { return $this->dataCadastro; } public function setCpf($x) { $this->cpf = $x; } public function setNome($x) { $this->nome = $x; } public function setSobrenome($x) { $this->sobrenome = $x; } public function setDataCadastro($x) { $this->dataCadastro = $x; } public function getValorBonus() { return $this->valorBonus; } public function setValorBonus($x) { $this->valorBonus = $x; } } ?>
Ficou com muitas linhas né? E conforme esta entidade for crescendo o numero de campos, vai aumentar ainda mais as linhas! Como o PHP é mais inteligente que isso, vamos automatizar esse processo, utilizando um dos famosos métodos mágicos, chamado __call. O funcionamento dele é muito simples: Toda vez que for invocado um método da classe que não existe, o PHP redireciona para este método informando o nome do método e os parâmetros informados. Sendo assim, podemos criar setters e getters dinâmicos, economizando tempo suficiente para tomar uma xícara de café a mais durante o dia. Veja:
<?php class ClientePessoaFisica { private $cpf; private $nome; private $sobrenome; private $dataCadastro = date(); private $valorBonus = 0; /** * Método simples, sem muita validação. * Se quiser um mais completo veja: http://cborrow.wordpress.com/2010/09/18/dynamic-getter-and-setters-in-php/ */ public function __call( $sMetodo, $aValor ) { if (preg_match('/set(.+)/A', $sMetodo, $aMatch)) { // Se for um set... $this->$aMatch[1] = $aValor[0]; } else if (preg_match('/get(.+)/A', $sMetodo, $aMatch)) { // Se for um get return $this->$aMatch[1]; } } } ?>
Muito simples, muito fácil, muito prático. Veja nossa super classe em funcionamento:
<?php $cliente = new ClientePessoaFisica(); $cliente->setNome("José"); $cliente->setValorBonus(9000); echo "Meu cliente favorito é o "+$cliente->getNome()+" por isso ele tem "+$cliente->getValorBonus()+" reais de bonus"; ?>
Opa, mas tem um problema nessa classe… no exemplo foi instanciado cliente sem CPF, e não pode existir cliente sem CPF pois este é a identidade do cliente. Também foi possível instanciar sem o sobrenome do cliente, e tudo isso torna esse cliente inválido perante ao modelo, pois está inconsistente com as regras que o definem. Para solucionar este problema, impedindo instanciar classes inconsistentes, basta criar um construtor! Veja como:
<?php class ClientePessoaFisica { private $cpf; private $nome; private $sobrenome; private $dataCadastro; private $valorBonus; public __construct($cpf, $nome, $sobrenome) { $this->cpf = $cpf; $this->nome = $nome; $this->sobrenome = $sobrenome; $this->dataCadastro = date(); $this->valorBonus = 0; } /** * Método simples, sem muita validação. * Se quiser um mais completo veja: http://cborrow.wordpress.com/2010/09/18/dynamic-getter-and-setters-in-php/ */ public function __call( $sMetodo, $aValor ) { if (preg_match('/set(.+)/A', $sMetodo, $aMatch)) { // Se for um set... $this->$aMatch[1] = $aValor[0]; } else if (preg_match('/get(.+)/A', $sMetodo, $aMatch)) { // Se for um get return $this->$aMatch[1]; } } } ?>
O Construtor impede que não seja possível cadastrar clientes sem as 3 informações obrigatórias. Ele também inicializa a classe, setando os valores iniciais para o bônus e para a data de cadastro. Veja agora como ficou a classe utilizando o construtor:
<?php $cliente = new ClientePessoaFisica("000.000.000-00", "José", "Silva"); $cliente->setValorBonus(9000); echo "Meu cliente favorito é o "+$cliente->getNome()+" por isso ele tem "+$cliente->getValorBonus()+" reais de bonus"; ?>
Mas ainda não podemos dizer que nossa entidade está completa pois, se notarem o exemplo acima, estamos instanciando um cliente informando um CPF inválido. Para corrigir isso, basta validar o CPF informado no construtor, e para completar, adicionamos as validações para que não seja informado valores nulos para o nome e o sobrenome.
<?php class ClientePessoaFisica { private $cpf; private $nome; private $sobrenome; private $dataCadastro; private $valorBonus; public __construct($cpf, $nome, $sobrenome) { Validator::validaCpf($cpf, "Somente CPF válido é permitido"); Validator::validaNotNull($nome, "Um nome deve ser informado"); Validator::validaNotNull($sobrenome, "Um sobrenome deve ser informado"); $this->cpf = $cpf; $this->nome = $nome; $this->sobrenome = $sobrenome; $this->dataCadastro = date(); $this->valorBonus = 0; } /** * Método simples, sem muita validação. * Se quiser um mais completo veja: http://cborrow.wordpress.com/2010/09/18/dynamic-getter-and-setters-in-php/ */ public function __call( $sMetodo, $aValor ) { if (preg_match('/set(.+)/A', $sMetodo, $aMatch)) { // Se for um set... $this->$aMatch[1] = $aValor[0]; } else if (preg_match('/get(.+)/A', $sMetodo, $aMatch)) { // Se for um get return $this->$aMatch[1]; } } } ?>
Agora se tentar passar um valor inválido para a entidade, será barrado pela validação e recebera uma linda e charmosa exception como consolo. Porém, se você perceber nossa classe ainda não está totalmente completa. Lembra que tentamos facilitar nossa vida utilizando o método mágico __call para ganhar uma xícara de café a mais? pois esse método abriu uma brecha na integridade da classe; Veja o exemplo:
<?php // CPF gerado em http://www.geradorcpf.com/ . Se ele for o seu, me perdoe, não foi intencional $cliente = new ClientePessoaFisica("875.618.570-71", "José", "Silva"); $cliente->setCpf("000.000.000-00"); // Setando um CPF inválido sem gerar erro $cliente->setValorBonus(9000); echo "Meu cliente favorito é o "+$cliente->getNome()+" por isso ele tem "+$cliente->getValorBonus()+" reais de bonus"; ?>
Esse é o problema de se utilizar métodos mágicos, pois ele expõe todos os valores da entidade a alteração, e isso não é desejável para alguns casos, pois, depois de instanciado um cliente, não deve ser permitido alterar o seu CPF, o nome e o sobrenome podem ser alteráveis, mas teríamos que validar se o valor que está sendo informado está correto, e isso também não é possível de fazer facilmente com essa abordagem. Mesmo se implementássemos uma configuração específica para cada campo, isso tornaria o código muito difícil de entender, e qualquer regra nova não contemplada pela configuração, ou algum calculo que possa ser executado quando setar um novo valor, teríamos um trabalhão para aplicar. Sendo assim, acredito que a melhor abordagem para a entidade é voltar ao nossos antigos e eficientes “setters” e “getters”, porém, com alterações para que a entidade se mantenha integra:
<?php class ClientePessoaFisica { private $cpf; private $nome; private $sobrenome; private $dataCadastro = date(); private $valorBonus = 0; public __construct($cpf, $nome, $sobrenome) { Validator::validaCpf($cpf, "Somente CPF válido é permitido"); $this->cpf = $cpf; $this->setNome($nome); // Centraliza a validação de nome $this->setSobrenome($sobrenome); // Centraliza a validação de sobrenome $this->dataCadastro = date(); $this->valorBonus = 0; } // public function setCpf($x) { $this->cpf = $x; } Não pode alterar o CPF! // public function setDataCadastro($x) { $this->dataCadastro = $x; } Não pode alterar a data de cadastro!! public function setNome($nome) { Validator::validaNotNull($nome, "Um nome deve ser informado"); $this->nome = $nome; } public function setSobrenome($sobrenome) { Validator::validaNotNull($sobrenome, "Um sobrenome deve ser informado"); $this->sobrenome = $sobrenome; } public function setValorBonus($valor) { Validator::notNegative($valor, "Não é permitido bônus negativo"); $this->valorBonus = $valor; } // Getters... public function getCpf() { return $this->cpf; } public function getNome() { return $this->nome; } public function getSobrenome() { return $this->sobrenome; } public function getDataCadastro() { return $this->dataCadastro; } public function getValorBonus() { return $this->valorBonus; } } ?>
Agora sim podemos dizer que nossa classe está integra e ela possui garantias de que ninguem irá quebrar sua integridade. Dá um pouco mais de trabalho para codificar, mas agora temos um código limpo, fácil de entender e alterar, e você vai poder ficar tomando seu cafézinho em vez de ficar explicando pro estagiário novo como criar e modificar uma entidade nova que utiliza o método mágico “__call”; Ou corrigindo os problemas que o estagiário por esquecer no meio do sistema um código de teste que modificouo CPF do cliente mais importante da loja.
Agora podemos vamos utilizar nossa classe:
<?php // CPF gerado em http://www.geradorcpf.com/ . Se ele for o seu, me perdoe, não foi intencional $cliente = new ClientePessoaFisica("875.618.570-71", "José", "Silva"); //$cliente->setCpf("000.000.000-00"); // Isso não é mais possível $cliente->setValorBonus(9000); echo "Meu cliente favorito é o "+$cliente->getNome()+" por isso ele tem "+$cliente->getValorBonus()+" reais de bonus"; // Utilizando uma bonificação de 200 reais $novoBonus = $cliente->getValorBonus() - 200; $cliente->setValorBonus($novoBonus); ?>
Vou finalizar por aqui este artigo, mas ja adiantando que ainda temos muito a melhorar nessa nossa entidade. No proximo item da série vou falar de coesão e acoplamento nas entidades.
Até la