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 >
