3

In OOP, I can populate (initialize) an object using constructor injection at object creation time, or setter injection, after creation time.

I like the idea of populating object at creation time via constructor, for object parameters that are essential to the object. And use setter methods for things that are non-essential or secondary.

And all was well.

Until, I discovered DataMapper pattern. In the pattern I

  • load up data from the database (data concern)
  • map schema colums to object parameters (mapping concern)
  • create/update/delete/return the object to its caller (object management concern)

I am quite content with DataMapper essentially being the manager for the object, thereby creating the object, loading data, and then populating that object. If all 3 of the above concerns share the same function, there is no issue as they all seem to seamlessly integrate together.

Problem

The issue arises when I start separating concerns. Namely, the mapping concern. Mapping concern should only concern itself with taking the DB schema column names and copying data from DB resource object over to the new object's data field parameter variables. For me it translates to this

  • accept a created object as input parameter
  • fill out the object from DB schema via the mapping (using setters for private vars, or directly if vars are public)
  • return the filled out object back to its caller.

I am thinking that mapping concern should not be creating the object. This means, the mapping concern cannot use constructor initialization.

Question

What can I do to use separate the mapping concern and still use constructor initialization?

Possible Work-arounds

  • Use setter initialization methods only [abandon constructor initialization even when it's desired]
  • bundle two different concerns of managing the object (namely creating it [means ability to initialize it via constructor]) and schema-to-object mapping inside the same class. [abandon SRP]

Code Example

For clarity - see comments

class ProductDataMapper
{
    public function dataConcern($id)
    {
        $sql = "SELECT model as name FROM product where id = {$id}";
        $result = db_query($sql);
        $data = db_fetch_array($result);
        return $data;
    }

    public function mappingConcern($data)
    {
        //schema to property mapping (to separate concerns)
        $parameters = array();
        $parameters['name'] = $data['schema_column_name_in_db'];

        //In order to use constructor initialization, mapping concern
        //has to be the place to *create* the product (using new)
        //my question deals with separating 'object creation' from 'object mapping'

        //init via constructor - this is the *undesirable* concern for a mapper
        $product = new Product($parameters);

        //init via setters
        foreach ($data as $key => $value) {
            $setter = 'set' . ucfirst($key);
            $product->$setter($value);
        }
        return $product;
    }

    public function getProduct($productId)
    {
        $data = $this->dataConcern($productId);
        $product = $this->mappingConcern($data);
        return $product;
    }
}

Update

I am thinking that I can separate out object creation WITH mapping into a separate concern.

That concern will be Object-Creation-and-Schema-Mapping-To-Object-via-Constructor.

Namely, still not perfect, because there will be two mapper concerns

  • mapper that creates and initializes the object via constructor
  • mapper that uses setters to populate the object
4

You are over-using SRP. Let's take a look at mappingConcern's creation-and-initialization version:

public function mappingConcern($data)
{
    $parameters = $data;

    $product = new Product($parameters);

    return $product;
}

$parameters is the same as $data, and $product is returned immediately, so it can be translated to:

public function mappingConcern($data)
{
    return new Product($data);
}

That's all your function does! Calling another function(the constructor)! Nothing else - no logic to choose the function, no preparation of arguments, no parsing of returned value, no nothing - just calling another function, passing the argument as is.

Why can't you do this directly from getProduct?

public function getProduct($productId)
{
    $data = $this->dataConcern($productId);
    return new Product($data);
}

When you do it like this, the question of constructor vs setters seems silly, right? So, even if you do need a mappingConcern function(for polymorphism or whatever), why can't it do this simple, one command?

I think your problem is that you are diving too deep when trying to separate responsibilities and concerns. SRP and SoC refer to the interface, not to the implementation(implementation has other rules for separating stuff). That is, when you describe what a function does it should only do one thing, but when you describe how it does it you can describe multiple things. What mappingConcern does is not "create an object and map the data to it" - that's the implementation. What mappingConcern does is "convert the raw $data to an object". That's one thing, and complies to SRP and SoC.

  • While I agree generally with the too much SRP, I have updated my question showing why I did this. $data contains column schema names. I want to separate my business object (Product) from knowing what the database column names are. The way I receive my data from the database is an associative PHP array with names as they are in the database. So if I change names in the database, or change DB structure, then I have to also change my Product. How would you address this? Does this need separation or is Product depending on schema changes is okay in this case? – Dennis Jul 2 '15 at 14:19
  • This doesn't change what mappingConcern does - "convert the raw $data to an object" - only how it does it. And since the "how" is allowed to do more than one thing as long as they all contribute to the "what" - that's still OK. As for tying Product to the schema - what I usually do is to annotate the object's fields with the name of the columns, and have a generic mapper look at these annotations and do the mapping. I believe that having some boilerplate mapping code that needs to be kept in sync is far worse than giving up that particular, useless decoupling... – Idan Arye Jul 2 '15 at 23:23
  • Thanks. the last sentence "...than giving up that particular, useless..." It read a bit confusing to me, but understood it as "using generic mapping (no upkeep) is better than doing the mapping yourself (extra upkeep)", correct? – Dennis Jul 6 '15 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.