10. Aplicações em camadas
10.1. Introduction

Vamos descobrir agora como estruturar uma aplicação PHP em camadas:

Nesse esquema, é a camada da esquerda que toma a iniciativa de utilizar a camada da direita. O papel das camadas é o seguinte:
- [1]: a camada denominada [dao] (Data Access Objects) é responsável pelas interações com repositórios de dados externos [4] (arquivos, bancos de dados, serviços web…). Essa camada é, por vezes, chamada de [DAL] (Camada de Acesso a Dados), um termo que descreve melhor a função da camada. Essa camada pode ler dados [3] ou gravar dados [2]. Ela é acionada pela camada [métier] [6] e retorna resultados para ela [7];
- [5]: uma camada denominada [métier] que reúne procedimentos “de negócio” aos quais são fornecidos todos os dados de que necessitam. Geralmente, essa é a camada mais estável de um projeto, pois não depende da forma como os dados são obtidos. Esses dados têm duas fontes:
- [9]: os dados fornecidos pelo script PHP;
- [6,7]: os dados solicitados à camada [dao].
- [8]: o script principal é o coordenador. Em um aplicativo de console, ele irá:
- criar as camadas [métier] e [dao];
- executar o algoritmo do aplicativo. Esse algoritmo funciona como o de um maestro: não contém nenhum código de “negócio” nem de acesso aos dados. O script principal limita-se a chamar os procedimentos das camadas [métier] e [9]. Ele ignora totalmente a camada [dao] e os dados externos. Ele pode fornecer à camada [métier] dados da camada [9]. Em um aplicativo de console, esses dados podem provir de arquivos de configuração ou do usuário do script. Ele recebe resultados [10] da camada [métier]. Ele pode precisar armazenar alguns resultados: para isso, utiliza novamente os procedimentos da camada [métier], que, por sua vez, se dirigem à camada [dao], responsável pela tarefa;
- entre os resultados, o script principal pode receber exceções. É sua função gerenciar as exceções que são reportadas por todas as camadas;
Essa divisão em camadas tem como objetivo facilitar a evolução da aplicação. Para isso, cada camada implementa uma interface. Suponhamos que:
- a camada [dao] seja implementada por uma classe [Dao], que implementa uma interface [IDao];
- a camada [métier] é implementada por uma classe [Métier], que implementa uma interface [IMétier];
Os procedimentos da camada [métier] passarão, então, a utilizar a interface [IDao] em vez da classe [Dao]. Isso permite que a camada [Dao] seja atualizada sem afetar a camada [Métier]. Suponhamos que, na versão 1, a camada [Dao1] utilize dados de um banco de dados. Após uma atualização, esses dados passam a ser fornecidos por um serviço web na versão 2, [Dao2]. Deve-se garantir que as duas classes [Dao1] e [Dao2] implementem a mesma interface [IDao] e que lancem as mesmas exceções. Se isso for verificado, a camada [Métier], que trabalha com a interface [IDao], permanecerá inalterada;
O mesmo raciocínio se aplica à camada [Métier].
Vejamos uma implementação com classes e interfaces:

O aplicativo implementará a seguinte estrutura em camadas:

10.2. Objetos trocados entre camadas
Normalmente, as camadas trocam diversos objetos. Neste caso, elas trocarão a seguinte classe [Personne.php]:
<?php
// uma pessoa
class Personne {
// identificador
private $id;
// construtor
public function __construct(int $id) {
$this->id = $id;
}
// toString
public function __toString(): string {
return "[Personne($this->id)]";
}
}
Comentários
- linha 6: a pessoa possui apenas um atributo, seu identificador [$id]. Vamos supor que ele identifique uma única pessoa em um data warehouse;
- linhas 9-11: o construtor que permite criar um tipo [Personne] com seu identificador;
- linhas 14-16: o método [__toString] que exibe o identificador da pessoa;
10.3. Camada [dao]
A interface [IDao] implementada pela camada [dao, 1] é a seguinte [IDao.php]:
<?php
// camada [dao] ------------------------
interface IDao {
// retirada de uma pessoa em um depósito externo
// fornecimento do identificador da pessoa
public function get(int $id): Personne;
// armazenamento de uma pessoa em um depósito externo
public function save(Personne $p): void;
}
Essa interface é implementada pela seguinte classe [Dao1]: [Dao1.php]:
<?php
class Dao1 implements IDao {
// salvamento de uma pessoa em um depósito externo
public function save(Personne $p): void {
print "[Dao1] : Sauvegarde de la personne $p en base de données [locale]\n";
}
// recuperação de uma pessoa em um depósito externo
public function get(int $id): Personne {
print "[Dao1] : Récupération de la personne d'identité ($id) en base de données [locale]\n";
return new Personne($id);
}
}
Comentários
- a classe não interage com um data warehouse. Limitamo-nos a exibir mensagens para acompanhar a execução do código (linhas 7 e 12);
- linha 13: retorna corretamente uma pessoa com o identificador passado como parâmetro para o método (linha 11);
Também implementamos a interface [IDao] com a seguinte classe [Dao2] [Dao2.php]:
<?php
class Dao2 implements IDao {
// armazenamento de uma pessoa em um depósito externo
public function save(Personne $p): void {
print "[Dao2] : Sauvegarde de la personne $p en base de données [distante]\n";
}
// recuperação de uma pessoa em um armazém externo
public function get(int $id): Personne {
print "[Dao2] : Récupération de la personne d'identité ($id) en base de données [distante]\n";
return new Personne($id);
}
}
A classe [Dao2] é semelhante à classe [Dao1], exceto que modificamos as mensagens exibidas.
10.4. Camada [métier]
A camada [métier, 2] oferece a seguinte interface [IMétier] [IMetier.php]:
<?php
// camada [métier] ------------------------
interface IMétier extends IDao {
// processamento de uma pessoa identificada por seu ID
public function doSomething(Personne $p): void;
}
Comentários
- A interface [IMétier] estende a interface [IDao]. Isso não é de forma alguma obrigatório. Fazemos isso aqui porque o exemplo é simples;
- linha 7: o método [doSomething] é específico da camada [Métier];
A interface [IMétier] será implementada pela seguinte classe [Métier]: [Métier.php]:
<?php
class Métier implements IMétier {
// camada [dao]
private $dao;
// getter/setter
public function setDao(IDao $dao): void {
$this->dao = $dao;
}
public function getDao(): IDao {
return $this->dao;
}
// explora-se uma pessoa
public function doSomething(Personne $p): void {
// processamento
print "Métier : Traitement métier de la personne $p\n";
}
// salvar a pessoa
public function save(Personne $p): void {
print "[Métier] : Sauvegarde de la personne $p\n";
// solicita-se à camada [dao] que faça o salvamento
$this->dao->save($p);
}
public function get(int $id): Personne {
print "[Métier] : récupération de la personne d'identifiant $id\n";
// solicita-se a pessoa à camada [dao]
$personne = $this->dao->get($id);
return $personne;
}
}
Comentários
- linha 5: a camada [métier] deve ter uma referência à camada [dao] para poder utilizar os métodos desta última;
- linhas 8-10: o método [setDao] permite atribuir à camada [métier] a referência da camada [dao]. Observe-se que o tipo do parâmetro é [IDao]. Assim, a camada [métier] poderá trabalhar com qualquer classe que implemente a interface [IDao]. Se passarmos de uma camada [Dao1] para uma camada [Dao2] e ambas implementarem a interface [IDao], a camada [métier] não precisa ser reescrita;
- linhas 17-20: implementação de [IMetier::doSomething];
- linhas 23-27: implementação de [IMetier::save]. Esse método deve salvar um registro de pessoa em um data warehouse. A camada [métier] não sabe fazer isso. Ela recorre à camada [dao] para realizar esse salvamento;
- linhas 29-34: implementação de [IMetier::get]. Esse método deve recuperar, de um data warehouse, o registro de pessoa cujo identificador lhe é passado como parâmetro. A camada [métier] não sabe fazer isso. Ela recorre à camada [dao] para realizar a tarefa (linha 32);
Conclusão
Sempre que a camada [métier] precisar acessar os dados armazenados no data warehouse, ela deverá passar pela camada [dao], que foi criada para acessar esses dados.
10.5. Script principal
Vamos escrever dois scripts, que atuam como coordenadores dessa aplicação. O primeiro, [main1.php], utilizará a camada [Dao1], enquanto o segundo, [main2.php], utilizará a camada [Dao2]. Queremos demonstrar que isso não afeta de forma alguma o código da camada [Métier].
O script [main1.php] é o seguinte:
<?php
// respeito estrito aos parâmetros das funções
declare (strict_types=1);
// inclusão de classes e interfaces
require_once __DIR__."/Personne.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/IMetier.php";
require_once __DIR__."/Dao1.php";
require_once __DIR__."/Métier.php";
// teste ----------------
// criação das camadas
$dao1 = new Dao1();
$métier = new Métier();
$métier->setDao($dao1);
// uso da camada [métier]
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
Comentários
- Vamos relembrar alguns pontos: o script [main1.php] é o “maestro” do aplicativo. Ele cria a estrutura em camadas do aplicativo (linhas 15-17) e, em seguida, começa a interagir com a camada [métier] (linhas 19-21). A estrutura em camadas é, de fato, a seguinte:

De acordo com esse esquema, o script [main1.php] deve se comunicar apenas com a camada [métier]. Ele não deve se comunicar com a camada [dao], mesmo que isso seja teoricamente possível.
Os resultados da execução são os seguintes:
[Métier] : récupération de la personne d'identifiant 4
[Dao1] : Récupération de la personne d'identité (4) en base de données [locale]
[Métier] : Traitement métier de la personne [Personne(4)]
[Métier] : Sauvegarde de la personne [Personne(4)]
[Dao1] : Sauvegarde de la personne [Personne(4)] en base de données [locale]
Comentários
- a linha 10 do código provocou a gravação das linhas 1 e 2 dos resultados;
- a linha 20 do código provocou a gravação da linha 3 dos resultados;
- a linha 21 do código fez com que as linhas 4 e 5 dos resultados fossem gravadas;
O script [main2.php] é o seguinte:
<?php
// respeito estrito aos parâmetros das funções
declare (strict_types=1);
// inclusão de classes e interfaces
require_once __DIR__."/Personne.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/IMetier.php";
require_once __DIR__."/Dao2.php";
require_once __DIR__."/Métier.php";
// teste ----------------
// criação das camadas
$dao2 = new Dao2();
$métier = new Métier();
$métier->setDao($dao2);
// uso da camada [métier]
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
Comentários
- linhas 36-38: a estrutura em camadas agora utiliza a camada [Dao2];
Os resultados da execução são os seguintes:
[Métier] : récupération de la personne d'identifiant 4
[Dao2] : Récupération de la personne d'identité (4) en base de données [distante]
[Métier] : Traitement métier de la personne [Personne(4)]
[Métier] : Sauvegarde de la personne [Personne(4)]
[Dao2] : Sauvegarde de la personne [Personne(4)] en base de données [distante]
A camada [Dao1] simula um acesso a um banco de dados local, enquanto a camada [Dao2] simula um acesso a um banco de dados remoto. Desde que essas duas camadas respeitem a interface [IDao], percebe-se que o código da camada [Métier] não precisou ser alterado.
Vamos aplicar o que acabamos de aprender ao exercício de cálculo de impostos, que nos serve de fio condutor.