I have a pretty similar technique, but I've implemented it in a way that
requires very little work if you're using Zend_Db_Table as your data access
layer.  There are two relevant interfaces:

   1. Galahad_Model_DaoInterface which defines what a data access object
   should look like (things like save, fetchAll and fetchByPrimary).
   2. Galahad_Model_ConstraintInterface which mirrors the
   Zend_Db_Table_Select API.

My data mapper objects have a constraint() method (or getConstraint() if you
prefer—I like "constraint" because it leads to a more fluent usage) which
just calls the loaded data access object's constraint() method.  That way
each DAO can implement its own constraint system using a common interface.
 The beauty is that because the constraint interface mirrors the
Zend_Db_Table_Select API, and the DAO interface is pretty close to the
Zend_Db_Table API, I don't have to implement much to have everything working
with a relational database.  If I ever need to switch to a web service or
some other data persistence method it will mean that I'll have to implement
a somewhat complex constraint class, but the API will be exactly the same
from the consuming code's point of view.

Some example code to demonstrate:

// In Controller
$mapper = new Application_Model_Mapper_Article();
$constraint = $mapper->constraint()->where('category_id = ?',
$category->getId());
$articles = $mapper->fetchAll($constraint);

In that example, my DAO might look something like:

class Application_Model_DbTable_Article
extends Zend_Db_Table
 implements Galahad_Model_DaoInterface
{
 public function constraint()
{
 return new Galahad_Model_DbTable_Constraint();
}
}

And my constraint looks something like:

class Galahad_Model_DbTable_Constraint
 extends Zend_Db_Table_Select
implements Galahad_Model_ConstraintInterface
{}

As you can see, there's virtually no code in these classes because they are
essentially Zend_Db_Table and Zend_Db_Table_Select objects, but my consuming
code doesn't need to know that.  All it knows is that it wants a list of
articles with a certain category ID.

The downside of this is that if I ever do switch DAO's I'll probably have to
write a lot of code to properly mimic Zend_Db_Table_Select's API, but I'm OK
with that because it means that for 95% of my projects, where I am using
Zend_Db, I'll be up to speed with practically no work.

I have a working implementation of this system if anyone's interested.  I
just recently moved a bunch of things around and haven't yet pushed those
changes to GitHub, but I will shortly and would be happy to share the link
with anyone who's interested.

Cheers,

  <http://cmorrell.com/> *Chris Morrell*  Web: http://cmorrell.com  Twitter:
@inxilpro <http://twitter.com/inxilpro>


On Thu, Apr 15, 2010 at 11:59 AM, Hector Virgen <djvir...@gmail.com> 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.
>
> 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.
>
> 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);
> }
>
> The goal behind this design is to make the controllers completely unaware
> that a database even exists and makes it easy to stub in mappers for unit
> tests:
>
> // in controller
> public function indexAction()
> {
>     // check for injected mappers (provided by unit tests)
>     // this logic could be moved to an action helper
>     if (!$articlesMapper = $this->getInvokeArg('articles_mapper')) {
>         $articlesMapper = new Default_Model_Mapper_Articles();
>     }
>
>     $this->view->articles = $articlesMapper->fetchAll();
> }
>
> 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.
>
> Another benefit was that I was able to quickly build up some new mappers to
> benchmark the performance if we switched to a NoSQL database like MongoDB. I
> branched my project and rewrote some mappers to use MongoDB and I was able
> to compare the app side by side with the MySQL version within just a few
> hours.
>
> --
> Hector
>
>
>
> On Thu, Apr 15, 2010 at 7:31 AM, keith Pope <mute.p...@googlemail.com>wrote:
>
>> On 15 April 2010 14:48, Matthew Weier O'Phinney <matt...@zend.com> wrote:
>> > -- Ralf Eggert <r.egg...@travello.de> wrote
>> > (on Thursday, 15 April 2010, 02:10 PM +0200):
>> >> 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 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?
>> >
>> > I typically think of this as a job for the mapper, as it's mapping the
>> > data source to objects. That gives you the flexibility of either
>> > wrapping it in your service layer (I do this so I can return a paginator
>> > object) or simply calling it as a method off your mapper.
>>
>> I have started to use the repository pattern lately this seems to work
>> well, so you have:
>>
>> - dbTable
>> - mapper
>> - model
>> - repository
>> - service
>>
>> So now for highly specialized queries I would have them inside the
>> repository, not sure about
>> fetchBlueArticlesOrderedByGermanyPaidLastMonth() but I would have a
>> pretty descriptive method name :) I have implemented a criteria object
>> so the rules can be easily added at runtime, so maybe something like
>> getOrderedArticlesForMonth(). Using the repo you can then do something
>> like:
>>
>> $articleOrderRepo = new Kp_Model_Repository_ArticleOrders();
>> $articleOrderRepo->addCriteria(new
>> Kp_Domain_Repository_Criteria('color', '= ?', 'blue'));
>> $articleOrderRepo->addCriteria(new
>> Kp_Domain_Repository_Criteria('country', '= ?', 'Germany'));
>> $articleOrderRepo->getOrderedArticlesForMonth(10);
>>
>> The only thing here is that the criteria/repository are fairly one
>> dimensional so complex joins would need to be placed inside the mapper
>> as its hard to accurately generate the correct joins. Also sometimes
>> for large queries, for example when doing a export of all order for a
>> year, I have a getRaw type method on the repository, this will then
>> return data instead of objects to stop memory errors.
>>
>> >
>> > --
>> > Matthew Weier O'Phinney
>> > Project Lead            | matt...@zend.com
>> > Zend Framework          | http://framework.zend.com/
>> > PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>> >
>>
>>
>>
>> --
>> ------------
>> http://www.thepopeisdead.com
>>
>
>

Reply via email to