Hi Ralf,

you wrote:
> I have a question regarding a model infrastructure that uses models,
> data sources, data mappers and a service layer. I like this concept a
> lot but am still thinking about one issue.

I also like this concept. I read your (and others) discussion ([1], [2])
about that, including watching Matthew's "Play Doh - Modelling Your Objects"
webcast.


> I want to get a list of all blue articles which have been ordered by
> users from Germany and have been paid last month. Thinking about the
> database this will definitely be a rather complex database join about
> at least three tables (articles, users, order). Lets call this method
> fetchBlueArticlesOrderedByGermanyPaidLastMonth().
> 
> Where should this method be located?
> 
> - in the data source (Zend_Db_Table class)
> - in the model
> - in the data mapper
> - in the service layer
> 
> I could find pros and cons for each solution if I want to. But what do
> others think?

First of all, I won't separate data mapper and data source. When your data
mapper maps data to/from a database, why do you also need a separate data
source? That's why my data mapper extends Zend_Db_Table_Abstract (when it's
a mapper for a database source).

To your question, I won't created such a special methods. I mean an
application starts with a simple functionality of just returning the latest
X articles. You build your "fetchLatestArticles($limit)" method. What's
next? You only want articles based on certain categories. You start building
your "fetchArticlesBasedOnCategory($category)" method. Now you start
offering articles and want a list, representing the latest X orders. And
again you create a method... - what I want to show is, that I don't think it
is practical to create a method for each possible result set.

That's why I only created methods for really common result sets, like
fetchLatestArticles(). But then I create a public method
"fetchCustomQuery(Zend_Db_Table_Select $select)". This method would be
typically consumed by the service layer:

$mapper = new My_Article_Mapper();

$select = $mapper->getTable()->select();
$select->where(category = ?, $category);

$articles = $mapper->fetchCustomQuery($select);

Anyway, our examples aren't real: I guess nobody would really put orders
into an article object and everyone would have own objects for categories.
But the way you would do it, stays the same.

One thing to notice:
Using a method like "fetchCustomQuery()" would limit the ability to simply
switch the mapper. For example, if you switch from a database store to a
file store, there is currently no Zend_Db_Table_Select abstraction. But that
shouldn't be real problem: It's like the ability to change the database
adapter. Ralph wrote about that [1].
When you really want *no* dependencies, you need to create an interface for
your mappers and create methods for every possible result set, because the
service layer doesn't know anything about your source.


While you are talking about that, I also have a question:
Are you preparing your data in the mapper? Think about an article store. You
have the ID, title, content, edit timestamp and published flag. When you
store your articles in a nested set, you also have columns for left and
right values. Do you just fetch a row and pass it to your object or are you
preparing and dropping unnecessary "properties"? That's what I am doing:

[...]
protected function _factory(Zend_Db_Table_Row $row)
{
  $data = array(
    'id'          => (int) $row->id,
    'title'       => (string) $row->title,
    'content'     => (string) $row->content,
    //'edited_on'   => new Zend_Date($row->edited_on, Zend_Date::ISO_8601),
    // I moved that to my model, to support lazy loading
    'edited_on'   => (string) $row->edited_on,
    'isPublished' => (int) $row->isPublished
  );

  $article = new My_Model_Article();
  $article->load($data);

  return $article;
}

public function fetchLatestArticles($limit = 10)
{
  if (!is_integer($limit))
  {
    $message = sprintf('Integer expected, but %s was given!',
getType($limit));
    throw new InvalidArgumentException($message);
  }

  $select = $this->select();
  $select->where('isPublished', 1, Zend_Db::PARAM_INT)
         ->order('edited_on DESC')
         ->limit($limit);

  $resultSet = $this->fetchAll($select);
  $pages = array();
  foreach ($resultSet AS $row)
  {
    $pages[] = $this->_factory($row);
  }
  unset($resultSet, $row);

  return $pages;
}
[...]


And another question:
My model's load method won't trigger validation. Do you validate loaded
data?



See also:
=========
[1] http://n4.nabble.com/Best-practice-for-validation-td649931.html
[2]
http://n4.nabble.com/Best-practice-for-forms-models-validators-and-filters-t
d655893.html
[1]
http://ralphschindler.com/2009/07/15/database-abstraction-layers-must-live


-- 
Regards,
Thomas


Reply via email to