The key here is to look at which classes should have which responsibilities, and specifically, which responsibilities are needed to make your domain function.
User Domain Object
Should be responsible for telling you about its state with respect to the useful business rules of your application (isAdmin()
, isBanned()
, isSuspended()
, getWarnLevel()
). Think of this object like an API bucket. What do you want to know about it? What useful information can it tell? Build an API that answers those questions. Be careful not to let the User tell you too much about OTHER objects in the system. That should not be its responsibility.
Cares about:
- Telling you about itself
- Managing its own state
Doesn't care about
- Whether it gets persisted
- How its made
- Any other objects (unless it's an aggregate root)
- Lots of other stuff
User Repository
A class responsible for allowing you to retrieve and persist fully formed, existing Users
. Maybe they persist only in memory during the current request. Maybe they persist in cache. Maybe they persist in MySQL. It doesn't matter. All this thing does is allow you to get users, and persist them. That is its responsibility. It can, but doesn't have to, know about the persistence mechanism. It only needs to know how to use the persistence mechanism.
(findById($id)
, findByEmail($email)
, findBanned()
, findByCriteria($Criteria)
- good candidate for the strategy or specification pattern, save($User)
, delete($User)
). Again, the key here is to build an API that satisfies the business rules of the domain. Do you have a need to find a user by email address? Then make that an explicit access point in the repository. If you don't have a need, then don't. How do you need to find Users
? Answer that with an API.
Cares about
- Giving you access to existing User objects based on any arbitrary criteria
- Invoking the persistence mechanism you've supplied it
Doesn't care about
- How exactly
User
objects are persisted - Making
User
objects - Lots of other stuff
User Factory
UserRepositories
are for handling existing User
objects, but how do you create them in the first place? With factories. These can be simple factories, that just make one type of user. Or they can be abstract factories, that make different types of users. This is up to you and what your systems needs are. Again, think in terms of what API is needed to satisfy the business rules of your domain space: (make($type, $data)
, makeAdmin($data)
, makeMember($data)
). PHP 5.6's variadic operator syntax will make this WAY cleaner to work with btw.
Cares about
- Making shiny new
Users
Doesn't care about
- The source of the data for making those shiny new
Users
- What you do with the
User
after it's made - Lots of other stuff
User Gateway/Mapper
This could be your actual persistence mechanism: it might interface directly with a relational database, make use of a Factory, and get used by the Repository. It does the actual DB fetching, and maps the data into a format that the Factory can digest. The factory shouldn't be responsible for this mapping because it shouldn't have any knowledge of the source format, only the format it needs to assemble the domain object. Thus the mapping responsibility lies with the Gateway/Mapper.
Cares about
- Where the
User
is getting persisted to/retrieved from, and how - Translating that data from persistence to something a Factory wants
- Probably cares about a Factory
Doesn't care about
- The specific storage driver (e.g. MySQL, Postgres) - this is where PDO comes in
- Making a new
User
object - Lots of other stuff
Now, admittedly this looks way more simple than it really is. How do you handle children of aggregates (e.g. the many Comments
that belong to one Post
)? At what stage do you give those to the Post
? Do you even give them to it all the time, or only when you explicitly ask for it (e.g. via a callback?) These are tough questions that I don't have the answers to, and that are partially answered by your domain needs in the first place.
ORM is a tough problem to solve. Doctrine and Eloquent are excellent, but don't strictly follow the pattern above. That's ok though. The pattern above is a guideline, not a rule. It's goal is to focus on separation of concerns and focused responsibility. It may not be necessary for your application to have all of these layers.
Concerning validation, there are two kinds of validation:
Form input validation
Domain object validation
Form validation is usually best done by a form validator class and some rules specifically defined for a given form. Usually you can define those rules in whatever form builder class you have (e.g. Form::text('first_name', array('rules' => 'required|alpha'))
). The controller should take the form validator as a dependency, but should not do the actual validation itself.
Domain object assembly validation (e.g. protecting the integrity of your model) can live either in the domain object itself via setters, or it can live in the factory. This depends entirely on how you plan to construct domain objects.
It should be noted that you should have BOTH types of validation: form validation to validate input, and domain object validation to validate data integrity when you create the object.