On 4/15/2010 1:13 PM, Thomas D. wrote:
Hi,
Hector Virgen wrote:
We are taking a different approach that works well for us. When you look
at methods like these:
fetchLatestArticles()
fetchArticlesBasedOnCategory($category)
fetchBlueArticlesByOrderedByGermanyPaidLastMonth()
They all have two things in common: they return articles and they rely
on criteria.
*ACK*
I think it's more important to concentrate on your domain AND not your
data layer first.
It's GOOD to have many specialized methods if your business logic works
that way and more importantly is described that way.
This is a concept known tell don't ask.. if your business logic is
always asking for specialize queries / commands or anything really,
it should exist somewhere in your domain model.
Your controllers and application services should really just be asking
the domain to do work.
Controllers should interpret the request and pass the info to the domain
model to do work.
With that in mind, we have a single method in our mappers that can cover
all of those cases:
Default_Model_Mapper_Articles#fetchAll($criteria, $sort)
The criteria is similar to a WHERE clause in SQL, but it's not in SQL
format. It uses objects instead:
$category = new Default_Model_Category();
$category->setId(123);
$mapper = new Default_Model_Articles_Mapper();
$articles = $mapper->fetch(array(
category => $category
));
The mapper's fetch method then inspects the criteria array and maps it
to a SQL WHERE clause. This includes adding any required joins:
public function fetchAll(array $criteria = null, $sort = null)
{
$select = $this->getSelect();
if (isset($criteria['category'])) {
$select->where('category_id = ?', $criteria['category']->getId());
}
return $this->_createCollection($select, $sort);
}
The getSelect() method returns the bare bones select object for
articles, where only the "id" is returned. The _createCollection()
method takes the select object and fetches the result into a
lazy-loading iterator.
But how do you deal with other properties? For example, your articles have a
"isPublished" flag.
Now you want a list with n articles, which are published (isPublished ===
1), ordered by another property "edited_on".
When I understand your solution correctly, it would require that you add all
possible criteria in your mapper, which can be used.
Not every possible.. just queries that describe your domain... its okay
to have a generalize method for general queries, but anything complex OR
frequently requested should be a method.
[...]
There are a lot of steps in this design but it helps to ensure that at
any time you can swap out mappers or change your DB schema without
having to rewrite a single line in the controllers. I actually used this
design for one of our components and, half-way through, redesigned the
entire schema for better performance. The only changes I had to make
were in the mappers themselves.
[...]
Every time you change your DB schema, you would need to change all your
mappers, to support the changed properties.
Keith's way with the repository would require now extra changes in the model
or the mapper, because the abstraction of the query makes it generic.
Once again, that's why you have unit tests, we should not be afraid of
refactoring our domain.. this does not always turn into db schemas
changes btw.
By keeping your controllers very dumb and simple and not depending on
business specific knowledge or internal working of the domain logic, you
leave that up to the domain model, which should expose as simple an api
as possible so that internally it can alter its datasource, private
vars, etc.
Sometimes this mapping can get very complex, especially if you want to
support lots of criteria. For these cases, I create a helper class whose
only job it is to build that select query. It would be used internally
by the mapper only, transparent from the controllers.
public function fetch(array $criteria = null, $sort = null)
{
$search = new Default_Model_Mapper_Search_Articles($this);
$search->setCriteria($criteria);
$select = $search->getSelect();
return $this->_createCollection($select, $sort);
}
How does the mapper call it? Would your mapper expose a public method, which
will call this helper class?