On Thu, Apr 15, 2010 at 10:13 AM, Thomas D. <[email protected]> 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*
>
>
> > 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".
>
I would pass in the criteria as array('published' => true). The mapper then
maps that to "WHERE isPublished = 1".
I don't support returning *n* articles in the mapper because my lazy-loading
iterator handles that for me:
$articles = $mapper->fetchAll(array('published' => true));
assert($articles instanceof Default_Model_ArticleCollection); // true
assert($articles instanceof SeekableIterator); // true
$articles->seekTo(20);
$article = $articles->current();
assert($article instanceof Default_Model_Article); // true
>
> When I understand your solution correctly, it would require that you add
> all
> possible criteria in your mapper, which can be used.
>
> > [...]
> > 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.
>
>
> > 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?
>
My mappers only exposes a few public methods:
find(int|string $id)
fetchAll($criteria, $sort);
save($article);
The search object is loaded within the mapper's fetchAll method, and is only
used by the mapper. Its usage is transparent to any objects that user the
mapper.
--
Hector