SitePoint Sponsor

User Tag List

Results 1 to 25 of 26

Hybrid View

  1. #1
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Implementing a data mapper

    I've been thinking lately that the ActiveRecord approach to ORM I've been using isn't flexible enough, and I've been looking into other patterns. I'm aware of the ones discussed in PoEAA, but not having read the book, I'm not 100% on what the best implementation would be. Here are some ideas I have, though:

    The first is to have the mapper be contained by the model, passed as a parameter in the constructor, like this:

    PHP Code:
    <?php

    class Model {
        var 
    $mapper;
        
        function 
    Model($mapper) {
            
    $this->mapper $mapper;
            
        }

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

    class 
    Mapper {
        function 
    save($model) {
            
            
        }
        
    }

    $model = new Model(new Mapper());

    $model->save();


    ?>
    I like this approach because it achieves the decoupling of the data access code from the business logic with very little change to application code; simply the extra parameter in the constructor.

    However, the code could be even further decoupled like this:

    PHP Code:
    <?php

    class Model {
        
    }

    class 
    Mapper {
        function 
    save($model) {
            
            
        }    
        
    }

    $model = new Model();
    $mapper = new Mapper();

    $mapper->save($model);


    ?>
    However this would require rewritting a lot of the code that uses the model, and I don't currently see what advantages it has over the other approach.

    Are there any other approaches that are worth looking into? How is the Data Mapper pattern typcially implemented? And how about the Row and the Table Data Gateway patterns?

    Thanks...

  2. #2
    SitePoint Enthusiast
    Join Date
    Jul 2005
    Location
    United Kingdom
    Posts
    86
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Just wondering, I know that in general loose coupling is preferred but why exactly should the Model and Data Mapper be decoupled? In any decent separation the Data Mapper will be used only by the Model layer and personally, I'd say it's part of the Model layer in that it should be separated from the rest of the application.

    In my opinion, something along these lines would be better:
    PHP Code:
    <?php
    class Mapper {
    }

    class 
    Model {
        private 
    $data;

        function 
    save () {
            
    $mapper = new Mapper();
            
    // Code to save model...
        
    }
    }
    ?>
    This would mean that in OOD terms, the relationship would be composition whereas with your examples, a different layer creates (aggregates) the Mapper and the Model merely sends messages (association) to it.

    Try picturing it in UML, it's oh-so-useful

  3. #3
    SitePoint Guru dbevfat's Avatar
    Join Date
    Dec 2004
    Location
    ljubljana, slovenia
    Posts
    684
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    How about when you want to use a different mapper? Creating the mapper sometimes depends on some decision-making and maybe configuration, so the models shouldn't bear the responsibility of creating them.

    Besides, if you use a database abstraction layer, even the mapper has to be decoupled with it, so what you get is:
    PHP Code:
    $db = new DB();
    $mapper = new Mapper($db);
    $model = new Model($mapper);
    $model->save(); 
    which means that, unless you want the model to be tied in an application context (ie. retrieve the $db from a registry/singleton/godforbid-global), you have to extract the creation of the mapper and place it outside. Or maybe provide a factory class that does the code above and is context-aware.

  4. #4
    SitePoint Enthusiast
    Join Date
    Jul 2005
    Location
    United Kingdom
    Posts
    86
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by dbevfat
    How about when you want to use a different mapper? Creating the mapper sometimes depends on some decision-making and maybe configuration, so the models shouldn't bear the responsibility of creating them.

    Besides, if you use a database abstraction layer, even the mapper has to be decoupled with it, so what you get is:
    PHP Code:
    $db = new DB();
    $mapper = new Mapper($db);
    $model = new Model($mapper);
    $model->save(); 
    which means that, unless you want the model to be tied in an application context (ie. retrieve the $db from a registry/singleton/godforbid-global), you have to extract the creation of the mapper and place it outside. Or maybe provide a factory class that does the code above and is context-aware.
    What's wrong with the following?
    PHP Code:
    <?php
    abstract class Mapper {
    }

    class 
    MySQLMapper extends Mapper {
    }

    class 
    PgSQLMapper extends Mapper  {
    }

    class 
    DatabaseFactory {
        public static function 
    create($type) {
            switch (
    strtolower($type)) {
                case 
    'mysql': return new MySQLMapper(); break
                case 
    'pgsql': return new PgSQLMapper(); break
                
    // etc.
                
    default: trigger_error('Bad Mapper selected'E_USER_ERROR);
        }
    }

    class 
    Model {
        private 
    $data;
        private 
    $db;

        public function 
    __construct() {
            
    $this->db DatabaseFactory::create('mysql');
        }

        public function 
    save() {
        }
    }
    ?>
    This also allows different models to utilise different databases, which I find far more flexible.

    The Database Access Layer is part of the Model in any 3-tiered application (which I'm assuming is the context of this discussion). Therefore, it shouldn't be globally accessible. What the hell is a view or controller going to do with a database!?

  5. #5
    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 Lazesharp
    What the hell is a view or controller going to do with a database!?
    The view - nothing. Controllers - everything.

    Quote Originally Posted by johno
    Can you please post here some of the inflexibilities you are dealing with? Thanks.
    I can't speak for 33degrees, but a thing that keeps me from picking up one of the nice ORM-layers out there is the single-table approach they all seem to share. There is no reason why an ActiveRecord can't span several tables, and in fact it could be quite useful with multi-table inheritance.

  6. #6
    SitePoint Enthusiast
    Join Date
    Jul 2005
    Location
    United Kingdom
    Posts
    86
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    The view - nothing. Controllers - everything.
    We must have very different ideas of what a Controller does. In my opinion, a controller doesn't take the data from the database and place it in the model (and vica versa) it merely co-ordinates the creation, operation and collaboration of the Model with the View. For me, the Model is responsible for interacting with external data sources.

    Everyone is entitled to their own opinions and interpretations I suppose.

  7. #7
    SitePoint Guru dbevfat's Avatar
    Join Date
    Dec 2004
    Location
    ljubljana, slovenia
    Posts
    684
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Lazesharp
    What's wrong with the following?
    ...
    This also allows different models to utilise different databases, which I find far more flexible.
    In my opinion, at least two things are "wrong" here:

    1) you're retrieving/constructing a database from inside of the model, which couples the model with the DatabaseFactory class. This is not flexible enough, if you want to reuse the model.

    2) the model is aware of the completely irrelevant piece of data (irrelevant to the model, that is): the database type string. To provide some portability, you'd have to pass that string to the Model constructor, so _it_ could pass it down to the Mapper, so the proper DB gets constructed, right? Instead, you could just pass an existent mapper to the model (a model factory does that), which results in two good side-effects:
    - switching to another db only happens at the construction time of the models (from within the factory), not from the models themselves. It's not model's responsibility to be aware of the database in use.
    - models could reuse the mapper instance, instead of each constructing their own. While we could argue about which is better, it's definitely more resource-friendly to reuse an instance. After all, in a large application there could be thousands of models in use at some time.

    Here's what I mean:
    PHP Code:
    abstract class Mapper {}
    class 
    MysqlMapper extends Mapper {}
    class 
    PgsqlMapper extends Mapper {}

    class 
    Model
    {
      function 
    __construct(Mapper $mapper) {}
    }

    class 
    AppModelFactory
    {
      function 
    Create($ModelName)
      {
        
    // retrieve the DB type string from the application registry
        // create the proper mapper
        // create the model, based on $ModelName
        // return the model
      
    }
    }

    // Usage:
    $model AppModelFactory::Create('User');
    $model->save(); 
    The AppModelFactory is the only class tied in a context. It only provides some application-specific glue, it's lightweight and easy to port. It can cache mappers for later re-use if the same types of models are requested. The mapper and model are decoupled to some extent -- the model is not aware of what type of mapper it uses, it relies on it to be the valid one.

    The database/mapper/model switching is concentrated in these few lines of code for the whole project. The logic of selecting the db lies here.

    Also, the end-users' model creation code is lightweight and easy.

    Regards

  8. #8
    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 dbevfat
    Instead, you could just pass an existent mapper to the model (a model factory does that), which results in two good side-effects:
    Or you could let the model be ignorant about the mapper, which would be even more flexible. Eg :
    PHP Code:
    $user = new User();
    $mapper->save($user); 
    This way you could have different mappers (say two different database-connections) and move the object between the two.
    PHP Code:
    $user $mapperA->get('User'1);
    $mapperB->save($user); 
    In fact. To qualify as a DataMapper, the object has to be ignorant of the mapper. (http://www.martinfowler.com/eaaCatalog/mapper.html)

    Quote Originally Posted by Lazesharp
    We must have very different ideas of what a Controller does. In my opinion, a controller doesn't take the data from the database and place it in the model (and vica versa) it merely co-ordinates the creation, operation and collaboration of the Model with the View. For me, the Model is responsible for interacting with external data sources.
    After re-reading your post, I must admit I got it wrong. I meant that the database is the model (or part of). But you're right that when using a DataMapper, the Object-Model gets seperated from the storage (database). Using TableGateway/ActiveRecord the seperation isn't as sharp.

  9. #9
    SitePoint Enthusiast
    Join Date
    Jul 2005
    Location
    United Kingdom
    Posts
    86
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    After re-reading your post, I must admit I got it wrong. I meant that the database is the model (or part of). But you're right that when using a DataMapper, the Object-Model gets seperated from the storage (database). Using TableGateway/ActiveRecord the seperation isn't as sharp.
    Agreed, though I would still class the Model, the Mapper and the Database Abstraction as all being part of the Data Model layer. While they are loosely separated, in a 3-tier design, I'd place them firmly in the Data Model

    Heh, don't worry, I got a few things wrong two (my examples for one thing). But instead of drawing attension to that, I've instead opted to draw attention to your admission of incorrectness in the hope that no one will notice mine :P

  10. #10
    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)
    Chapter #3 from PeEAA, which is about ORM, was out as a sample chapter at some time, but I seem to have lost the link. Google should help you there.

  11. #11
    SitePoint Zealot johno's Avatar
    Join Date
    Sep 2003
    Location
    Bratislava, Slovakia
    Posts
    184
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees
    I've been thinking lately that the ActiveRecord approach to ORM I've been using isn't flexible enough, and I've been looking into other patterns. ...
    Can you please post here some of the inflexibilities you are dealing with? Thanks.
    Annotations support for PHP5
    TC/OPT™ Group Leader

  12. #12
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Wow, wasn't expecting this many replies already, thanks for that.

    To answer a few questions:

    Quote Originally Posted by johno
    Can you please post here some of the inflexibilities you are dealing with? Thanks.
    There are two main issues I'm having. The first is that, from a testing perspective, it's easier to run tests on my business logic with the persistance mechanism abstracted out; I can simple replace it with a mock mapper. The other is that I want to get away from models being neccesarily tied to a database, and make persitance optional. This would have the added benefit of being able to change persistance mechanisms at run time, loading objects from one place, and then save them to another. A good example would be an Email object. Using different types of mappers, I could; load it from an imap mailbox, keep it in the session for modifying, save it in the database, write it out to a text file, etc.

    Quote Originally Posted by kyberfabrikken
    I can't speak for 33degrees, but a thing that keeps me from picking up one of the nice ORM-layers out there is the single-table approach they all seem to share. There is no reason why an ActiveRecord can't span several tables, and in fact it could be quite useful with multi-table inheritance.
    Inheritence is an issue, but as you said, it can be implemented using ActiveRecord and is not one of the reasons why I was looking into mappers

    Quote Originally Posted by kyberfabrikken
    It seems logical that you'd only need one Mapper per class - not one per object. So rather something like
    Actually, my goal would be to have a single Mapper per persistance mechanism; I would rather avoid having to code a Mapper per Model per datasource...

  13. #13
    SitePoint Member
    Join Date
    Jul 2005
    Posts
    24
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    just a question...

    how would you structure your domain classes such that the mapper would be able to figure out how to perform the CRUD operations? would there be a standard method that would basically list all fields?

    in addition, how do you plan on going about the different finder methods if you have only data mapper.

    thanks.

  14. #14
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by gaialucien
    just a question...

    how would you structure your domain classes such that the mapper would be able to figure out how to perform the CRUD operations? would there be a standard method that would basically list all fields?
    Clearly, there's a certain amount of metadata needed for CRUD and in an ActiveRecord approach it can all be stuffed into the Model. For a mapper, I think field information would be found in the model, whereas persistence implementation specific data would be in the mapper (table name, etc). In my current ORM system, I use a schema object to store this metadata, so that it could be stored in a variety of formats (ie. simply serialised to the file system), or read directly from the database, which is handy when you're developing and your tables are changing often. I think I would probably do something similar with a DataMapper approach...

    Quote Originally Posted by gaialucien
    in addition, how do you plan on going about the different finder methods if you have only data mapper.
    There are two options I can think of offhand, either the finder methods go into the data mapper, or there's a third Finder object that takes care of this. Not being too familiar with the pattern itself, I'm not sure what the recommended approach would be.

  15. #15
    SitePoint Member
    Join Date
    Mar 2005
    Posts
    10
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm a little knew to the database mapper pattern so I have a quesiton regarding retrieving existing classes from the database. From what I can see would you do this by:

    PHP Code:
    $user $mapperA->get('User'1); 
    $mapperB->save($user); 
    so for exampe if I have an AccountMapper and an Account Object would I:

    PHP Code:
    $AccountMapper= new AccountMapper($db);
    $account$AccountMapper->getByUserNameAndPassword($user$pass);
    $account->changeParameterX('sdfsdf');
    $account->changeParameterY('sdfsdf');
    $AccountMapper->save($account); 
    Sorry if it sounds simple, I'm just not sure if I've got the write idea.

    Thanks

  16. #16
    SitePoint Enthusiast
    Join Date
    Jul 2005
    Location
    United Kingdom
    Posts
    86
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    To be honest, my example was pretty bad, in fact, i missed most of the point I was trying to convey. Let me try again.

    Lets say we have an abstraction layer:
    PHP Code:
    abstract class Database {}
    class 
    MySQLDatabase extends Database {}
    class 
    SQLiteDatabase extends Database {}
    class 
    DatabaseFactory {
        public static function 
    create($type) {
            
    // Code to return an instance of a Database class.
        
    }

    Now lets assume we have a model layer:
    PHP Code:
    abstract class Model {}
    class 
    ExampleModel extends Model {
        private 
    $data;

        public function 
    getData() { return $this->data; }
        public function 
    setData($data) { $this->data $data; }

    Now lets follow Martin Fowlers DataMapper pattern and place a model specific data mapper to map the Model to the table.
    PHP Code:
    abstract class Mapper {}
    class 
    ExampleMapper extends Mapper {
        private 
    $model;
        private 
    $db;

        public function 
    __construct($model) {
            
    $this->model $model;
            
    // Creates an instance of the DB abstraction. Therefore, Mapper composes Database
            
    $this->db = new DatabaseFactory::create('sqlite'); // The Database "type" could here be taken from a registry, passed through a parameter etc.
        
    }
        public function 
    insert() {
            
    // Utilise $this->db and $this->model to map $this->model->getData() to a table or tables
        
    }

    Now, when we look at the controller, the controller no longer has anything to do with the database and is in fact working solely with the Data Model layer.
    PHP Code:
    // Controller aggregates Model (nothing new here)
    $model = new ExampleModel();
    $model->setData('foo');
    // Controller also aggregates Mapper (this, therefore, can be considered part of the Data Model layer.
    $mapper = new ExampleMapper($model);
    $mapper->insert(); 
    I'd throw together some UML to illustrate this further, but I don't have a UML editor at work.

  17. #17
    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 Lazesharp
    I'd throw together some UML to illustrate this further, but I don't have a UML editor at work.
    With JavaWebStart you have :
    http://argouml.tigris.org/

  18. #18
    SitePoint Enthusiast
    Join Date
    Jul 2005
    Location
    United Kingdom
    Posts
    86
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Lazesharp
    I'd throw together some UML to illustrate this further, but I don't have a UML editor at work.
    Turns out Visio was installed.
    See attachment for UML of my earlier code example.
    Attached Images Attached Images

  19. #19
    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)

  20. #20
    SitePoint Zealot
    Join Date
    Jun 2004
    Location
    Netherlands
    Posts
    172
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Several of the existing frameworks have datamappers. Take a look at zend framework, ez components or symphony framework

  21. #21
    SitePoint Member
    Join Date
    Jun 2008
    Posts
    16
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by matthijsA View Post
    Several of the existing frameworks have datamappers. Take a look at zend framework, ez components or symphony framework
    I can't speak for the other two but Zend definitely does not have a data mapper. There is a proposal for one though.

  22. #22
    SitePoint Enthusiast
    Join Date
    Jul 2008
    Location
    London, UK
    Posts
    26
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I was just about to ask about ZF's data mapper! It didn't seem like the Table gateway was doing a full data mapper job but it seems pretty close though.

    Is my understanding correct to say that ZF doesn't do a data mapper because Zend_Db_Table doesn't take domain entity objects as its argument for doing inserts/update/delete. Also it returns Zend_Db_Table_Row and Zend_Db_Table_Rowset for selects instead of entity objects?

    Thanks

  23. #23
    SitePoint Enthusiast
    Join Date
    Jul 2008
    Location
    London, UK
    Posts
    26
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    just had a look at ezComponents and that doesn't seem to do data mapper either.

    Quote Originally Posted by matthijsA View Post
    Several of the existing frameworks have datamappers. Take a look at zend framework, ez components or symphony framework


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
  •