Skip to content

10. Layered Applications

10.1. Introduction

Image

We will now explore how to structure a PHP application in layers:

Image

In this diagram, the left layer initiates the use of the right layer. The roles of the layers are as follows:

  • [1]: The layer called [DAO] (Data Access Objects) handles interactions with external data stores [4] (files, databases, web services, etc.). This layer is sometimes called [DAL] (Data Access Layer), a term that better describes the layer’s role. This layer can read data [3] or write data [2]. It is called upon by the [business] layer [6] and returns results to it [7];
  • [5]: a layer called [business] that contains "business" procedures, which are provided with all the data they need. This is generally the most stable layer of a project because it does not depend on how the data is acquired. The data comes from two sources:
    • [9]: data provided by the PHP script;
    • [6,7]: data requested from the [DAO] layer.
  • [8]: the main script acts as the orchestrator. In a console application, it will:
    • create the [business] and [DAO] layers;
    • execute the application’s algorithm. This algorithm acts like an orchestrator: it contains no “business” logic or data access code. The main script simply calls the procedures of the [business] layer [9]. It completely ignores the [DAO] layer and external data. It can provide data to the [business] layer [9]. In a console application, this data may come from configuration files or from the script user. It receives results [10] from the [business] layer. It may need to store certain results: to do this, it again uses the procedures of the [business] layer, which in turn will address the [DAO] layer to perform the task;
    • among the results, the main script may receive exceptions. It is its role to handle exceptions that are propagated from all layers;

This layered architecture is designed to facilitate the evolution of the application. To this end, each layer implements an interface. Suppose that:

  • the [DAO] layer is implemented by a [DAO] class that implements an [IDao] interface;
  • the [business] layer is implemented by a [Business] class that implements an [IBusiness] interface;

The procedures in the [business] layer will then use the [IDao] interface rather than the [Dao] class. This allows the [Dao] layer to be updated without affecting the [Business] layer. Suppose that in version 1, the [Dao1] layer uses data from a database. Following an update, this data is now provided by a web service in version 2, [Dao2]. We will ensure that both the [Dao1] and [Dao2] classes implement the same [IDao] interface and throw the same exceptions. If this is verified, the [Business] layer that works with the [IDao] interface will remain unchanged;

The same reasoning applies to the [Business] layer.

Let’s look at an implementation using classes and interfaces:

Image

The application will implement the following layered structure:

Image

10.2. Objects exchanged between layers

Normally, layers exchange various objects. Here, they will exchange the following class [Person.php]:


<?php

// a person
class Person {
  // identifier
  private $id;

  // constructor
  public function __construct(int $id) {
    $this->id = $id;
  }

  // toString
  public function __toString(): string {
    return "[Person($this->id)]";
  }

}

Comments

  • line 6: the Person has only one attribute, its identifier [$id]. We will assume that this identifies a unique person in a data warehouse;
  • lines 9–11: the constructor that allows us to create a [Person] object using its ID;
  • lines 14–16: the [__toString] method that displays the person’s identifier;

10.3. [DAO] Layer

The [IDao] interface implemented by the [dao, 1] layer is as follows [IDao.php]:


<?php


// [dao] layer ------------------------
interface IDao {

  // Retrieving a person from an external database
  // pass the person's ID
  public function get(int $id): Person;

  // Save a person to an external store
  public function save(Person $p): void;
}

This interface is implemented by the following [Dao1] class [Dao1.php]:


<?php

class Dao1 implements IDao {

  // Save a person to an external database
  public function save(Person $p): void {
    print "[Dao1]: Saving person $p to the [locale] database\n";
  }

  // Retrieve a person from an external repository
  public function get(int $id): Person {
    print "[Dao1]: Retrieving the person with ID ($id) from the [locale] database\n";
    return new Person($id);
  }

}

Comments

  • The class does not interact with a data warehouse. We simply display messages to track the code’s execution (lines 7 and 12);
  • Line 13: We return a person with the ID passed as a parameter to the method (line 11);

We also implement the [IDao] interface with the following [Dao2] class [Dao2.php]:


<?php

class Dao2 implements IDao {

  // Save a person to an external database
  public function save(Person $p): void {
    print "[Dao2] : Saving person $p to the [remote] database\n";
  }

  // Retrieve a person from an external repository
  public function get(int $id): Person {
    print "[Dao2]: Retrieving the person with ID ($id) from the [remote] database\n";
    return new Person($id);
  }

}

The [Dao2] class is similar to the [Dao1] class, except that we have modified the messages displayed.

10.4. [Business] Layer

The [business, 2] layer provides the following [IMetier] interface [IMetier.php]:


<?php

// [business] layer ------------------------
interface IBusiness extends IDao {

  // we use a person identified by their ID
  public function doSomething(Person $p): void;
}

Comments

  • The [IMétier] interface extends the [IDao] interface. This is not mandatory at all. We do it here because the example is simple;
  • line 7: the [doSomething] method is specific to the [Business] layer;

The [IMetier] interface will be implemented by the following [Métier] class [Métier.php]:


<?php

class Business implements IBusiness {
  // [DAO] layer
  private $dao;

  // getter / setter
  public function setDao(IDao $dao): void {
    $this->dao = $dao;
  }

  public function getDao(): IDao {
    return $this->dao;
  }

  // we use a person
  public function doSomething(Person $p): void {
    // processing
    print "Business: Business processing for person $p\n";
  }

  // Save the person
  public function save(Person $p): void {
    print "[Business Logic] : Saving person $p\n";
    // we ask the [dao] layer to perform the save
    $this->dao->save($p);
  }

  public function get(int $id): Person {
    print "[Business Logic] : Retrieving the person with ID $id\n";
    // request the person from the [dao] layer
    $person = $this->dao->get($id);
    return $person;
  }

}

Comments

  • line 5: the [business] layer must have a reference to the [DAO] layer in order to use its methods;
  • Lines 8–10: The [setDao] method allows the [business] layer to be provided with a reference to the [DAO] layer. Note that the parameter type is [IDao]. This means the [business] layer will be able to work with any class that implements the [IDao] interface. If we switch from a [Dao1] layer to a [Dao2] layer and both implement the [IDao] interface, the [business] layer does not need to be rewritten;
  • lines 17–20: implementation of [IMetier::doSomething];
  • lines 23–27: implementation of [IMetier::save]. This method must save a person to a data warehouse. The [business] layer cannot do this. It calls on the [DAO] layer to perform this save;
  • lines 29–34: implementation of [IMetier::get]. This method must retrieve from a data warehouse the person whose ID is passed to it as a parameter. The [business] layer does not know how to do this. It calls on the [DAO] layer to do the work (line 32);

Conclusion

Whenever the [business] layer needs to access data stored in the data warehouse, it must go through the [DAO] layer, which was created to access this data.

10.5. Main Script

We will write two scripts to act as the orchestrators for this application. The first [main1.php] will use the [Dao1] layer, while the second [main2.php] will use the [Dao2] layer. We want to demonstrate that this has no impact on the code in the [Business] layer.

The [main1.php] script is as follows:


<?php

// Strict adherence to function parameters
declare (strict_types=1);

// include classes and interfaces
require_once __DIR__."/Person.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/BusinessLogic.php";
require_once __DIR__."/Dao1.php";
require_once __DIR__."/Business.php";

// test ----------------
// creating the layers
$dao1 = new Dao1();
$business = new Business();
$businessModel->setDao($dao1);
// using the [business] layer
$person = $job->get(4);
$job->doSomething($person);
$job->save($person);

Comments

  • Let’s review a few points: the [main1.php] script acts as the application’s orchestrator. It creates the application’s layered structure (lines 15–17) and then begins communicating with the [business] layer (lines 19–21). The layered structure is as follows:

Image

According to this diagram, the [main1.php] script should only interact with the [business] layer. It should not interact with the [DAO] layer, even though this is theoretically possible.

The results of the execution are as follows:


[Business]: Retrieving the person with ID 4
[Dao1]: Retrieval of the person with ID (4) from the [local] database
[Business] : Business processing of the person [Person(4)]
[Business] : Saving the person [Person(4)]
[Dao1]: Saving the person [Person(4)] to the [local] database

Comments

  • Line 10 of the code caused lines 1 and 2 of the results to be written;
  • Line 20 of the code caused line 3 of the results to be written;
  • Line 21 of the code caused lines 4 and 5 of the results to be written;

The script [main2.php] is as follows:


<?php

// Strict adherence to function parameters
declare (strict_types=1);

// include classes and interfaces
require_once __DIR__."/Person.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/BusinessLogic.php";
require_once __DIR__."/Dao2.php";
require_once __DIR__."/Business.php";

// test ----------------
// creating the layers
$dao2 = new Dao2();
$business = new Business();
$businessModel->setDao($dao2);
// using the [business] layer
$person = $business = get(4);
$business = $business->doSomething($person);
$job->save($person);

Comments

  • lines 36–38: the layered architecture now uses the [Dao2] layer;

The results of the execution are as follows:


[Business] : Retrieving the person with ID 4
[Dao2]: Retrieving the person with ID (4) from the [remote] database
[Business] : Business processing of the person [Person(4)]
[Business] : Saving the person [Person(4)]
[Dao2]: Saving the person [Person(4)] to the [remote] database

The [Dao1] layer simulates access to a local database, while the [Dao2] layer simulates access to a remote database. As long as these two layers adhere to the [IDao] interface, we can see that the code in the [Business] layer did not need to be changed.

We will apply what we have just learned to the tax calculation exercise that serves as our guiding thread.