It feels like Layer 3, the Database Request layer needs this. First, to be honest, the layering could use fixing. If you have "layers" and the question "which layer does this go in?" is not easily answered then you either don't have enough layers, or don't have the right layers.
What you have:
Database layer
Database connection layer
Database Request Layer
(this is your problem) Database Model Layer
(and so is this) Model Layer
Layer 3, the Database Request Layer is where your Query Builder belongs. Nothing outside of that layer should be doing ad hoc queries. Really, Layer 3 is more commonly referred to as the Data Access Layer (DAL) or Repository Layer. This is the layer that knows about the database. It should even know about the factories required to generated your entity objects.
Think of the DAL or Repository as a coordinating layer between two sub layers: The Database Request and Entity Factory layers.
What you really need:
+----------+
| Database |
+----------+
/\
||
||
\/
+----------------------------+ +---------------+ +----------------+ +--------+
| Database Request (Gateway) | | Query Builder | | Entity Factory | | Entity |
+----------------------------+ +---------------+ +----------------+ +--------+
/\ /\ /\ /\
|| || || ||
|| || || ||
|| || ++===============++ ||
|| || || ||
|| || || ||
|| \/ \/ ||
|| +------------+ ||
++==================> | Repository | <=============================++
+------------+
/\
||
||
\/
+------------------+
| Your Application |
+------------------+
Your entity classes should have absolutely no knowledge about persistence. It's the Repository/DAL that coordinates between multiple layers underneath - including your query builder.
Nothing outside the Repository/DAL should have anything to do with the query builder. This is a utility for making the construction of SQL queries easier. Instead, you can provide a Data Transfer Object (DTO) that will contain all the possible criteria values if you need to perform a search. The Repository/DAL should have a method that accepts this criteria DTO as an argument, and then uses the DTO to build a query using the Query Builder.
From that, something else has to translate the query builder object into a request to the database. You can create another class called a Query Executor which translates the Query Builder and creates a DB request, or just pass the query builder directly to the DB request object. Yeah you "break the separation of layers" but how much coupling have you really introduced? It's up to you.
Once your application reaches the complexity where you have a question like this, you need to invest your time in learning and configuring a good Object Relational Mapper (ORM).
So I Inject those repository classes to my Application classes?
Yes, but generally only your controllers and service classes will need access to repository objects.
Are those repository classes manually created in the controller (or the DI-Container respectively)?
A little be "Yes" and a little bit "No".
If you use Dependency Injection then the DI container or a factory object that churns out controllers will instantiate the repository.
If you introduce interfaces for your repository, you can have both:
interface IFooRepository
{
public function find($id);
}
class FooRepository implements IFooRepository
{
}
class FoosController
{
public function __construct(IFooRepository $repository = null) {
if (!isset($repository)) {
$repository = new FooRepository();
}
$this->repository = $repository;
}
}
The constructor for the controller accepts an IFooRepository
as an argument. If null
it defaults to the FooRepository
concrete implementation. Now, you can actually unit test your controllers by providing a mock or stub of the IFooRepository
interface.