Skip to content

10. Aplicações em camadas

10.1. Introduction

Image

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

Image

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:

Image

O aplicativo implementará a seguinte estrutura em camadas:

Image

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:

Image

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.