Using the Factory Pattern in PHP and Laravel
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Using the Factory Pattern in PHP and Laravel: Finding vs. Creating Models
As developers building complex applications with frameworks like Laravel, we often gravitate toward design patterns to enforce structure and maintainability. One pattern that frequently comes up in discussions about object-oriented design is the Factory Pattern. When implementing this pattern in the context of Eloquent Models, a crucial architectural decision arises: Should a factory be responsible for finding models as well as creating them?
The short answer, from a pure Object-Oriented Programming (OOP) and Domain-Driven Design (DDD) perspective, is no. Mixing the concerns of data retrieval (finding) and object instantiation (creating) into a single Factory often violates the Single Responsibility Principle (SRP). Let's dive into why, explore the correct separation of duties, and how we can structure our Laravel applications more effectively.
The Separation of Concerns: Finding vs. Creating
In an application built on Eloquent, finding a record from the database is inherently a data access operation, while creating a new record is an object persistence operation. A good design separates these responsibilities clearly.
What the Factory Should Focus On
A Factory's primary role should be object creation. It should encapsulate the logic required to construct a specific type of object, hiding the messy details of instantiation from the client code.
If we focus solely on creation, our factory methods would look like this:
// Example Product Factory (Focusing only on creation)
class ProductFactory
{
public function create(array $data): \App\Models\Product
{
return \App\Models\Product::create($data);
}
public function createWithAttributes(array $data): \App\Models\Product
{
return \App\Models\Product::create($data);
}
}
Notice that this factory delegates the actual database interaction (the create method) to the Eloquent Model itself. This is a powerful pattern: we use the Factory for orchestration, and Eloquent handles the persistence details.
Where Does Finding Belong? The Repository Pattern
The act of "finding" data—querying the database based on criteria—is fundamentally a Data Access Object (DAO) concern. This responsibility is perfectly suited for the Repository Pattern.
Instead of having the Factory handle complex query logic, we introduce a separate layer: the Repository. Repositories abstract away the underlying persistence mechanism (whether it’s Eloquent, raw SQL, or a third-party API).
A repository would handle all data retrieval methods:
// Example Product Repository
class ProductRepository
{
public function findById(int $id): ?\App\Models\Product
{
return \App\Models\Product::find($id);
}
public function findByName(string $name): ?\App\Models\Product
{
return \App\Models\Product::where('name', $name)->first();
}
}
Putting It Together in a Laravel Context
By separating these roles, we achieve far greater flexibility. Our Factory focuses on how to build the object, and our Repository focuses on how to retrieve it. This aligns perfectly with how complex applications should be structured, especially when leveraging the robust features provided by frameworks like Laravel. We can rely on the Service Container to manage these dependencies cleanly, a concept central to effective architecture found in resources like https://laravelcompany.com.
The Refined Flow
- Client Request: The controller needs a product.
- Repository Layer: The client asks the
ProductRepositoryto find the product by ID. The repository executes Eloquent queries and returns the Model instance (or null). - Factory Layer (Optional): If the system requires complex setup or conditional creation logic before persistence, the Factory is invoked to handle the object construction.
This clear separation ensures that if you decide to switch from Eloquent to a different ORM in the future, you only need to refactor the Repository layer, leaving your factory and domain logic untouched. This adherence to SOLID principles makes the codebase significantly easier to maintain and scale.
Conclusion
In summary, avoid making your Factory responsible for both finding data and creating objects. Instead, embrace specialized roles: use Factories strictly for object instantiation logic, and implement the Repository Pattern to handle all data retrieval operations. This architectural choice leads to cleaner code, better testability, and a more robust system that scales effectively within the Laravel ecosystem.