Skip to content

10. Aplicações em camadas

10.1. Introdução

Image

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

Image

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:

Image

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

Image

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:

Image

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.