10. Aplicações em camadas
10.1. Introdução

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

Neste diagrama, a camada da esquerda inicia a utilização da camada da direita. As funções das camadas são as seguintes:
- [1]: A camada denominada [DAO] (Data Access Objects) lida com as interações com armazenamentos de dados externos [4] (ficheiros, bases de dados, serviços web, etc.). Esta camada é por vezes designada por [DAL] (Camada de Acesso a Dados), um termo que descreve melhor a função da camada. Esta camada pode ler dados [3] ou escrever dados [2]. É chamada pela camada [business] [6] e devolve-lhe os resultados [7];
- [5]: uma camada denominada [negócio] que contém procedimentos «de negócio», aos quais são fornecidos todos os dados de que necessitam. Esta é geralmente a camada mais estável de um projeto, uma vez que não depende da forma como os dados são obtidos. Os dados provêm de duas fontes:
- [9]: dados fornecidos pelo script PHP;
- [6,7]: dados solicitados à camada [DAO].
- [8]: o script principal atua como orquestrador. Numa aplicação de consola, irá:
- cria as camadas [business] e [DAO];
- executa o algoritmo da aplicação. Este algoritmo atua como um orquestrador: não contém lógica de «negócio» nem código de acesso a dados. O script principal limita-se a chamar os procedimentos da camada [negócio] [9]. Ignora completamente a camada [DAO] e os dados externos. Pode fornecer dados à camada [negócio] [9]. Numa aplicação de consola, estes dados podem provir de ficheiros de configuração ou do utilizador do script. Recebe resultados [10] da camada [business]. Pode ser necessário armazenar determinados resultados: para tal, recorre novamente aos procedimentos da camada [business], que, por sua vez, irá recorrer à camada [DAO] para executar a tarefa;
- entre os resultados, o script principal pode receber exceções. É sua função lidar com as exceções que são propagadas de todas as camadas;
Esta arquitetura em camadas foi concebida para facilitar a evolução da aplicação. Para tal, cada camada implementa uma interface. Suponha que:
- a camada [DAO] é implementada por uma classe [DAO] que implementa uma interface [IDao];
- a camada [business] é implementada por uma classe [Business] que implementa uma interface [IBusiness];
Os procedimentos na camada [business] utilizarão então a interface [IDao] em vez da classe [Dao]. Isto permite que a camada [Dao] seja atualizada sem afetar a camada [Business]. Suponha que, na versão 1, a camada [Dao1] utilize dados de uma base de dados. Após uma atualização, estes dados são agora fornecidos por um serviço web na versão 2, [Dao2]. Asseguraremos que tanto a classe [Dao1] como a [Dao2] implementam a mesma interface [IDao] e lançam as mesmas exceções. Se isto for verificado, a camada [Business] que trabalha com a interface [IDao] permanecerá inalterada;
O mesmo raciocínio aplica-se à camada [Business].
Vejamos uma implementação utilizando classes e interfaces:

A aplicação irá implementar a seguinte estrutura em camadas:

10.2. Objetos trocados entre camadas
Normalmente, as camadas trocam vários objetos. Aqui, irão trocar a seguinte classe [Person.php]:
<?php
// a person
class Personne {
// identifier
private $id;
// manufacturer
public function __construct(int $id) {
$this->id = $id;
}
// toString
public function __toString(): string {
return "[Personne($this->id)]";
}
}
Comentários
- linha 6: a Pessoa tem apenas um atributo, o seu identificador [$id]. Vamos assumir que este identifica uma pessoa única num armazém de dados;
- linhas 9–11: o construtor que nos permite criar um objeto [Person] utilizando o seu ID;
- 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
// layer [dao] ------------------------
interface IDao {
// recovery of a person from an external warehouse
// pass the person's login
public function get(int $id): Personne;
// saving a person in an external warehouse
public function save(Personne $p): void;
}
Esta interface é implementada pela seguinte classe [Dao1] [Dao1.php]:
<?php
class Dao1 implements IDao {
// saving a person in an external warehouse
public function save(Personne $p): void {
print "[Dao1] : Sauvegarde de la personne $p en base de données [locale]\n";
}
// recovery of a person from an external warehouse
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. Apenas exibimos mensagens para acompanhar a execução do código (linhas 7 e 12);
- Linha 13: Devolvemos uma pessoa com o ID passado como parâmetro para o método (linha 11);
Implementamos também a interface [IDao] com a seguinte classe [Dao2] [Dao2.php]:
<?php
class Dao2 implements IDao {
// saving a person in an external warehouse
public function save(Personne $p): void {
print "[Dao2] : Sauvegarde de la personne $p en base de données [distante]\n";
}
// recovery of a person from an external warehouse
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 modificámos as mensagens apresentadas.
10.4. Camada [Business]
A camada [business, 2] fornece a seguinte interface [IMetier] [IMetier.php]:
<?php
// business] layer ------------------------
interface IMétier extends IDao {
// we use a person identified by his id
public function doSomething(Personne $p): void;
}
Comentários
- A interface [IMétier] estende a interface [IDao]. Isto não é de todo obrigatório. Fazemo-lo aqui porque o exemplo é simples;
- linha 7: o método [doSomething] é específico da camada [Business];
A interface [IMetier] será implementada pela seguinte classe [Métier] [Métier.php]:
<?php
class Métier implements IMétier {
// layer [dao]
private $dao;
// getter / setter
public function setDao(IDao $dao): void {
$this->dao = $dao;
}
public function getDao(): IDao {
return $this->dao;
}
// a person is exploited
public function doSomething(Personne $p): void {
// treatment
print "Métier : Traitement métier de la personne $p\n";
}
// safeguarding the individual
public function save(Personne $p): void {
print "[Métier] : Sauvegarde de la personne $p\n";
// the [dao] layer is asked to make the backup
$this->dao->save($p);
}
public function get(int $id): Personne {
print "[Métier] : récupération de la personne d'identifiant $id\n";
// we ask for the person in the diaper [dao]
$personne = $this->dao->get($id);
return $personne;
}
}
Comentários
- linha 5: a camada [business] deve ter uma referência à camada [DAO] para poder utilizar os seus métodos;
- Linhas 8–10: O método [setDao] permite que a camada [business] receba uma referência à camada [DAO]. Note-se que o tipo do parâmetro é [IDao]. Isto significa que a camada [business] poderá trabalhar com qualquer classe que implemente a interface [IDao]. Se mudarmos de uma camada [Dao1] para uma camada [Dao2] e ambas implementarem a interface [IDao], a camada [business] não precisa de ser reescrita;
- linhas 17–20: implementação de [IMetier::doSomething];
- linhas 23–27: implementação de [IMetier::save]. Este método deve guardar uma pessoa num armazém de dados. A camada [business] não consegue fazer isto. Recorre à camada [DAO] para realizar este armazenamento;
- linhas 29–34: implementação de [IMetier::get]. Este método deve recuperar de um armazém de dados a pessoa cujo ID lhe é passado como parâmetro. A camada [business] não sabe como fazer isto. Recorre à camada [DAO] para realizar o trabalho (linha 32);
Conclusão
Sempre que a camada [business] precisar de aceder a dados armazenados no data warehouse, deve passar pela camada [DAO], que foi criada para aceder a esses dados.
10.5. Script principal
Iremos escrever dois scripts para atuarem como orquestradores desta aplicação. O primeiro [main1.php] utilizará a camada [Dao1], enquanto o segundo [main2.php] utilizará a camada [Dao2]. Pretendemos demonstrar que isto não tem qualquer impacto no código da camada [Business].
O script [main1.php] é o seguinte:
<?php
// strict adherence to function parameters
declare (strict_types=1);
// inclusion classes and 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";
// test ----------------
// layer creation
$dao1 = new Dao1();
$métier = new Métier();
$métier->setDao($dao1);
// using the [business] layer
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
Comentários
- Vamos rever alguns pontos: o script [main1.php] atua como o orquestrador da aplicação. Cria a estrutura em camadas da aplicação (linhas 15–17) e, em seguida, começa a comunicar com a camada [business] (linhas 19–21). A estrutura em camadas é a seguinte:

De acordo com este diagrama, o script [main1.php] deve interagir apenas com a camada [business]. Não deve interagir 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 fez com que as linhas 1 e 2 dos resultados fossem gravadas;
- A linha 20 do código fez com que a linha 3 dos resultados fosse gravada;
- 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
// strict adherence to function parameters
declare (strict_types=1);
// inclusion classes and 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";
// test ----------------
// layer creation
$dao2 = new Dao2();
$métier = new Métier();
$métier->setDao($dao2);
// using the [business] layer
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
Comentários
- linhas 36–38: a arquitetura em camadas utiliza agora 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 o acesso a uma base de dados local, enquanto a camada [Dao2] simula o acesso a uma base de dados remota. Desde que estas duas camadas cumpram a interface [IDao], podemos ver que o código na camada [Business] não precisou de ser alterado.
Vamos aplicar o que acabámos de aprender ao exercício de cálculo de impostos que nos serve de fio condutor.