10. Layered Applications
10.1. Introduction

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

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:

The application will implement the following layered structure:

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:

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.