I've updated my Collection object and it is working very nicely. I can now
iterate through an entity's "child" entities very easily thanks to the lazy
loading collection, and the entity has no idea that a mapper even exists:
$quizMapper = new QuizMapper();
$quiz = $quizMapper->find(123);
foreach ($quiz->getQuestions() as $question) {
    assert($question instanceof Question); // true
}

But now I have a problem with relationships in the other direction --
specifically, the "belongs to" relationship.

Let's say I have a Question object and need to access the Quiz object that
it belongs to. Since a Question can only belong to one Quiz, how could I
achieve that without a mapper? For example I want to do this:

$questionMapper = new QuestionMapper();
$question = $questionMapper->find(456);
$quiz = $question->getQuiz();

How are these types of relationships normally handled? I was thinking of
creating a new type of class that is similar to the Collection object, but
only deals with a single entity. For example (simplified for brevity):

class My_Domain_Entity_Reference
{
    protected $_id;
    protected $_mapper;

    public function __construct($id, My_Domain_Entity_Mapper_Interface
$mapper)
    {
        $this->_id = $id;
        $this->_mapper = $mapper;
    }

    public function getReference()
    {
        return $this->_mapper->find($this->_id);
    }
}

Then, I could add this reference to my Question object in the
QuestionMapper's find() method:

class QuestionMapper
implements My_Domain_Entity_Mapper_Interface
{
    public function find($id)
    {
        $row = $this->_questionTable->find($id)->current();
        $question = new Question();
        /* ... */
        $quiz_id = $row->quiz_id;
        $quizMapper = new QuizMapper();
        $question->addReference('quiz', new
My_Domain_Entity_Reference($quiz_id, $quizMapper));
    }
}

I would then have to update the Question::getQuiz() method to pull the value
from its references.

Does this seem like a good way to approach the "belongs-to" relationship
problem? Thanks again for all your help!

--
Hector


On Sun, Aug 30, 2009 at 3:34 PM, Hector Virgen <djvir...@gmail.com> wrote:

> Thanks, Benjamin. I like your version of the Collection object better that
> mine. It seems much more flexible and supports lazy loading without the need
> to provide my Quiz object with a mapper. I'm going to update my Collection
> class with your idea and post back here if I run into any problems. Thanks
> again!
>
> --
> Hector
>
>
>
> On Sat, Aug 29, 2009 at 12:00 AM, Benjamin Eberlei <kont...@beberlei.de>wrote:
>
>> Hello,
>>
>> lazy loads are implemented by some underyling magic. Say you have a Quiz
>> with questions and you load the quiz. Instead of adding an array of all
>> the
>> questions right away, you build a collection class which implements
>> ArrayAccess, Iterator and Countable and takes a PHP Callback which is
>> fired
>> upon the first acces of any of those functions. See here:
>>
>>
>> http://framework.zend.com/svn/framework/standard/branches/user/beberlei/Zend_Entity/library/Zend/Entity/LazyLoad/Collection.php
>>
>> In your example it would look like:
>>
>> $quiz = $quizMapper->findById(1);
>>
>> // Inside QuizMapper building the quiz:
>> $questionsMapper = new QuestionsMapper();
>> $state['questions'] = new Zend_Entity_LazyLoad_Collection(
>>    array($questionsMapper, loadByQuizId), array($state['id'])
>> );
>>
>> This way you have a collection inside your quiz, its just not loaded yet
>> though. Only upon first access of this pseudo array all the data is
>> loaded.
>>
>> Implementing LazyLoad for querying is easy, there are some tricks to do it
>> right when saving the collection then if you dont want to completly load
>> it.
>>
>> greetings,
>> Benjamin
>>
>> On Saturday 29 August 2009 08:06:06 am Hector Virgen wrote:
>> > Thank you for the replies, Dmytro and Keith.
>> > I just finished reading over the Zend_Db_Mapper proposal, and it is
>> looking
>> > really nice. It seems to be similar to what I've been putting together,
>> > which makes me feel confident that I'm at least somewhat on the right
>> > track.
>> >
>> > I can agree that the entity should not know about the mapper, but
>> without
>> > it I wasn't able to figure out a way for the entity to support lazy
>> loading
>> > (where would it load from?). This was the part I struggled with the
>> most,
>> > because I wanted my entities to know about their relationships:
>> >
>> > $quiz->getOwner(); // lazy loads the owner as a User entity
>> >
>> > Unless I'm missing something, it seems important that a quiz knows about
>> > its owner if even just for saving purposes:
>> >
>> > class QuizMapper
>> > {
>> >     /* ... */
>> >     public function save(Quiz $quiz)
>> >     {
>> >         $data = array(
>> >             'title' => $quiz->title,
>> >             'owner_id' => $quiz->owner->id
>> >         );
>> >         // add code to save to persistent storage
>> >     }
>> > }
>> >
>> > I suppose I could just test if the quiz has an owner object, and save
>> its
>> > ID if it does, but then I have to account for a quiz that doesn't have
>> an
>> > owner -- would its value be set to false?
>> >
>> > I think maybe shadow data can help in this case by resorting to the
>> shadow
>> > value if owner object doesn't exist, allowing me to re-save an entity
>> > without ever having to load the owner object at all.
>> >
>> > Dmytro, regarding your question about where the collection gets its IDs,
>> it
>> > is provided by the mapper. For example, my QuizMapper may have a method
>> > that returns all quizzes owned by a user. But instead of returning an
>> array
>> > of instantiated Quiz objects, it returns a single Collection object that
>> > produces Quiz entities when iterated:
>> >
>> > class QuizMapper
>> > {
>> >     /* ... */
>> >     public function findByUser(User $user)
>> >     {
>> >         $db = $this->getDatabase()
>> >         $select = $db->select()
>> >             ->from('quizzes', array('quiz_id'))
>> >             ->where('user_id = ?', $user->id)
>> >         ;
>> >         $ids = $db->fetchCol($select);
>> >         $collection = new My_Entity_Collection();
>> >         $collection->setMapper($this);
>> >         $collection->setIds($ids);
>> >         return $collection;
>> >     }
>> > }
>> >
>> > This allows me to create specialty methods that return entities based on
>> > any criteria (like for searches, etc) and the collection can be
>> paginated
>> > with Zend_Paginator. From what I understand, the Zend_Db_Mapper proposal
>> > also has a collection that is similar to this one.
>> >
>> > What I was also thinking of doing was expanding on the Collection idea
>> and
>> > making a class that contains information on how to build a reference
>> entity
>> > using an implementation of the Value Holder type of lazy loading. All
>> this
>> > class would contain is a mapper, an ID, and a factory method to use the
>> > mapper to create the entity. Something like this:
>> >
>> > class My_Entity_Reference
>> > {
>> >     protected $_id;
>> >     protected $_mapperClass;
>> >
>> >     public function __construct($id, $mapperClass)
>> >     {
>> >         $this->_id = $id;
>> >         $this->_mapperClass = $mapperClass;
>> >         $this->_method = $method;
>> >     }
>> >
>> >     public function getValue()
>> >     {
>> >         $mapper = new $this->_mapperClass();
>> >         return $mapper->find($this->_id);
>> >     }
>> > }
>> >
>> > I could then use this reference object to give the entity a way to
>> > lazy-load a single resource without giving the entity access to a
>> mapper.
>> > Perhaps this would be a good alternative to giving the entity a mapper
>> to
>> > work with? Or should I be relying on shadow data? Maybe I could use both
>> > ideas together and use the Reference object as a shadow data by adding a
>> > getId() method. So many ways to go! :)
>> >
>> > --
>> > Hector
>> >
>> > On Fri, Aug 28, 2009 at 4:45 PM, keith Pope <mute.p...@googlemail.com
>> >wrote:
>> > > 2009/8/28 Hector Virgen <djvir...@gmail.com>:
>> > > > Thanks for the reply, Tim. So you're saying I'll need one or more
>> > > > mappers for my Quiz entity depending on how much information I want
>> to
>> > > > prefill
>> > >
>> > > the
>> > >
>> > > > quiz with? For example I'll have classes like:
>> > > >
>> > > > QuizSimpleMapper
>> > > > QuizWithQuestionsMapper
>> > > >
>> > > > Let's say we allow the users to tag their quizzes from a set of
>> global
>> > >
>> > > tags,
>> > >
>> > > > and they can use as many tags as they want. Would I then need more
>> > >
>> > > mappers?
>> > >
>> > > > QuizWithTagsMapper
>> > > > TagMapper
>> > > > TagWithQuizzesMapper
>> > > >
>> > > > Then, if I need a Quiz with its questions and its tags, do I need to
>> > >
>> > > create
>> > >
>> > > > yet another mapper?
>> > > >
>> > > > QuizWithQuestionsAndTagMapper
>> > > >
>> > > > This is becoming overwhelming. I feel like this complexity could be
>> > > > simplified if the entity had access to the mapper that created it,
>> > >
>> > > allowing
>> > >
>> > > > it to pull in more data in a lazy-loading fashion. But as you said,
>> > > > that goes against the design pattern.
>> > > > What I have been working on lately has been an "entity collection"
>> > > > object which contains a mapper instance and a list of IDs. It
>> > > > implements the SeekableIterator and Countable interfaces and uses
>> > > > lazy-loading to load
>> > >
>> > > the
>> > >
>> > > > actual entity on demand when My_Model_Entity_Collection::current()
>> is
>> > > > called. This seems to help so that objects aren't created until
>> they're
>> > > > accessed, and they can be accessed without any additional work on
>> the
>> > > > object. Any thoughts on this?
>> > > > --
>> > > > Hector
>> > >
>> > > I would say that the quickstart is only a pointer to how a mapper can
>> > > work, once you start looking at relationships that are not simple you
>> > > will need to create more and more infrastructure to handle them. The
>> > > idea of a data mapper is to help create a Domain Model, this also will
>> > > require components to manage object life cycle such as unit of work
>> > > and identity map patterns.
>> > >
>> > > I would suggest reading up on domain model, Eric Evans Domain Driven
>> > > Design is probably the best book to read on this.
>> > >
>> > > Also check out the Zend_Db_Mapper proposal, you will see from this how
>> > > involved creating a data mapper is :) The good news is this proposal
>> > > has been approved for development which is very good news for the
>> > > framework. There is code checked into the SVN for this component too,
>> > > which is worth a read through.
>> > >
>> > > > On Fri, Aug 28, 2009 at 3:32 PM, Tim Navrotskyy
>> > > >
>> > > > <dmytro.navrots...@gmail.com> wrote:
>> > > >> Hi,
>> > > >>
>> > > >> Hector Virgen wrote:
>> > > >> > 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.
>> > > >>
>> > > >> I think the data mapper pattern is not implemented right in the
>> > >
>> > > QuickStart
>> > >
>> > > >> guide which leads to series of questions like yours. Even on the
>> > > >> http://martinfowler.com/eaaCatalog/dataMapper.html reference page
>>  we
>> > >
>> > > see:
>> > > >> > A layer of Mappers (473) that moves data between objects and a
>> > >
>> > > database
>> > >
>> > > >> > while keeping them independent of each other and the mapper
>> itself.
>> > > >>
>> > > >> According to this statement the class Quiz may not depend on any
>> Data
>> > > >> Mapper, including the QuestionMapper.
>> > > >>
>> > > >> The client code (e.g. action controller) using a Data Mapper may
>> look
>> > >
>> > > like
>> > >
>> > > >> this:
>> > > >>
>> > > >> //fetching questions for some quiz
>> > > >> $questions = $questionMapper->findByQuiz($quiz);
>> > > >>
>> > > >> $questions is now a collection of objects with complete question
>> > > >> informaton
>> > > >> including the answer choices.
>> > > >>
>> > > >> The client doesn't ask the Quiz Entity to get the questions, but
>> the
>> > > >> mapper.
>> > > >> The question mapper may communicate with the AnswerChoice mapper to
>> > >
>> > > fetch
>> > >
>> > > >> the choices and populate the questions with them.
>> > > >>
>> > > >> Implementing it this way you make your Model independent of the
>> Mapper
>> > >
>> > > and
>> > >
>> > > >> therefore the storage type.
>> > > >>
>> > > >> Hector Virgen wrote:
>> > > >> > Also, since each question belongs to a quiz, should the Question
>> > >
>> > > object
>> > >
>> > > >> > contain an instance of the Quiz object it belongs to?
>> > > >>
>> > > >> I think it's acceptable if you use this backreference somewhere.
>> > > >>
>> > > >> Hector Virgen wrote:
>> > > >> > 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.
>> > > >>
>> > > >> With the Data Mapper layer decoupled from your model you may now
>> > >
>> > > implement
>> > >
>> > > >> another mapper for each use case which pulls exactly as many
>> > >
>> > > dependencies
>> > >
>> > > >> as
>> > > >> needed. You could still use techniques you mentioned like Identity
>> Map
>> > >
>> > > or
>> > >
>> > > >> Lazy Loading. Martin Fowler describes them very good in
>> > > >> http://martinfowler.com/books.html#eaa PoEAA .
>> > > >>
>> > > >> Tim.
>> > > >> --
>> > > >> View this message in context:
>> > >
>> > >
>> http://www.nabble.com/Data-Mappers-and-Relational-Modelling-tp25193848p25
>> > >198001.html
>> > >
>> > > >> Sent from the Zend Framework mailing list archive at Nabble.com.
>> > >
>> > > --
>> > > ----------------------------------------------------------------------
>> > > [MuTe]
>> > > ----------------------------------------------------------------------
>>
>>
>> --
>> Benjamin Eberlei
>> http://www.beberlei.de
>>
>
>

Reply via email to