Hello,
Sorry for the long post. I have been having trouble lately with the data
mapper pattern and modelling relationships. I'm hoping to get some pointers
from the community.
The example in the Zend Framework Quick Start [1] involves only a single
model and a single mapper, which is fine -- the pattern works beautifully in
isolation. But in my application I have many models each with their own
mappers, and the models are related to each in one-to-one, one-to-many, and
many-to-many relationships.
Here's an example. Let's say I'm building a quiz application and have 3
models:
- Quiz - represents a group of questions
- Question - has a question and multiple answer choices
- AnswerChoice - represents a single answer choice
I also have 3 mappers, one for each model:
- QuizMapper
- QuestionMapper
- AnswerChoiceMapper
A quiz can have multiple questions, so my first question is: should the Quiz
object be able to fetch its own Question objects?
class Quiz
{
/* ... */
public function getQuestions()
{
$questionMapper = new QuestionMapper(); // Should a model know about
other mappers?
return $questionMapper->findByQuiz($this);
}
}
Also, since each question belongs to a quiz, should the Question object
contain an instance of the Quiz object it belongs to?
class QuestionMapper
{
/* ... */
public function find($id)
{
$row = $this->_questionsTable->find($id)->current();
$quizMapper = new QuizMapper();
$data = array(
'id' => $row->question_id,
'question' => $row->question,
'quiz' => $quizMapper->find($row->quiz_id)
);
return new Question($data);
}
}
If I follow this pattern, then when I "find" a single AnswerChoice object,
the AnswerChoiceMapper would load the parent Question object, which loads
the parent Quiz object, which loads the parent User object, etc. This seems
to be inefficient, especially when iterating through multiple answer
choices.
Once thing that I've done to help decrease the load is to use an Identity
Map [2] to reduce the duplication of models, but that still leads to
creating objects that I may not need.
So to get around this, I'm using lazy loading in my models to query the
mapper for the relationship on first access. Is it typical of the DataMapper
pattern to rely so heavily on lazy loading?
class Question
{
/* ... */
protected $_quiz;
/* ... */
public function getQuiz()
{
if (null === $this->_quiz)
$this->_quiz = $this->_quizMapper->findByQuestion($this);
}
return $this->_quiz;
}
}
This solution relies on the model knowing about other mappers besides its
own, which I think is a little odd.
I've considered adding "shadow data" [3] to my models that contain foreign
key values for the related entities. This would allow me to proxy to the
find() method from the model's own mapper:
class Question
{
/* ... */
protected $_quiz;
protected $_shadowData = array();
/* ... */
public function getQuiz()
{
if (null === $this->_quiz)
$this->_quiz = $this->_mapper->findQuiz($this);
}
return $this->_quiz;
}
}
class QuestionMapper
{
/* ... */
public function findQuiz(Question $question)
{
$quizId = $question->getShadowData('quiz');
return $this->getQuizMapper()->find($quizId);
}
}
This seems to blur the line a bit between models and data mappers, as the
shadow information is only useful for the specific data mapper being used.
But I suppose this is better than instantiated a mapper directly in the
model, right?
Any thoughts on these approaches? Is this a typical for handling data
mappers and relational modelling? Thanks for reading and looking forward to
hearing your responses.
*References:*
1. Zend Framework Quick Start
http://framework.zend.com/docs/quickstart/create-a-model-and-database-table
2. Identity Map
http://martinfowler.com/eaaCatalog/identityMap.html
3. Mapping Objects to Relational Databases: O/R Mapping in Detail
http://www.agiledata.org/essays/mappingObjects.html
--
Hector