SitePoint Sponsor

User Tag List

Results 1 to 14 of 14

Thread: Datamapper pattern - confused

  1. #1
    SitePoint Member
    Join Date
    Feb 2010
    Posts
    13
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Question Datamapper pattern - confused

    Hi,
    I'm implementing my own interpretation of the datamapper pattern and have some questions about the pattern in general.

    Everywhere I read about the datamapper pattern I see something like this:
    Code PHP:
    <?php
    $user_mapper = new UserMapper;
    $user = $user_mapper->find(1)
    $user->username = 'UserName';
    $user_mapper->save($user);
    ?>
    First; I cannot see any reason for not making the UserMapper a static class when you send it the $user object to save? This would accomplish the same thing in fewer lines with the same functionality and probably more efficient since we don't have to instantiate a UserMapper object.. What I mean is something like this:
    Code PHP:
    <?php
    $user = UserMapper::find(1)
    $user->username = 'UserName';
    UserMapper::save($user);
    ?>

    Or what if I let the datamapper know about the current domainobject.. Wouldn't it be more convenient to just use $user_mapper->save(); and have the $user object already in the mapper? Since we already have to instantiate the UserMapper anyway..?
    Code PHP:
    <?php
    $user_mapper = new UserMapper;
    $user = $user_mapper->find(1)
    $user->username = 'UserName';
    $user_mapper->save();
    ?>


    I also see a lot of mappers with 'static' variables like relationships/properties (and their validation rules) used as regular variables with $this->_relations, when it is clearly that ALL mappers/models of that type will have the exact same relations/validations rules/properties and therefor should be static variables... or am I missing something? I feel that many PHP developers who use OOP, tries to make everything into objects just for the sake of it.. I also see alot of singleton classes that clearly is singletons just because it makes them "object oriented"..


    What I'm asking is basically if I'm implementing a datamapper pattern wrong if I use static variables for variables that doesn't change between instances of the same class/mapper, and if I'm missing something if I leave out the $mapper->save($user) part (do $mapper->save() instead) and just makes the datamapper know about the $user object I'm currently editing by setting a variable $this->_domain_object within the datamapper so I don't have to pass the $user domainobject back to the datamapper when saving..

  2. #2
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by wadebiffen View Post
    What I'm asking is basically if I'm implementing a datamapper pattern wrong if I use static variables for variables that doesn't change between instances of the same class/mapper, and if I'm missing something if I leave out the ...
    Static or instance is orthogonal to the data mapper pattern. You can implement it either way.

    Quote Originally Posted by wadebiffen View Post
    First; I cannot see any reason for not making the UserMapper a static class when you send it the $user object to save? This would accomplish the same thing in fewer lines with the same functionality and probably more efficient since we don't have to instantiate a UserMapper object
    First - Performance is a red herring. The time taken to instantiate an object is minuscule. Querying the database even once takes thousands times more resources. So please erase that thought from your head.

    The difference between static and instance is a bit of a red herring as well. What you're really comparing here is the difference in scope. A local variable (The first example) vs. a global variable (the second). In this case, you have achieved a global variable by the use of a static class. An alternative, but semantically equivalent variant is a singleton. Compare:

    Code PHP:
    <?php
    $user = UserMapper::find(1);
    $user->username = 'UserName';
    UserMapper::save($user);
    ?>

    With this:

    Code PHP:
    <?php
    $user = UserMapper::getInstance()->find(1);
    $user->username = 'UserName';
    UserMapper::getInstance()->save($user);
    ?>

    Or this variant:

    Code PHP:
    <?php
    $user = $GLOBALS['UserMapper']->find(1);
    $user->username = 'UserName';
    $GLOBALS['UserMapper']->save($user);
    ?>

    Or even this:

    Code PHP:
    <?php
    $user = UserMapper()->find(1);
    $user->username = 'UserName';
    UserMapper()->save($user);
    ?>

    The problem with a global variable - in whatever shape in comes - is that its scope is very wide. This makes it hard to reason about, and causes hidden dependencies and bad encapsulation. These are rather abstract problems, so they are hard to explain very concretely. They are real nonetheless.

    Quote Originally Posted by wadebiffen View Post
    Or what if I let the datamapper know about the current domainobject.. Wouldn't it be more convenient to just use $user_mapper->save(); and have the $user object already in the mapper? Since we already have to instantiate the UserMapper anyway..?
    Code PHP:
    <?php
    $user_mapper = new UserMapper;
    $user = $user_mapper->find(1)
    $user->username = 'UserName';
    $user_mapper->save();
    ?>
    That sounds like a very bad idea. What happens if you have to deal with more than one user object?

  3. #3
    SitePoint Member
    Join Date
    Feb 2010
    Posts
    13
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for a quick and thorough reply!

    I know I'd have to instantiate a datamapper for each domainobject if I go for the last alternative, but even if it would produce more lines of code, it would seem more logical, well atleast I think so.


    What you are basically saying is that I'm not wrong when thinking of the datamapper as a static class, since you propose 3 alternatives that are equal/very similar to using a static class?

    I think the singleton pattern may be the most correct OOP solution I'm looking for, but I think I'll have to try out different implementations and see which one gives me the most flexibility without sacrificing the right 'feel'...

  4. #4
    SitePoint Guru
    Join Date
    Jun 2006
    Posts
    638
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Question, why don't you have the Mapper functionality in the actual User object?

    PHP Code:
    <?php
    $user 
    = new User(1);
    $user->username 'UserName';
    $user->save();
    ?>
    What's bad with that approach?

  5. #5
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I tend to use what I call a Session (probably badly named) object, which contains mapping between classes, and their mappers, and deals the mapper instantiation and so on at runtime.

    Something along the lines of...

    PHP Code:
    class Session
    {
      protected 
    $mappers;

       protected function 
    getMapper($object)
       {
         
    $className get_class($object);
         while (
    $className && !isset($this->mappers[$className]))
           
    $className get_parent_class($className);
         return 
    $className $this->mappers[$className] : null;
       }

        function 
    save($object)
        {
            
    $mapper $this->getMapper($object);
            return 
    $mapper->save($object);
        }

    This frees up any need for any specific named mappers being elsewhere. Just call

    PHP Code:
    $session->save($object); 
    Simplifies alot of mapping, I believe, particularly when objects span multiple tables (for instance when dealing with inheritance)

  6. #6
    Twitter: @AnthonySterling silver trophy AnthonySterling's Avatar
    Join Date
    Apr 2008
    Location
    North-East, UK.
    Posts
    6,109
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Vali View Post
    Question, why don't you have the Mapper functionality in the actual User object?

    PHP Code:
    <?php
    $user 
    = new User(1);
    $user->username 'UserName';
    $user->save();
    ?>
    What's bad with that approach?
    Check out a post by kyberfabrikken that made sense to me.
    @AnthonySterling: I'm a PHP developer, a consultant for oopnorth.com and the organiser of @phpne, a PHP User Group covering the North-East of England.

  7. #7
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by wadebiffen View Post
    What you are basically saying is that I'm not wrong when thinking of the datamapper as a static class, since you propose 3 alternatives that are equal/very similar to using a static class?
    I tried not to pass any judgment. If you ask me, I strongly prefer local scope over global scope. So I would use neither of those three patterns. The point I was trying to make is that a static class is essentially a global variable - And each of those patterns basically do the same.

    Quote Originally Posted by wadebiffen View Post
    I think the singleton pattern may be the most correct OOP solution I'm looking for
    By which standard do you measure correctness? OOP is not about following a set of prescribed rules. It's not an old testament religion. That aside, I would never use a singleton because 1) it's a global variable (And I try to avoid those) and 2) there are better ways to create global variables in php (re. the patterns I showed above). But if it makes sense to you, then by all means go ahead.

  8. #8
    SitePoint Member
    Join Date
    Feb 2010
    Posts
    13
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm not using objective criteria when I measure correctness.. I just base it on feel, and I just don't get the feel that it is "correct" to instantiate a mapper and treat it like a static class when you have to pass the domainobject around..

    If the mapper was aware of the domainobject, it would make sense to use the mapper like it is used in every other implementation (so I could use $mapper->save()). It just feels wrong to instantiate the mapper and use the methods within the mapper as just a set of functions ($mapper->find(1), $mapper->findByName('Ole') etc.. which all just returns a domainobject without knowing about it).

    I'm not saying your wrong and I'm right, I'm just trying to make you understand how I think about it so you can convince me that I'm totally wrong with my perception of how the mapper should work..

    I've tried using a static class as the mapper, but since I'm currently limited to php 5.2, I'm starting to dislike the static class implementation.. (I'm setting up relationships by instantiating the required domainobject on demand, and the mapper to use has to be dynamically called. $mapper::find(1) would have been so nice instead of the call_user_func solution I have to use.. I'm thinking about letting it go and just do it the regular "wrong" way just to be able to use $mapper->find(1) ). And addition to that limitation (which can be overcome) I've stumbled upon the problem with self:: binding to my parent mapper.. which I don't like at all, it may actually seem like I would be better of by spending some time to get php 5.3 going on freebsd or just use a regular class.. or.. use a singleton...


    I don't think I fully understand the global variable problem by using static classes/singletons.. Can't you access a mapper globally even if it is just a regular object? You can just instantiate it wherever you need it anyway? Or are you talking about that specific "instance" which you will be able to access globally when using static classes/singletons, but will not by using regular objects?

    Either way, the mapper doesn't do anything fancy, it is basically just some functions that return something based on what you give them.. And it doesn't matter if you have instance x or instance y of the mapper since they are all exactly the same. Can it be that I'm thinking like this because my mapper currently doesn't do anything fancy, and the other mappers around actually do something useful that requires them to be regular objects ?


    I must admit that the prettiest solution (IMO) is actually using regular objects, but it seems wrong in my head..

    Thanks for your all your answers!

  9. #9
    SitePoint Guru TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    960
    Mentioned
    8 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by Vali View Post
    Question, why don't you have the Mapper functionality in the actual User object?

    PHP Code:
    <?php
    $user 
    = new User(1);
    $user->username 'UserName';
    $user->save();
    ?>
    What's bad with that approach?
    This was something I struggled to grasp at first too. The biggest problem is that really, you'd need to do:

    PHP Code:
    $user = new User($db1); 
    Which means the user object is tied to the database I/O. With $mapper->save(); you can leave the model unchanged but substitute the mapper... For example, you may want to create an audit trail when the user's password or username is updated.. but if they're just updating profile information not write to the log. With DataMapper you can have two variants, one which creates the audit trail and one which doesn't.
    The only way to do this with ActiveRecord ($user->save()) is to add parameters to the save function on a model by model basis... which gets very messy very quickly.

    edit: My current WIP implementation works like this:

    PHP Code:
    $user $this->mapper->user[123];
    $user->firstName 'Tom';
    $this->mapper->save($user); 
    By implementing ArrayAccess and Countable I treat the mapper as if it were an array with complete access to the table.

    It caches the fetched results so you can do something like:

    PHP Code:
    $this->mapper->user[123]->firstName 'Tom';
    $this->mapper->user[123]->website 'http://r.je';
    $this->mapper->user->save($this->mapper->user[123]); // OR $this->mapper->user->save(123); 
    Though it was PoC more than anything, not sure how much of a good idea it is really. I also implemented an autosave feature, which runs in a register_shutdown_function() and saves any fetched and modified users (or whatever table(s) you're dealing with) but imho, it was a nice idea but create's too many unknowns in what the code is doing.

  10. #10
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ren View Post
    I tend to use what I call a Session (probably badly named) object, which contains mapping between classes, and their mappers, and deals the mapper instantiation and so on at runtime.

    Something along the lines of...

    PHP Code:
    class Session
    {
      protected 
    $mappers;

       protected function 
    getMapper($object)
       {
         
    $className get_class($object);
         while (
    $className && !isset($this->mappers[$className]))
           
    $className get_parent_class($className);
         return 
    $className $this->mappers[$className] : null;
       }

        function 
    save($object)
        {
            
    $mapper $this->getMapper($object);
            return 
    $mapper->save($this$object);
        }

    Note the passing of $this to the mapper save method.

    This frees up any need for any specific named mappers being elsewhere. Just call

    PHP Code:
    $session->save($object); 
    Simplifies alot of mapping, I believe, particularly when objects span multiple tables (for instance when dealing with inheritance)
    Heh, I shouldn't post after 2AM, as realised that left out the key point of having the session in the first place.

    It gets passed as an argument to the various mappers, so any related objects can get persisted with $session->save($relatedObject); too. Allowing cascaded updates/deletes.

  11. #11
    SitePoint Guru TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    960
    Mentioned
    8 Post(s)
    Tagged
    2 Thread(s)
    I like that a lot, but can you load data through your "Session" object too?

  12. #12
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by TomB View Post
    I like that a lot, but can you load data through your "Session" object too?
    Yeah. Though tend to be hand written methods for specific job, as I have a dislike for dynamically generated SQL.

    Also has to deal with transactions, so end up with as want to remain ignorant of what the actually mapper does, it could perform several dml queries, just persisting one object (for instance spans multiple tables) or cascading related objects.

    PHP Code:
    $session->beginTransaction();
    try
    {
       
    $session->save($object);
       
    $session->commit();
    }
    catch (
    Exception $e)
    {
       
    $session->abort();
       throw 
    $e;


  13. #13
    SitePoint Guru
    Join Date
    Jun 2006
    Posts
    638
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by TomB View Post
    This was something I struggled to grasp at first too. The biggest problem is that really, you'd need to do:

    PHP Code:
    $user = new User($db1); 
    I see, I thought that was a problem also, but after making a ton of websites, from 3 page sites to sites dealing with billions of records, I noticed that you will ALWAYS work with just one database object.

    At first, I used to have my DB objects like you, but when optimization came, and had to move objects from one database to another, there was a TON of code to update.

    Even if you have a master/slave setup, or get data from 15 different servers depending on what object your loading, you will never need more than one DB object.

    So I made it static (singleton functionality, but with less typing / harder to unit test).
    PHP Code:
    # Var loaded from a configuration file
    $dbLink = array (
        
    'user' => '',
        
    'password' => '',
        
    'host' => '',
        
    'link' => 'default',
        
    'methods' => array('insert''update''delete'),  # This is a master server
      
    );
    DB::connect($dbLink);

    class 
    User extends abstractDTO {
      private 
    $conf = array (
          
    'dbLink' => 'default',   # What DB link to use.
          
    'cacheLink' => 'default',   # What cache link to use.
          
    'fields' => ...
          ...
        );

    Each object (ex: User) is linked to the DB link, so when it interacts with the database, it can chose the correct connection.

    The reason I ended up moving the object functionality into the actual object (witch used to be a DTO, but now it's the data mapper & DTO in one) was because maintenance was easier with the two in one.

    I worked in Java more than a year on a big project (alongside 11 big-shot developers), and we started with a very similar setup like yours here. It worked great for a few months, until the boss had a few ideas, and we had to re-factor some code. Once we started to re-factor the DTOs, we had to re-factor the mappers, to keep them clear (UserMapper deals with the User object, and so on), and that turned into a nightmare.

    Eventually, programmers stopped re-factoring the mappers, and we ended up with ListMapper that returns a list of User objects, UserMapper that could return list of UserProperties and so on. Eventually, we created a mapper wrapper, so we can call any object through it (it would find the object type and call it's mapper, returning the results), but that didn't quite work either because there are always exceptions.

    Basically, it was very hard to find out where is what, a problem that it's hard to have if your object's functionality is in that object.

  14. #14
    SitePoint Guru TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    960
    Mentioned
    8 Post(s)
    Tagged
    2 Thread(s)
    I've never found separate data sources an issue either.. and I didn't mention that as a reason that DataMapper is better although it is a reason, like you it's rarely an issue.

    You may want to rean an XML file from an external source...

    DataMapper makes this easy:

    PHP Code:
    //I'll use "User" to be consistent with previous examples:
    $user $this->mapper->userXml->findById(123);

    //The beauty of this, is that $user can now be saved to your DB with the standard mapper..

    $this->mapper->user->save($user);

    //Or the other way around to write the $user object to XML with whatever specific implementation details you like. 
    Though for me that's a very small advantage.

    What I said above is more relevant to me, being able to specifically switch mappers for certain parts of the system.

    A good example is permissions.

    For non-admins you may wish to always use "AND deleted= 0", but admins might need to see all records. By doing this in the mapper, it allows a much cleaner approach:

    PHP Code:
    $mapper $this->mapper->getMapper('blogs'); 
    //$this->mapper is a factory, which has knowledge of the currently logged in user. Either it creates "BlogMapper" and sets a flag, or it creates "AdminBlogMapper" or "BlogMapper".
    $blogs $mapper->findByDate(new DateTime('2010-02-22)); 
    This gives a consistent approach and removes the need for extra logic at the controller level.

    Of course most of the time this kind of thing is better done by filtering out the unwanted records in the view... but there are times when you do need to deal with stricter result sets throughout the application. (e.g. when you're aggregating the data. You don't want to have to loop through everything to do a simple count($results); )

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •