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