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