Table of Contents
14. Factories — When Object Creation Deserves a Home
By now, we’ve seen how our domain is shaped:
- Value Objects carry meaning.
- Entities carry identity.
- Services coordinate behavior between objects.
- Repositories store and retrieve entities.
But there’s one more question that appears naturally as our code grows:
- Where should
object 'creation'
live?
At first, it seems obvious:
“Just use the keyword new inside the constructor or service.”
But as soon as creation becomes even slightly complex —
multiple parameters, validation, defaults, dependencies, or branching logic
—
we start to feel the friction.
This is where factories come in.
A factory reveals intention.
1. What a Factory Really Is
A factory is simply:
A named place for object creation
when
new is no longer enough.
In PHP, a factory isn't a complex academic concept
—it’s just a dedicated spot for our setup logic to live.
Factories exist for the same reason Services exist:
- to keep our domain expressive
- to keep our objects small and intentional
- to keep our code readable to humans (and AI)
2. When new Is Enough
If our object is simple:
$user = new User($name, $email);
That’s fine. No factory needed.
Factories are not mandatory.
They are not “best practice”.
They are just an optional tool for when creation becomes a concept.
3. So, When does new Start to Feel Wrong?
Factory creation becomes a useful concept when:
- the constructor has too many parameters
- the parameters need validation
- the object needs defaults or derived values
- the object depends on other objects
- the creation process has a name (“register”, “publish”, “open”, “schedule”)
- we want to hide implementation details
- we want to avoid leaking Value Object construction everywhere
When we feel this friction,
a factory gives the creation logic
a home.
4. A Gentle Example
Imagine creating a User entity:
class User { public function __construct( public string $id, public string $name, public Email $email, public DateTimeImmutable $createdAt ) {} }
Creating a user now requires:
- generating an ID
- validating an email
- creating a DateTime
- assembling everything in the right order
We could do this in a controller or service:
$user = new User( Uuid::uuid4()->toString(), $name, new Email($email), new DateTimeImmutable() );
But this is noisy.
It mixes concerns.
It leaks implementation details everywhere.
A factory gives this process a name:
class UserFactory { public function register(string $name, string $email): User { return new User( Uuid::uuid4()->toString(), $name, new Email($email), new DateTimeImmutable() ); } }
And now the creation reads like a story:
$user = $userFactory->register($name, $email);
That’s the whole point.
5. Factories and Services Are Not the Same
A service performs an action in our domain:
$ticketAssignmentService->assign($ticket, $user);
A factory creates something in our domain:
$user = $userFactory->register($name, $email);
Factories don’t coordinate behavior.
They don’t store anything.
They don’t perform business rules.
They simply create objects with intention.
6. When to Use a Factory
Use a factory when:
- creation has a name
- creation has rules
- creation has steps
- creation has dependencies
- creation has meaning
If creation is trivial, skip the factory.
If creation is a concept, give it a home.
7. The Mental Model
A factory is:
- a named place for creation
- a way to hide complexity
- a way to keep constructors simple
- a way to keep services focused
- a way to make our domain read like a story
Factories help our code say what it means.
8- When to Ask AI for a Factory — A Conversation Example
Sometimes we don’t realize we need a factory until we start talking to AI.
Here’s what that moment looks like in practice.
1. We start with a simple request
“Create a User entity with name, email, and createdAt.”
AI gives us something like:
class User { public function __construct( public string $id, public string $name, public Email $email, public DateTimeImmutable $createdAt ) {} }
So far, so good.
2. Then we ask AI to show how to create one User
“Show me how to create a new User.”
AI responds with something like:
$user = new User( Uuid::uuid4()->toString(), $name, new Email($email), new DateTimeImmutable() );
And this is the moment where our judgment kicks in.
We look at that creation code and think:
- That’s a lot of steps.
- That’s a lot of noise.
- That’s not something I want repeated everywhere.
- This creation process feels like a concept.
This is the moment to ask for a factory.
3. We ask AI to give creation a home
“This creation logic is too noisy. Give me a factory that handles user registration.”
AI now produces something like:
class UserFactory { public function register(string $name, string $email): User { return new User( Uuid::uuid4()->toString(), $name, new Email($email), new DateTimeImmutable() ); } }
And suddenly the creation reads like a story:
$user = $userFactory->register($name, $email);
This is the moment when the domain becomes clearer —
not because of the code,
but because of the conversation.
Factories are about clarity.
When creation becomes a story,
give it a name.
Summary
Factories are not about abstraction — they’re about clarity. When creation becomes a concept, naming it makes our domain easier to read, easier to teach, and easier to collaborate with AI. We’re not adding structure. We’re revealing intention.
Tony de Araujo —New York
