Object Oriented Programming: Data Mapping

The What

Just because you use objects doesn’t mean you are programming in an object oriented manner.  A function like this defeats the purpose:

public function init()
{
    $user = new User();
    return $user;
}

It immediate creates a dependency.  Whatever object init() is in, we know it relies on User.  It also prevents us from testing init() without User.  User needs to be in place, and it needs to work.  If init() fails, we don’t know if it’s init() or User.

So why bring this up here and now?  It’s in response to a post on Forrst (which you can’t see because it’s probably behind a login wall).  I don’t mean to pick on the person posting the original thread.  It’s simple a case of not knowing.  Anyways, the follow up response to my reply included: “I guess we could also put the database code within the User class itself”.

The basic question revolved around creating a new User object.  Here were my suggestions.

My suggestions

Actually, no, you want to keep your DB code in a different object. You want a Data Mapper instead to actually send the Model into a DB object. Basically:

$user = new User("[email protected]","password");
$mapper = new UserMapper($user, new MapperStore());
$mapper->save();

Now, you could move that into User.

$user = new User();
$user->save();

You’d have save() accept two optional parameters.

public function save(Mapper $mapper = null, MapperStore $store = null)

Mapper and MapperStore are interfaces, of course. That way a user could send along a different Mapper if need be. I’ll get to why you might want to do this in a bit. Anyways, if $mapper wasn’t set, save would Reflect upon the class name (User) and grab the appropriate mapper: UserMapper, and use that.

MapperStore could default to a DB Connection, and essentially do the same thing, reflect upon the User class, and save the User information to the Database.

So, how does this all become useful?

$card = new Creditcard();
$card->save();

Simple enough. But let’s say you want to store certain information in a different location. Rather than change your Model, or your mapper, or your Store, you do this:

$card->save(new SafeCardMapper(), new FileStore());

So, your Model remains the same, and the mapper merely strips out information that shouldn’t be passed on to the storage object, in this case a FileStore.

Of course, you can set this stuff when you construct Creditcard as well, and it’s a fair argument to make. Personally, I like keeping the stuff close to where it’s being used. It means I can also do this:

$card->save();
$card->save(new SafeCardMapper(), new FileStore());

(Of course, even that smells bad to me, and I’d want to consider chaining mappers and stores together somehow so I could just call save() once. Maybe something like:)

function chainStorage(MapperStore $store, Mapper $mapper = null);
...
$card->chainStorage(new FileStore(), new SafeCardMapper());
$card->save(); // This would save both DB and SafeCard mappers

Anyways, once again, this probably goes above and beyond what you want. =)

Retrospect

Even still,chainStorage() and save() bug me.  I was branching from the OP’s original comment.  I’d much prefer to turn this around, something along the lines of a real Storage system.

$storage = new Storage(array(new FileStore(), new SafeCardMapper()), array(new DatabaseStore(), new CardMapper()));
$storage->store($card);

In this case, Storage simply accepts Store/Mapper pairs (or we could have a addStorage() method which accept both a storage and mapper.  Options are obvious.  Point is, You simply pass along what to store, Storage loads the Store, and uses the mapper to pull in the data.  Card doesn’t know it’s being stored.  Nothing changes.  Mapper would hand Card an updated ID if needed after the update.

Anyways, this is why they invented refactoring.