1
\$\begingroup\$

I have currently implemented this simple example of a database to output for a REST API to show the way information is being parsed, mapped and serialized starting from database data converting it to JSON. This solution currently works fine, but the problem comes from the fact that it makes use of PHP tricks (like calling a function from a string) which I really do not approve of. Do you think there is a better way/pattern to implement/split this solution?

class DataMapper {
    /**
     * @var array
     */
    private $rawData;

    /**
     * @var array
     */
    private $parsedData;

    /**
     * @var array
     */
    private $mapping = [
        'dbField1' => 'outputField1',
        'dbField2' => ['outputField2' => ['double']],
        'dbField3' => ['outputField3' => ['double', 'dollars']]
    ];

    public function __construct(array $data) {
        $this->rawData = $data;
        $this->mapData();
    }

    /**
     * @param $value
     * @return mixed
     */
    private function double($value) {
        return $value * 2;
    }

    /**
     * @param $value
     * @return string
     */
    private function dollars($value) {
        return $value . "$";
    }

    // Yes, I know the methods shouldn't be here but it rather be decoupled in another class and
    // injected with a DI but I wrote it like this just as an example
    private function mapData() {
        foreach($this->mapping as $dbFieldName => $content) {

            if(isset($this->rawData[$dbFieldName])) {

                if(is_string($content)) {
                    $this->parsedData[$content] = $this->rawData[$dbFieldName];
                } else
                if (is_array($content)) {
                    $frontFieldName = key($content);
                    $functionsArray = reset($content);
                    $value = $this->rawData[$dbFieldName];

                    foreach($functionsArray as $functionName) {
                        $value = $this->{$functionName}($value);
                    }
                    $this->parsedData[$frontFieldName] = $value;
                }
            }
        }
    }

    /**
     * Simple getter
     * @return array
     */
    public function getParsedData() {
        return $this->parsedData;
    }
}

// Usually the data comes from the DB so it would be in some kind of Row Set structure
$data = ['dbField1' => 1, 'dbField2' => 1, 'dbField3' => 1];
// The data is handed to the Holder which parses it
$dm = new DataMapper($data);
// The data is parsed so it can be 
echo json_encode($dm->getParsedData());

You can experiment with it here.

I would highly appreciate it if you could give me pointers to how this could be made better programming to interfaces instead of simple strings; using composition, dependency injections, and maybe separation of the mapper functions in some abstract factories.

\$\endgroup\$
1
\$\begingroup\$

The only thing I would change is the mapping definition, e.g. separate the configuration from the actual logic so you don't have to write many mappers, but can use one with multiple configurations.

Quoting http://martinfowler.com/eaaCatalog/metadataMapping.html

Much of the code that deals with object-relational mapping describes how fields in the database correspond to fields in in-memory objects. The resulting code tends to be tedious and repetitive to write. A Metadata Mapping allows developers to define the mappings in a simple tabular form, which can then be processed by generic code to carry out the details of reading, inserting, and updating the data.

The dollars method probably shouldn't be in your mapper though, because it is formatting the data. Move this to the presentation layer. If you want to map to money, introduce a Money object.

You can spice it up by also introducing a TypeConverter thing which then knows how to handle certain types from the ColumnDefinition, e.g. something like

class FooMapDefinition implements IteratorAggregate
{
    private $map = [
        'someProperty' => [
            'mapTo': 'propertyName',
            'mapAs': Money::class
        ],
        // … more definitions
    ];

    public function getIterator()
    {
        return this new ArrayIterator($this->map);
    }

}

Then inject a TypeConverterFactory into the mapper. When you map the properties, you let the factory create a converter from the class name in mapAs, push the value from the source data through it and then map it to your entity.

\$\endgroup\$
  • \$\begingroup\$ Gordon, thank you a lot, the dollar parser is just a simple parser, but as I think about it, it tends to be more of a decorator and formatting as you say (usually that is done by the presentation layer/ front-end so you are right) \$\endgroup\$ – ziGi Oct 14 '14 at 8:34
  • \$\begingroup\$ Isn't it also a good idea instead of creating multiple mapper objects using the TypeConverterFactory to store them in a Dependency Injection Container so they could be reused. For example I can see that database date to string date could be a Converter used quite a lot so is instantiating multiple instances of it necessary if we only use it for it's methods? \$\endgroup\$ – ziGi Oct 14 '14 at 9:27
  • 1
    \$\begingroup\$ @ziGi yes, your factory can manage the lifetime of the various converters, returning the same one over and over again. Also yes, you dont need multiple mappers if you have multiple ColumnDefinitions because the mapper code is likely stable generic code. \$\endgroup\$ – Gordon Oct 14 '14 at 12:01

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.