-- Hector Virgen <[email protected]> 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 <[email protected]> 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 <[email protected]>
> 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
> <[email protected]
> >wrote:
> > > 2009/8/28 Hector Virgen <[email protected]>:
> > > > 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
> > > >
> > > > <[email protected]> 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 | [email protected]
Zend Framework | http://framework.zend.com/