-- Hector Virgen <djvir...@gmail.com> wrote
(on Tuesday, 01 September 2009, 12:02 PM -0700):
> 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):

Before you get too carried away...

ORM stuff is really, really difficult. DataMappers that deal with a
single table are fairly easy (which is part of the reason we demonstrate
the technique in the quick start), but once you start dealing with
relations (the realm of ORMs -- Object Relational Mappers), it becomes
quite complicated -- as you're starting to discover.

Benjamin Eberlei is working on a general ORM solution for ZF currently,
and it's in the incubator. Do yourself a favor and check out his work --
it's under the Zend_Entity namespace. If you have ideas on how it might
work, give him a helping hand. :)

If you need stable code, and need it now, try out Doctrine.

> 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
> 
> 
> 
> 

-- 
Matthew Weier O'Phinney
Project Lead            | matt...@zend.com
Zend Framework          | http://framework.zend.com/

Reply via email to