kassko/data-mapper

A mapper which gives a lot of features to representate some raw data like objects

v1.0.0 2021-12-03 00:39 UTC

README

Build Status Total Downloads

A php mapper very tunable, cross-orm and cross-DBMS

  • Objects do not extends a base entity class
  • Map nested objects
  • Support relationship between all types of sources (relational databases, non relational, caches ...)
  • Lazy loading
  • Supports dynamic configuration with expression language
  • Various mapping configuration format
  • Can chain some fallbacks sources while a source is unavailable or instable

Installation

Note that:

  • version contains 4 numbers
  • the second version number is used when compatibility is broken
  • the third for new feature
  • the fourth for hotfix
  • the first for new API or to go from pre-release to release (from 0 to 1)

Using a version in 0.12 is recommended. Versions in 0.13 are no longer maintained.

Example of good requirement:

composer require kassko/data-mapper:"~0.12.6"

Basic usage

Run tests

Ensures phpunit is installed phpunit

Installation: precisions

If you use annotations, register the autoloader:

$loader = require 'vendor/autoload.php';

Doctrine\Common\Annotations\AnnotationRegistry::registerLoader($loader);

And run the environment:

(new Kassko\DataMapper\DataMapperBuilder)->run();

Accessing datas

Data-mapper style

$id = 1;
//Construct a person, set an id to it.
$person = new Kassko\Sample\Person($id);

echo $person->getName();//Implicitly load the name from the given id by fetching the source configured.

Doctrine style

//Here some stuff to retrieve the entityManager.

$id = 1;
$person = $entityManager->getRepository('Kassko\Sample\Person')->find($id);

echo $person->getName();

With data-mapper, you only need to call a getter, the access logic is in a configuration.

The configuration is either in the object php file or in a separated file. If it's in the object file, the configuration is in annotations or a method returns it in a php array or in a yaml string. If it's in a separated file, the configuration is in a php or a yaml file.

For simplicity, the usage is documented with annotations format. But in this documentation, you can find reference guide for the others formats.

Hydrating your object

Hydrating your object using inline data sources

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;
use Kassko\DataMapper\ObjectExtension\LoadableTrait;

class Person
{
    use LoadableTrait;

    private $id;
    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", args="#id", lazyLoading=true)
     */
    private $name;
    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", args="#id", lazyLoading=true)
     */
    private $email;

    public function __construct($id) { $this->id = $id; }
    public function getId() { return $this->id; }
    public function getName() { $this->loadProperty('name'); return $this->name; }
    public function getEmail() { $this->loadProperty('email'); return $this->email; }
}
namespace Kassko\Sample;

class PersonDataSource
{
    public function getData($id)
    {
        return [
          'name' => 'foo',
          'email' => 'foo@bar.com'
        ];
    }  
}

Hydrating your object using a data source store

A DataSource store is usefull

If you prefer to group your sources in one place:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;
use Kassko\DataMapper\ObjectExtension\LoadableTrait;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="nameSource", 
 *          class="Kassko\Sample\PersonDataSource", 
 *          method="getData",
 *          args="#id",
 *          lazyLoading=true
 *      ),
 *      @DM\DataSource(
 *          id="emailSource", 
 *          class="Kassko\Sample\PersonDataSource", 
 *          method="getData",
 *          args="#id",
 *          lazyLoading=true
 *      )
 * })
 */
class Person
{
    use LoadableTrait;

    private $id;
    /**
     * @DM\RefSource(id="nameSource")
     */
    private $name;
    /**
     * @DM\RefSource(id="emailSource")
     */
    private $email;

    public function __construct($id) { $this->id = $id; }
    public function getId() { return $this->id; }
    public function getName() { $this->loadProperty('name'); return $this->name; }
    public function getEmail() { $this->loadProperty('email'); return $this->email; }
}

Or if you have a source which hydrate several properties:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;
use Kassko\DataMapper\ObjectExtension\LoadableTrait;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="personSource", 
 *          class="Kassko\Sample\PersonDataSource", 
 *          method="getData",
 *          args="#id", 
 *          lazyLoading=true,
 *          supplySeveralFields=true
 *      )
 * })
 */
class Person
{
    use LoadableTrait;

    private $id;
    /**
     * @DM\RefSource(id="personSource")
     * @DM\Field("name"="first_name")
     */
    private $firstName;
    /**
     * @DM\RefSource(id="personSource")
     */
    private $name;
    /**
     * @DM\RefSource(id="personSource")
     */
    private $email;
    /**
     * @DM\RefSource(id="personSource")
     */
    private $phone;

    public function __construct($id) { $this->id = $id; }
    public function getId() { return $this->id; }
    public function getFirstName() { $this->loadProperty('firstName'); return $this->firstName; }
    public function getName() { $this->loadProperty('name'); return $this->name; }
    public function getEmail() { $this->loadProperty('email'); return $this->email; }
    public function getPhone() { $this->loadProperty('phone'); return $this->phone; }
}
namespace Kassko\Sample;

class PersonDataSource
{
    public function getData($id)
    {
        return [
            'first_name' => 'foo',
            'name' => 'bar',
            'email' => 'foo@bar',
            'phone' => '01 02 03 04 05',
        ];
    }
}

Note that you don't have to expose the setters. Expose them only if you have specific logic to write. I advise you not to make "public" these setters since the properties they wrapp are loaded (and so managed) with a data source.

Working with sources and dependency injection

CarRepository has some dependencies too (the entity manager), it is instantiated with a resolver class-resolver. Reminder: you can see more details here.

Run environment and register dependencies:

(new Kassko\DataMapper\DataMapperBuilder)
    ->settings(['container' => ['instance' => ['person.data_source_id' => $personDataSourceInstance]]])
    ->run()
;

You can send an object which implements \ArrayAccess:

(new Kassko\DataMapper\DataMapperBuilder)
    ->settings(['container' => ['instance' => $container]])
    ->run()
;

Or send an object which have "get" and "has" methods:

(new Kassko\DataMapper\DataMapperBuilder)
    ->settings(['container' => ['instance' => $container, 'get_method_name' => 'get', 'has_method_name' => 'has']])
    ->run()
;

And you specify your dependency id like below:

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="personSource", 
 *          class="@person.data_source_id", 
 *          method="find", 
 *          args="#id",
 *          lazyLoading=true
 *      )
 * })
 */
class Person
{
}

Source annotation details

  • id. An arbitrary id for the source. Optional but necessary if you need to mentionned the source in another annotation.
  • class. The class of the source that return datas. If the source has some dependencies (above PersonDataSource has a dependency $connection), its instanciation can be performed by a resolver named class-resolver. See more details here.
  • method. The name of the method that return datas.
  • args. Arguments of the method that return datas. You can send a raw value, a field value with the prefix # (ex: #id), an expression and more ... See more details here
  • supplySeveralFields. Whether the source returns the data directly or put them in a key of an array and return the array. If the source supply only one field, it can return directly the data, these data will be bound to the good property. Else the source should return an array with as keys as property to supply. The key is named like the property or a mapping is done in the annotation Field.

Data-mapper is not an ORM so it cannot generate for you some sql statement. But it can wrapp the use of an ORM like Doctrine ORM so that you can take advantage of the two. It also can help you to make relations between different DBMS.

Example with a relation with a Doctrine source

namespace Kassko\Sample;

use Kassko\DataMapper\ObjectExtension\LoadableTrait;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="personSource", 
 *          class="Kassko\Sample\PersonDataSource", 
 *          method="getData", 
 *          args="#id", 
 *          lazyLoading=true,
 *          supplySeveralFields=true
 *      ),
 *      @DM\DataSource(
 *          id="carSource", 
 *          class="Kassko\Sample\CarRepository", 
 *          method="find", 
 *          args="expr(source('personSource')['car_id'])",
 *          lazyLoading=true
 *      )
 * })
 */
class Person
{
    private $id;
    /**
     * @DM\RefSource(id="personSource")
     */
    private $firstName;
    /**
     * @DM\RefSource(id="personSource")
     */
    private $name;
    /**
     * @DM\RefSource(id="personSource")
     */
    private $email;
    /**
     * @DM\RefSource(id="personSource")
     */
    private $phone;
    /**
     * @DM\RefSource(id="carSource")
     */
    private $car;

    public function __construct($id) { $this->id = $id; }
    public function getId() { return $this->id; }
    public function getFirstName() { return $this->firstName; }
    public function getName() { return $this->name; }
    public function getEmail() { return $this->email; }
    public function getPhone() { return $this->phone; }
    public function getCar() { return $this->car; }
}
namespace Kassko\Sample;

class PersonDataSource
{
    public function getData($id)
    {
        return [
            'first_name' => 'foo',
            'name' => 'bar',
            'email' => 'foo@bar',
            'phone' => '01 02 03 04 05',
        ];
    }
}
namespace Kassko\Sample;

use Doctrine\ORM\EntityRepository;

/**
 * CarRepository is a Doctrine source that feed the property $car.
 */
class CarRepository extends EntityRepository
{
}

Features: details

=======================================================================================================

Basic usage

Given an object:

namespace Kassko\Sample;

class Watch
{
        private $brand;
        private $color;

        public function getBrand() { return $this->brand; }
        public function setBrand($brand) { $this->brand = $brand; }
        public function getColor() { return $this->color; }
        public function setColor($color) { $this->color = $color; }
}

Use the ResultBuilder:

The result builder allows you to hydrate all a collection from a result that contains several records. It also allows you to get only one result even if the result contains several records.

$data = [
        0 => [
                'brand' => 'some brand',
                'color' => 'blue',
        ],
        1 => [
                'brand' => 'some other brand',
                'color' => 'green',
        ],
];

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();

$dataMapper->resultBuilder('Kassko\Sample\Watch', $data)->all();//The result will be an array with two objects.
$dataMapper->resultBuilder('Kassko\Sample\Watch', $data)->first();//The result will be a watch object representing the first record.

The code above will display:

array(2) {
    ["brand"]=>
    string(10) "some brand"
    ["color"]=>
    string(4) "blue"
}

Inversely, you can extract values of an object or of an object collection:

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();

$dataMapper->resultBuilder('Kassko\Sample\Watch')->raw($object);
$dataMapper->resultBuilder('Kassko\Sample\Watch')->raw($collection);

Above, we use the default hydrator. But you often need to customize hydration because column names are different of properties name and for many other reasons.

To customize the hydration you should provide a mapping configuration. Severals formats are available but in this documentation we choose to use the annotation format (more details about all mapping configuration format are available here).

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Watch
{
        private $brand;

        /**
         * @DM\Fields(name="COLOR")
         */
        private $color;

        public function getBrand() { return $this->brand; }
        public function setBrand($brand) { $this->brand = $brand; }
        public function getColor() { return $this->color; }
        public function setColor($color) { $this->color = $color; }
}
$loader = require 'vendor/autoload.php';

Doctrine\Common\Annotations\AnnotationRegistry::registerLoader(array($loader, 'loadClass'));

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();
$dataMapper->resultBuilder('Kassko\Sample\Watch', $data)->all();

Use the result builder

You can find below all the ways to get results with the ResultBuilder:

        /*
        Return an array of objects.
        So return an array with only one object, if only one fullfill the request.
        */
        $resultBuilder->all();
        /*
        Return the object found.

        If more than one result are found, throw an exception
        Kassko\DataMapper\Result\Exception\NonUniqueResultException.

        If no result found, throw an exception
        Kassko\DataMapper\Result\Exception\NoResultException.
        */
        $resultBuilder->single();
        /*
        Return the object found or null.
        */
        $resultBuilder->one();

        /*
        Return the object found or a default result (like false).
        */
        $resultBuilder->one(false);

        /*
        If more than one result are found, throw an exception
        Kassko\DataMapper\Result\Exception\NonUniqueResultException.
        */
        /*
        Return the first object found or null.
        */
        $resultBuilder->first();

        /*
        Return the first object found or a default result (like value false).
        */
        $resultBuilder->first(false);

        /*
        If no result found, throw an exception
        Kassko\DataMapper\Result\Exception\NoResultException.
        */
        /*
        Return an array indexed by a property value.

        If the index does not exists (allIndexedByUnknown()), throw an exception Kassko\DataMapper\Result\Exception\NotFoundIndexException.

        If the same index is found twice, throw an exception
        Kassko\DataMapper\Result\Exception\DuplicatedIndexException.

        Examples:

        allIndexedByBrand() will index by brand value:
        [
                'BrandA' => $theWatchInstanceWithBrandA,
                'BrandB' => $theWatchInstanceWithBrandB,
        ]

        allIndexedByColor() will index by color value:
        [
                'Blue' => $theWatchInstanceWithColorBlue,
                'Red' => $theWatchInstanceWithColorRed,
        ]

        allIndexedByUnknown() will throw a Kassko\DataMapper\Result\Exception\NotFoundIndexException.
        */
        $resultBuilder->allIndexedByBrand();//Indexed by brand value
        //or
        $resultBuilder->allIndexedByColor();//Indexed by color value
        /*
        Return an iterator.

        Result will not be hydrated immediately but only when you will iterate the results (with "foreach" for example).
        */
        $result = $resultBuilder->iterable();
        foreach ($result as $object) {//$object is hydrated

                if ($object->getColor() === 'blue') {
                        break;

                        //We found the good object then we stop the loop and others objects in results will not be hydrated.
                }
        }
        /*
        Return an iterator indexed by a property value.

        If the index does not exists, throw an exception Kassko\DataMapper\Result\Exception\NotFoundIndexException.

        If the same index is found twice, throw an exception Kassko\DataMapper\Result\Exception\DuplicatedIndexException.
        */
        $resultBuilder->iterableIndexedByBrand();
        //or
        $resultBuilder->iterableIndexedByColor();

Enforce type of fields

This section will be written later.

Apply converters before hydration or extraction

Converter

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;
use Kassko\DataMapper\Hydrator\HydrationContextInterface;
use Kassko\DataMapper\Hydrator\Value;
use \DateTime;

class Watch
{
        private static $brandCodeToLabelMap = [1 => 'Brand A', 2 => 'Brand B'];
        private static $brandLabelToCodeMap = ['Brand A' => 1, 'Brand B' => 2];

        /**
         * @DM\Field(readConverter="readBrand", writeConverter="writeBrand")
         */
        private $brand;

        /**
         * @DM\Field(readConverter="hydrateBool", writeConverter="extractBool")
         */
        private $waterProof;

        /**
         * @DM\Field(readConverter="hydrateBoolFromSymbol", writeConverter="extractBoolToSymbol")
         */
        private $stopWatch;

        /**
         * @DM\Field(type="date", readDateConverter="Y-m-d H:i:s", writeDateConverter="Y-m-d H:i:s")
         */
        private $createdDate;

        public function getBrand() { return $this->brand; }
        public function setBrand($brand) { $this->brand = $brand; }
        public function isWaterProof() { return $this->waterProof; }
        public function setWaterProof($waterProof) { $this->waterProof = $waterProof; }
        public function hasStopWatch() { return $this->stopWatch; }
        public function setStopWatch($stopWatch) { $this->stopWatch = $stopWatch; }
        public function getCreatedDate() { return $this->createdDate; }
        public function setCreatedDate(DateTime $createdDate) { $this->createdDate = $createdDate; }

        public static function readBrand(Value $value, HydrationContextInterface $context)
        {
                if (isset(self::$brandCodeToLabelMap[$value->value])) {
                        $value->value = self::$brandCodeToLabelMap[$value->value];
                }
        }

        public static function writeBrand(Value $value, HydrationContextInterface $context)
        {
                if (isset(self::$brandLabelToCodeMap[$value->value])) {
                        $value->value = self::$brandLabelToCodeMap[$value->value];
                }
        }

        public static function hydrateBool(Value $value, HydrationContextInterface $context)
        {
                $value->value = $value->value == '1';
        }

        public static function extractBool(Value $value, HydrationContextInterface $context)
        {
                $value->value = $value->value ? '1' : '0';
        }

        public static function hydrateBoolFromSymbol(Value $value, HydrationContextInterface $context)
        {
                $value->value = $value->value == 'X';
        }

        public static function extractBoolToSymbol(Value $value, HydrationContextInterface $context)
        {
                $value->value = $value->value ? 'X' : ' ';
        }
}

readDateConverter contains the format of the string to transform into Date object. Internally, DataMapper uses the Php function DateTime::createFromFormat() to create a Date object from a raw string.

writeDateConverter contains the string format in which you want to serialize your Date object. Internally, DataMapper uses the Php function DateTime::createFromFormat() to serialise the Date object in a string.

Given this code:

$data = [
        'brand' => '1',
        'waterProof' => '1',
        'stopWatch' => 'X',
        'created_date' => '2014-09-14 12:36:52',
];

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();
$object = new Kassko\Sample\Watch;
$dataMapper->hydrator('Kassko\Sample\Watch')->hydrate($data, $object);
var_dump($object);

Your output will be:

object(Watch)#283 (8) {
        ["brand":"Watch":private]=> string(10) "Brand A"
        ["waterProof":"Watch":private]=> bool(true)
        ["stopWatch":"Watch":private]=> bool(true)
        ["createdDate":"Watch":private]=>
                object(DateTime)#320 (3) { ["date"]=> string(19) "2014-09-14 12:36:52" ["timezone_type"]=> int(3) ["timezone"]=> string(13) "Europe/Berlin" }
}

If the created_date had a bad format, an exception would have been thrown. For example, the format given above in the read date converter is 'Y-m-d H:i:s', so a create_date like '2014 09 14 12h36m52s' is not correct.

Date converter

This section will be written later.

Add callbacks before or after hydration process

This section will be written later.

Customize getters and setters

DataMapper automatically recognize getter (or isser or haser) and setter of a field.

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Watch
{
        /**
         * @DM\Field
         */
        private $brand;

        /**
         * @DM\Field
         */
        private $waterProof;

        /**
         * @DM\Field
         */
        private $stopWatch;

        public function getBrand() { return $this->brand; }
        public function setBrand($brand) { $this->brand = $brand; }
        public function isWaterProof() { return $this->waterProof; }
        public function setWaterProof($waterProof) { $this->waterProof = $waterProof; }
        public function hasStopWatch() { return $this->stopWatch; }
        public function setStopWatch($stopWatch) { $this->stopWatch = $stopWatch; }
}

To retrieve the corresponding getter, DataMapper look in order at:

  • a getter
  • an isser
  • a haser

You also can specify corresponding getters/setters:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Watch
{
        /**
         * @DM\Field(prefix="is")
         */
        private $waterProof;

        /**
         * @DM\Getter(prefix="has")
         */
        private $alarm;

        /**
         * @DM\Getter(prefix="are")
         */
        private $handsYellow;

        /**
         * @DM\Field(name="hasStopWatch")
         */
        private $stopWatch;

        /**
         * @DM\Getter(name="canColorChange")
         * @DM\Setter(name="colorCanChange")
         */
        private $variableColor;

        public function isWaterProof() { return $this->waterProof; }
        public function setWaterProof($waterProof) { $this->waterProof = $waterProof; }
        public function hasAlarm() { return $this->alarm; }
        public function setAlarm($stopWatch) { $this->alarm = $alarm; }
        public function areHandsYellow() { return $this->handsYellow; }
        public function setHandsyellow($handsYellow) { $this->handsYellow = $handsYellow; }
        public function hasStopWatch() { return $this->stopWatch; }
        public function setStopWatch($stopWatch) { $this->stopWatch = $stopWatch; }
        public function canColorChange() { return $this->variableColor; }
        public function colorCanChange($colorCanChange) { $this->variableColor = $colorCanChange; }
}

Hydrate nested objects

This section will be written later.

Configure a php object hydrator instead of using a mapping configuration

This section will be written later.

Work with object complex to create, like service

This section will be written later.

Work with other mapping configuration format

This section will be written later.

Outer mapping configuration format

This section will be written later.

Inner mapping configuration format

This section will be written later.

Improve persistance ignorance

This section will be written later.

Choose a mapping configuration at runtime

You can use the same model with various mapping configuration but you must work with one of the outer mapping configuration and not with mapping embedded in the object. So 'yaml_file' or 'php_file' are correct mapping format but 'annotations', 'inner_php' or 'inner_yaml' are bad format.

namespace Kassko\Sample;

class Color
{
        private $red;
        private $green;
        private $blue;

        public function getRed() { return $this->red; }
        public function setRed($red) { $this->red = $red; }
        public function getGreen() { return $this->green; }
        public function setGreen($green) { $this->green = $green; }
        public function getBlue() { return $this->blue; }
        public function setBlue($blue) { $this->blue = $blue; }
}

A english data source with the mapping in yaml:

# color_en.yml

fields:
        red: ~
        green: ~
        blue: ~

A french data source with the mapping in yaml:

# color_fr.yml

fields:
        red:
                name: rouge
        green:
                name: vert
        blue:
                name: bleu

And imagine we've got a spanish data source with the mapping in a php format.

//color_es.php

return [
        'fields' => [
                'red' => 'rojo',
                'green' => 'verde',
                'blue' => 'azul',
        ],
];
use DataMapper\Configuration\RuntimeConfiguration;

$data = [
        'red' => '255',
        'green' => '0',
        'blue' => '127',
];

$resultBuilder = $dataMapper->resultBuilder('Kassko\Sample\Color', $data);
$resultBuilder->setRuntimeConfiguration(
        (new RuntimeConfiguration)
        ->addClassMetadataDir('Color', 'some_resource_dir')//Optional, if not specified Configuration::defaultClassMetadataResourceDir is used.
        ->addMappingResourceInfo('Color', 'color_en.yml', 'inner_yaml')
);

$resultBuilder->single();
use DataMapper\Configuration\RuntimeConfiguration;

$data = [
        'rouge' => '255',
        'vert' => '0',
        'bleu' => '127',
];

$resultBuilder = $dataMapper->resultBuilder('Kassko\Sample\Color', $data);
$resultBuilder->setRuntimeConfiguration(
        (new RuntimeConfiguration)
        ->addClassMetadataDir('Color', 'some_resource_dir')
        ->addMappingResourceInfo('Color', 'color_fr.yml', 'inner_yaml')
);

$resultBuilder->single();
use DataMapper\Configuration\RuntimeConfiguration;

$data = [
        'rojo' => '255',
        'verde' => '0',
        'azul' => '127',
];

$resultBuilder = $dataMapper->resultBuilder('Kassko\Sample\Color', $data);
$resultBuilder->setRuntimeConfiguration(
        (new RuntimeConfiguration)
        ->addClassMetadataDir('Color', 'some_resource_dir')
        ->addMappingResourceInfo('Color', 'color_es.php', 'inner_php')
);

$resultBuilder->single();

Bind a mapping configuration to a property especially

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Customer
{
        /**
         * @DM\Field
         * @DM\Id
         */
        private $id;

        /**
         * @DM\Field(class="Kassko\Sample\Address")
         * @DM\Config(mappingResourceType="yaml", mappingResourceName="billing_address.yml")
         */
        private $billingAddress;//$billingAddress is a value object.

        /**
         * @DM\Field(class="Kassko\Sample\Address")
         * @DM\Config(mappingResourceType="yaml", mappingResourceName="shipping_address.yml")
         */
        private $shippingAddress;//$shippingAddress is a value object too.
}
namespace Kassko\Sample;

class Address
{
        private $street;
        private $town;
        private $postalCode;
        private $country;
}
# billing_address.yml

fields:
        street:
                name: billing_street
        town:
                name: billing_town
        postalCode:
                name: billing_postal_code
        country:
                name: billing_country
# shipping_address.yml

fields:
        street:
                name: shipping_street
        town:
                name: shipping_town
        postalCode:
                name: shipping_postal_code
        country:
                name: shipping_country
$data = [
        'id' => 1,
        'billing_street' => '12 smarties street',
        'billing_town' => 'Nuts',
        'billing_postal_code' => '654321'
        'billing_country' => 'England',
        'shipping_street' => '23 smarties street',
        'shipping_town' => 'Mars',
        'shipping_postal_code' => '987654'
        'shipping_country' => 'England',
];

$dataMapper->resultBuilder('Kassko\Sample\Customer', $data)->single();

Note that you can have value objects which contains value objects and so on. And each value object can use it's own mapping configuration format.

Bind a source to a property or a set of properties / hydrate object from multi-sources, multi-orm

Data source

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Information
{
    /**
     * @DM\DataSource(class="Kassko\Sample\ShopDataSource", method="getBestShop")
     * @DM\Field(class='Kassko\Sample\Shop')
     */
    private $bestShop;

    /**
     * @DM\DataSource(class="Kassko\Sample\ShopDataSource", method="getNbShops")
     */
    private $nbShops;

    public function getBestShop() { return $this->bestShop; }
    public function setBestShop(Shop $shop) { $this->bestShop = $bestShop; }
}
namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Shop
{
    /**
     * @DM\Field
     */
    private $name;

    /**
     * @DM\Field
     */
    private $address;
}
namespace Kassko\Sample;

class ShopDataSource
{
    public function getBestShop()
    {
        return [
            'name' => 'The best',
            'address' => 'Street of the bests',
        ];
    }

    public function getNbShops()
    {
        return 25;
    }
}

Method arguments

This section will be written later.

Lazy loading

Below, we load properties "bestShop" and "keyboard" only when we use it.

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;
use Kassko\DataMapper\ObjectExtension\LazyLoadableTrait;

class Information
{
    use LazyLoadableTrait;

        /**
         * @DM\DataSource(class="Kassko\Sample\ShopDataSource", method="getBestShop", lazyLoading=true)
         * @DM\Field(class='Kassko\Sample\Shop')
         */
        private $bestShop;

        /**
         * @DM\DataSource(class="Kassko\Sample\ShopDataSource", method="getNbShops", lazyLoading=true)
         */
        private $nbShops;

        public function getBestShop()
        {
                $this->loadProperty('bestShop');//<= Load the best shop from the property name if not loaded.
                return $this->bestShop;
        }

        public function getNbShop()
        {
                $this->loadProperty('nbShops');//<= Load the best shop from the property name if not loaded.
                return $this->nbShops;
        }
}
namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Shop
{
        /**
         * @DM\Field
         */
        private $name;

        /**
         * @DM\Field
         */
        private $address;
}
namespace Kassko\Sample;

class ShopDataSource
{
    public function getBestShop()
    {
        return [
            'name' => 'The best',
            'address' => 'Street of the bests',
        ];
    }

    public function getNbShops()
    {
        return 25;
    }
}

Source store

Given this code:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Person
{
    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true, supplySeveralFields=true)
     */
    private $name;

    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true, supplySeveralFields=true)
     */
    private $address;

    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true, supplySeveralFields=true)
     */
    private $phone;

    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true, supplySeveralFields=true)
     */
    private $email;
}
namespace Kassko\Sample;

class PersonDataSource
{
    public function getData()
    {
        return [
            'name' => 'Foo',
            'address' => 'Blue road',
            'phone' => '01 02 03 04 05',
            'email' => 'foo@bar.baz',
        ];
    }
}

We can remove the duplication:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="some_source", class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true
 *      )
 * })
 */
class Person
{
    /**
     * @DM\RefSource(id="some_source")
     */
    private $name;

    /**
     * @DM\RefSource(id="some_source")
     */
    private $address;

    /**
     * @DM\RefSource(id="some_source")
     */
    private $phone;

    /**
     * @DM\RefSource(id="some_source")
     */
    private $email;
}

Fallback source

Here, sourceB replace sourceA if its not stable:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="sourceA", class="Kassko\Sample\ShopDataSource", method="getData", lazyLoading=true,
 *          fallbackSourceId="sourceB", onFail="checkException", exceptionClass="Kassko\Sample\NotStableSourceException"
 *      )
 * })
 */
class Person
{
}

Or:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="sourceA", class="Kassko\Sample\ShopDataSource", method="getData", lazyLoading=true,
 *          fallbackSourceId="sourceB", onFail="checkReturnValue", badReturnValue="null"
 *      )
 * })
 */
class Person
{
}

Bad return values could be: "null", "false", "emptyString" or "emptyArray".

Processor

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Key
{
    use LazyLoadableTrait;

    private $note;
    private $octave = 3;

    /**
     * @DM\DataSource(
     * id="ida", 
     * class="Kassko\Samples\KeyLLManager", 
     * method="getData", supplySeveralFields=true,
     * preprocessors = @DM\Methods({
     *  @DM\Method(method="somePreprocessor"),
     *  @DM\Method(class="Kassko\Sample\KeyProcessor", method="preprocess", args="##this")
     * })
     * processors = @DM\Methods({
     *  @DM\Method(method="someProcessor"),
     *  @DM\Method(class="Kassko\Sample\KeyProcessor", method="process", args="##this")
     * })
     *)
     */
    public $color;

    public function getColor() 
    {
        $this->loadProperty('color');
        return $this->color;
    }

    public function somePrepocessor()
    {
        //Some stuff
    }

    public function someProcessor())
    {
        //Some stuff
    }
}
namespace Kassko\Sample;

class KeyPreprocessor
{
    public function preprocess(Key $key)
    {
        $this->logger->info(sprintf('Before key hydration %s', $key->getId()));
    }

    public function process($keyColor)
    {
        $this->logger->info(sprintf('After key hydration %s', $key->getId()));
    }
}

Or:

class Key
{
    use LazyLoadableTrait;

    private $note;
    private $octave = 3;

    /**
     * @DM\DataSource(
     * id="ida", 
     * class="Kassko\Samples\KeyLLManager", 
     * method="getData", supplySeveralFields=true,
     * preprocessor = @DM\Method(method="somePreprocessor"),  
     * processor = @DM\Method(method="someProcessor")
     *)
     */
    public $color;

    public function getColor() 
    {
        $this->loadProperty('color');
        return $this->color;
    }

    public function somePrepocessor()
    {
        //Some stuff
    }

    public function someProcessor())
    {
        //Some stuff
    }
}

Depends

depends contains sources of which depends an other source. It's usefull when you need to ensures that a property is already available in a zone.

This section will be completed later.

Work with relations

This section will be written later.

How to have DataMapper/ResultBuilder ignorance in the client code

This section will be written later.

Use expression language

This section will be written later.

Basic usage of expression language

This section will be written later.

Add a function provider

This section will be written later.

Object listener

This section will be written later.

Add a custom mapping configuration format

This section will be written later.

Inherit mapping configuration

This section will be written later.

Component configuration reference

[
    'mapping' =>
    [
        //Default is "annotations" or other type (1).
        'default_resource_type' => 'annotations',

        //Optional key.
        'default_resource_dir' => 'some_dir',

        //Optional key. Has only sense if you use inner_php or inner_yaml format.
        //It's the method whereby you provide the mapping.
        'default_provider_method' => 'some_method_name',

        //Optional section.
        'groups' =>
        [
            'some_group' =>
            [
                //Default is "annotations" or other type (1).
                'resource_type' => annotations,

                //The resource dir of the given bundle.
                'resource_dir' => 'some_dir',

                //Default value is null.
                'provider_method' => null,
            ],
        ],

        //Optional section
        'objects':
        [
            [
                //Required (the full qualified object class name).
                'class' => 'some_fqcn',

                //Optional key. Allows to inherit settings from a group if there are not specified.
                'group' => 'some_group',

                //Optional key.
                'resource_type' => 'yaml_file',

                //Optional key.
                //The resource directory with the resource name.
                //If not defined, data-mapper fallback to resource_name and prepend to it a resource_dir (this object resource_dir or a group resource_dir or the default_resource_dir).
                //So if resource_path is not defined, keys resource_name and a resource_dir should be defined.
                'resource_path' => 'some_path',

                //Optional key. Only the resource name (so without the directory).
                'resource_name' => 'some_ressource.yml',

                //Optional key. Override default_provider_method.
                'provider_method' => 'some_method_name',
            ],
        ],
    ],

    //Optional section.
    'cache' =>
    [
        //Optional section. The cache for mapping metadata.
        'metadata_cache' =>
        [
            //A cache instance which implements Kassko\Cache\CacheInterface. Default is Kassko\Cache\ArrayCache.
            //If you use a third-party cache provider, maybe you need to wrap it into an adapter to enforce compatibility with Kassko\Cache\CacheInterface.
            'instance' => $someCacheInstance,

            //Default value is 0.
            //0 means the data will never been deleted from the cache.
            //Obviously, 'life_time' has no sense with an "ArrayCache" implementation.
            'life_time' => 0,

            //Default value is false. Indicates if the cache is shared or not.
            //If you don't specify it, you're not wrong. It optimises the
            'is_shared' => false,
        ],

        //Optional section. The cache for query results.
        //This section has the same keys as 'metadata_cache' section.
        'result_cache': => [],
    ],

    //Optional key. A logger instance which implements Psr\Logger\LoggerInterface.
    'logger' => $logger,

    //Optional key. Needed to retrieve repositories specified in 'repository_class' mapping attributes and which creation is assigned to a creator (a factory, a container, a callable).
    'class_resolver' => $someClassResolver,

    //Optional key. Needed to retrieve object listener specified in 'object_listener' mapping attributes and which creation is assigned to a creator (a factory, a container, a callable).
    'object_listener_resolver' => $someObjectListenerResolver,
]

(1) availables types are annotations, yaml_file, php_file, inner_php, inner_yaml. And maybe others if you add some custom mapping loaders.

Mapping configuration reference

Config config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Config(
     *      class="\ValueObjectClass",
     *      mappingResourceName="valueObjectResourceName",
     *      mappingResourcePath="valueObjectResourcePath",
     *      mappingResourceType="valueObjectResourceType"
     * )
     */
    protected $firstField;

Yaml format:

fields: [firstField]
config:
    firstField:
        class: "\\\ValueObjectClass"
        mappingResourceName: valueObjectResourceName
        mappingResourcePath: valueObjectResourcePath
        mappingResourceType: valueObjectResourceType

Php format:

[
    'fields' => ['firstField'],
    'config' => [
        'firstField'    => [
            'class' => '\ValueObjectClass',
            'mappingResourceName' => 'valueObjectResourceName',
            'mappingResourcePath' => 'valueObjectResourcePath',
            'mappingResourceType' => 'valueObjectResourceType'
        ]
    ]
];

CustomHydrator config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * Class SomeClass
 * 
 * @DM\CustomHydrator(
 *      class="SomeClassHydrator",
 *      hydrateMethod="hydrateMethod",
 *      extractMethod="extractMethod"
 * )
 */
class SomeClass
{
}

Yaml format:

object:
  customHydrator:
    class: SomeClassHydrator
    hydrateMethod: hydrateMethod
    extractMethod: extractMethod

Php format:

[
    'object'    => [
        'customHydrator'    => [
            'class' => 'SomeClassHydrator',
            'hydrateMethod' => 'hydrateMethod',
            'extractMethod' => 'extractMethod'
        ]
    ]
];

The methods prototype:

class SomeClassHydrator
{
    /**
     * Hydrate a SomeClass object from raw data $data.
     *
     * @param array The raw data
     * @return SomeClass
     */
    public function hydrateMethod(array $data)
    {
    }

    /**
     * Extract data from a SomeClass instance $object.
     *
     * @param SomeClass The object
     * @return array
     */
    public function extractMethod(SomeClass $object)
    {
    }
}

DataSource config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\DataSource(
     *      id="firstFieldId",
     *      lazyLoading=true,
     *      supplySeveralFields=true,
     *      depends={"depend#1","depend#2"},
     *      onFail="checkException",
     *      exceptionClass="\RuntimeException",
     *      badReturnValue="emptyString",
     *      fallbackSourceId="firstFieldFallbackSourceId",
     *      preprocessor=@DM\Method(method="fooPreprocessor"),
     *      processors = @DM\Methods({
     *          @DM\Method(method="barProcessor"),
     *          @DM\Method(method="bazProcessor")
     *      })
     *      class="\stdClass",
     *      method="someMethod",
     *      args={"argument#1", "argument#2"}
     * )
     */
     protected $firstField;
}

Yaml format:

fields:
    firstField:
        name: originalFieldName
        dataSource:
            id: firstFieldId
            lazyLoading: true
            supplySeveralFields: true
            depends: [depend#1, depend#2]
            onFail: checkException
            exceptionClass: "\\\RuntimeException"
            badReturnValue: emptyString
            fallbackSourceId: firstFieldFallbackSourceId
            preprocessor:
                class: "##this"
                method: fooPreprocessor
                args: []
            processor: ~
            preprocessors: []
            processors: 
                - {method: barProcessor}
                - {method: bazProcessor}
            class: "\\\stdClass"
            method: someMethod
            args: [argument#1, argument#2]

Php format:

[
    'fields' => [
        'firstField' => [
            'name'       => 'originalFieldName',
            'dataSource' => [
                'id'                  => 'firstFieldId',
                'lazyLoading'         => true,
                'supplySeveralFields' => true,
                'depends'             => ['depend#1', 'depend#2'],
                'onFail'              => 'checkException',
                'exceptionClass'      => '\RuntimeException',
                'badReturnValue'      => 'emptyString',
                'fallbackSourceId'    => 'firstFieldFallbackSourceId',
                'preprocessor'        => [
                    'class'  => '##this',
                    'method' => 'fooPreprocessor',
                    'args'   => []
                ],
                'processor'           => [],
                'preprocessors'       => [],
                'processors'          => [
                    ['method' => 'barProcessor'],
                    ['method' => 'bazProcessor'],
                ],
                'class'               => '\stdClass',
                'method'              => 'someMethod',
                'args'                => ['argument#1', 'argument#2']
            ]
        ]
    ]
];

To know more about the "Method config" usage, please see its dedicated documentation "Method config".

DataSourcesStore config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="personSource",
 *          class="Kassko\Sample\PersonDataSource",
 *          method="getData",
 *          args="#id",
 *          lazyLoading=true,
 *          supplySeveralFields=true,
 *          onFail="checkException",
 *          exceptionClass="\RuntimeException",
 *          badReturnValue="emptyString",
 *          fallbackSourceId="testFallbackSourceId",
 *          depends="#dependsFirst",
 *          preprocessor = @DM\Method(method="somePreprocessor"),
 *          processor = @DM\Method(method="someProcessor")
 *      )
 * })
 */
class SomeClass
{
}

Yaml format:

object:
    dataSourcesStore:
        - id: personSource
        class: "Kassko\\\Sample\\\PersonDataSource"
        method: getData
        args: [#id]
        lazyLoading: true
        supplySeveralFields: true
        onFail: checkException
        exceptionClass: \RuntimeException
        badReturnValue: emptyString
        fallbackSourceId: testFallbackSourceId
        depends: [#dependsFirst]
        preprocessor:
            class: ""
            method: somePreprocessor
            args: []
        processor:
            class: ""
            method: someProcessor
            args: []

Php format:

[
    'object'    => [
        'dataSourcesStore'    => [
            [
                'id'=> 'personSource',
                'class'=> 'Kassko\Sample\PersonDataSource',
                'method'=> 'getData',
                'args' => ['#id'],
                'lazyLoading' => true,
                'supplySeveralFields' => true,
                'onFail'    => 'checkException',
                'exceptionClass' => '\RuntimeException',
                'badReturnValue' => 'emptyString',
                'fallbackSourceId' => 'testFallbackSourceId',
                'depends' => ['#dependsFirst'],
                'preprocessor' => [
                    'class' => '',
                    'method' => 'somePreprocessor',
                    'args' => []
                ],
                'processor' => [
                    'class' => '',
                    'method' => 'someProcessor',
                    'args' => []
                ]
            ]
        ]
    ]
];

To know more about the "Method config" usage, please see its dedicated documentation "Method config".

ExcludeImplicitSource config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\RefImplicitSource(id="RefImplicitSourceId")
 */
class SomeClass
{
    protected $fieldToBindAutoToImplicitSource;

    protected $anotherFieldToBindAutoToImplicitSource;

    /**
     *@DM\ExcludeImplicitSource
     */
    protected $fieldNotToBindAutoToImplicitSource;
}

Yaml format:

object:
    RefImplicitSource: RefImplicitSourceId
fields:
    fieldToBindAutoToImplicitSource:
        name: fieldToBindAutoToImplicitSource
    anotherFieldToBindAutoToImplicitSource:
        name: anotherFieldToBindAutoToImplicitSource
    fieldNotToBindAutoToImplicitSource:
        name: fieldNotToBindAutoToImplicitSource
fieldsNotToBindToImplicitSource: [fieldNotToBindAutoToImplicitSource]

Php format:

[
    'object' => [
        'RefImplicitSource' => 'RefImplicitSourceId'
    ],
    'fields' => [
        'fieldToBindAutoToImplicitSource' => [
            'name'      => 'fieldToBindAutoToImplicitSource',
        ],
        'anotherFieldToBindAutoToImplicitSource' => [
            'name'      => 'anotherFieldToBindAutoToImplicitSource',
        ],
        'fieldNotToBindAutoToImplicitSource' => [
            'name'      => 'fieldNotToBindAutoToImplicitSource',
        ]
    ],
    'fieldsNotToBindToImplicitSource' => [
        'fieldNotToBindAutoToImplicitSource'
    ]
];

Field config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Field(
     *      name="FirstField",
     *      type="string",
     *      class="stdClass",
     *      readConverter="readConvertFirstField",
     *      writeConverter="writeConvertFirstField",
     *      fieldMappingExtensionClass="ExtensionClass"
     * )
     */
    protected $fieldOne;

    /**
     * @DM\Field(
     *      name="SecondField",
     *      type="integer",
     *      class="\DateTime",
     *      readDateConverter="readDateConvertSecondField",
     *      writeDateConverter="writeDateConvertSecondField",
     *      fieldMappingExtensionClass="ExtensionClass",
     *      defaultValue="12"
     * )
     */
    protected $fieldTwo;

    /**
     * @DM\Field(
     *      name="DateField",
     *      type="date"
     * )
     */
    protected $dateField;
}

Yaml format:

fields:
    fieldOne:
        name: FirstField
        type: string
        class: stdClass
        readConverter: readConvertFirstField
        writeConverter: writeConvertFirstField
        fieldMappingExtensionClass: ExtensionClass
    fieldTwo:
        name: SecondField
        type: integer
        class: "\\\DateTime"
        readDateConverter: readDateConvertSecondField
        writeDateConverter: writeDateConvertSecondField
        fieldMappingExtensionClass: ExtensionClass
        defaultValue: 12
    dateField:
        name: DateField
        type: date

Php format:

[
    'fields' => [
        'fieldOne'  => [
            'name'                       => 'FirstField',
            'type'                       => 'string',
            'class'                      => 'stdClass',
            'readConverter'              => 'readConvertFirstField',
            'writeConverter'             => 'writeConvertFirstField',
            'fieldMappingExtensionClass' => 'ExtensionClass',
        ],
        'fieldTwo'  => [
            'name'                       => 'SecondField',
            'type'                       => 'integer',
            'class'                      => '\DateTime',
            'readDateConverter'          => 'readDateConvertSecondField',
            'writeDateConverter'         => 'writeDateConvertSecondField',
            'fieldMappingExtensionClass' => 'ExtensionClass',
            'defaultValue'               => 12
        ],
        'dateField' => [
            'name'                       => 'DateField',
            'type'                       => 'date'
        ]
    ]
];

Getter config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Getter(
     *      name="getterName"
     * )
     */
    protected $firstField;

    /**
     * @DM\Getter(
     *      prefix="is"
     * )
     */
    protected $secondField;
}

Yaml format:

fields:
    firstField:
        name: firstField
        getter: 
            name: getterName
    secondField:
        name: secondField
        getter:
            prefix: is

Php format:

[
    'fields'    => [
        'firstField'    => [
            'name'      => 'firstField',
            'getter'    => ['name' => 'getterName'],
        ],
        'secondField'    => [
            'name'      => 'secondField',
            'getter'    => ['prefix' => 'is'],
        ],
    ]
];

Id config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Id
     */
    protected $firstField;
}

Yaml format:

id: firstField
fields: ["firstField"]

Php format:

[
    'id'        => 'firstField',
    'fields'    => ['firstField']
];

IdCompositePart config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\IdCompositePart
     */
    protected $firstField;

    /**
     * @DM\IdCompositePart
     */
    protected $secondField;
}

Yaml format:

idComposite:    [firstField, secondField]
fields:         [firstField, secondField]

Php format:

[
    'idComposite'   => ['firstField', 'secondField'],
    'fields'    => ['firstField', 'secondField']
];

Listeners config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Listeners(
 *  preHydrate = @DM\Methods({
 *      @DM\Method(class="SomeClass", method="preHydrateMethodName")   
 * }),
 *  postHydrate = @DM\Methods({
 *      @DM\Method(class="SomeClass", method="postHydrateMethodName"),
 *      @DM\Method(class="SomeClassB", method="postHydrateMethodName") 
 * }),
 *  preExtract = @DM\Methods({
 *      @DM\Method(class="SomeClass", method="preExtractMethodName", args="foo")   
 * }),
 *  postExtract = @DM\Methods({
 *      @DM\Method(class="SomeClass", method="postExtractMethodName", args={"foo", "#bar"})   
 * })
 * )
 */
class SomeClass
{
}

Yaml format:

listeners:
    preHydrate: 
        - {class: SomeClass, method: preHydrateMethodName}
    postHydrate: 
        - {class: SomeClass, method: postHydrateMethodName}
        - {class: SomeClassB, method: postHydrateMethodName}
    preExtract: 
        - {class: SomeClass, method: preExtractMethodName, args: foo}
    postExtract: 
        - {class: SomeClass, method: postExtractMethodName, args: ['foo', '#bar']}

Php format:

[
    'listeners' => [
        'preHydrate' => ['class' => 'SomeClass', 'method' => 'preHydrateMethodName'],                
        'postHydrate' => 
        [
            ['class' => 'SomeClass', 'method' => 'postHydrateMethodName'],
            ['class' => 'SomeClassB', 'method' => 'postHydrateMethodName'],
        ], 
        'preExtract' => ['class' => 'SomeClass', 'method' => 'preExtractMethodName', 'args' => 'foo'],
        'postExtract' => ['class' => 'SomeClass', 'method' => 'postExtractMethodName', 'args' => ['foo', '#bar']],
    ]
];

Method config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Method(method="someMethod")
 * @DM\Method(class="SomeClass", method="someMethod", args="abc")
 * @DM\Method(class="@some_object_created_from_factory_or_container", method="someMethod", args= {"##this", "abc"}
 */

Yaml format:

some_key:
    'method': someMethod

some_key:
    class: SomeClass
    method: someMethod
    args: abc

some_key:
    class: @some_object_created_from_factory_or_container
    method: someMethod
    args: ['##this', 'abc']

Php format:

[
    'method' => 'someMethod',
]

[
    'class' => 'SomeClass',
    'method' => 'someMethod',
    'args' => 'abc'
]

[
    'class' => '@some_object_created_from_factory_or_container',
    'method' => 'someMethod',
    'args' => ['##this', 'abc']
]

Object config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(
 *      fieldExclusionPolicy="exclude_all",
 *      providerClass="testProviderClass",
 *      readDateConverter="testReadDateConverter",
 *      writeDateConverter="testWriteDateConverter",
 *      propertyAccessStrategy=true,
 *      fieldMappingExtensionClass="testFieldMappingExtensionClass",
 *      classMappingExtensionClass="testClassMappingExtensionClass"
 * )
 */
class SomeClass
{

}

Yaml format:

fieldExclusionPolicy: exclude_all
object:
    providerClass: testProviderClass
    readDateConverter: testReadDateConverter
    writeDateConverter: testWriteDateConverter
    propertyAccessStrategy: true
    fieldMappingExtensionClass: testFieldMappingExtensionClass
    classMappingExtensionClass: testClassMappingExtensionClass

Php format:

[
    'fieldExclusionPolicy'  => 'exclude_all',
    'object'    => [
        'providerClass'         => 'testProviderClass',
        'readDateConverter'     => 'testReadDateConverter',
        'writeDateConverter'    => 'testWriteDateConverter',
        'propertyAccessStrategy'=> true,
        'fieldMappingExtensionClass' => 'testFieldMappingExtensionClass',
        'classMappingExtensionClass' => 'testClassMappingExtensionClass'
    ]
];

ObjectListeners config - DEPRECATED - SEE Listeners Config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/** 
 * @DM\ObjectListeners(
 *      classList={"SomeListenerAClass", "SomeListenerBClass"}
 * )
 */
class SomeClass
{
}

Yaml format:

objectListeners: [SomeListenerAClass, SomeListenerBClass]

Php format:

[
    'objectListeners'   => ['SomeListenerAClass', 'SomeListenerBClass']
];

PostExtract config - DEPRECATED - SEE Listeners Config

Deprecated. Use Listeners config instead.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(classMappingExtensionClass="mappingExtensionClass")
 *
 * @DM\PostExtract(method="postExtractMethodName")
 */
class SomeClass
{
}

Yaml format:

object:
    classMappingExtensionClass: mappingExtensionClass

interceptors:
    postExtract: postExtractMethodName

Php format:

[
    'object' => ['classMappingExtensionClass' => 'mappingExtensionClass'],
    'interceptors'  => [
        'postExtract'    => 'postExtractMethodName'
    ]
];

PostHydrate config - DEPRECATED - SEE Listeners Config

Deprecated. Use Listeners config instead.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(classMappingExtensionClass="mappingExtensionClass")
 *
 * @DM\PostHydrate(method="postHydrateMethodName")
 */
class SomeClass
{
}

Yaml format:

object:
    classMappingExtensionClass: mappingExtensionClass

interceptors:
    postHydrate: postHydrateMethodName

Php format:

[
    'object' => ['classMappingExtensionClass' => 'mappingExtensionClass'],
    'interceptors'  => [
        'postHydrate'    => 'postHydrateMethodName'
    ]
];

PreExtract config - DEPRECATED - SEE Listeners Config

Deprecated. Use Listeners config instead.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(classMappingExtensionClass="mappingExtensionClass")
 *
 * @DM\PreExtract(
 *      method="preExtractMethodName"
 * )
 */
class SomeClass
{
}

Yaml format:

object:
    classMappingExtensionClass: mappingExtensionClass

interceptors:
    preExtract: preExtractMethodName

Php format:

[
    'object' => ['classMappingExtensionClass' => 'mappingExtensionClass'],
    'interceptors'  => [
        'preExtract'    => 'preExtractMethodName'
    ]
];

PreHydrate config - DEPRECATED - SEE Listeners Config

Deprecated. Use Listeners config instead.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(classMappingExtensionClass="mappingExtensionClass")
 *
 * @DM\PreHydrate(method="preHydrateMethodName")
 */
class SomeClass
{
}

Yaml format:

object:
    classMappingExtensionClass: mappingExtensionClass

interceptors:
    preHydrate: preHydrateMethodName

Php format:

[
    'object' => ['classMappingExtensionClass' => 'mappingExtensionClass'],
    'interceptors'  => [
        'preHydrate'    => 'preHydrateMethodName'
    ]
];

Provider config - DEPRECATED - SEE DataSource Config

Deprecated. Use DataSource config instead. Configuration is the same as DataSource.

ProvidersStore config - DEPRECATED - SEE DataSourcesStore Config

Deprecated. Use DataSourcesStore config instead. Configuration is the same as DataSourceStore.

RefImplicitSource config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\RefImplicitSource(id="RefImplicitSourceId")
 */
class SomeClass
{

}

Yaml format:

object:
    RefImplicitSource: RefImplicitSourceId
fields:
    mockField:
        name: mockFieldName

Php format:

[
    'object' => [
        'RefImplicitSource' => 'RefImplicitSourceId'
    ],
    'fields' => [
        'mockField' => [
            'name'      => 'mockFieldName',
        ]
    ]
];

RefSource config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="someDataSource",
 *          class="Kassko\Sample\PersonDataSource",
 *          method="getData"
 *      )
 * })
 */
class SomeClass
{
    /**
     * @DM\RefSource(id="someDataSource")
     */
    private $fieldOne;
}

Yaml format:

object:
    dataSourcesStore:
        - id: personSource
        class: "Kassko\\\Sample\\\PersonDataSource"
        method: getData
fields:
    fieldOne:
        name: fieldOne
        refSource: someDataSource

Php format:

[
    'object'    => [
        'dataSourcesStore'    => [
            [
                'id'=> 'personSource',
                'class'=> 'Kassko\Sample\PersonDataSource',
                'method'=> 'getData',
            ]
        ]
    ],
    'fields'    => [
        'fieldOne' => [
            'name'  => 'fieldOne',
            'refSource' => 'someDataSource',
        ]
    ],
];
### Setter config

Annotation format:
```php
use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Setter(
     *      prefix="setterPrefix",
     *      name="setterName"
     * )
     */
    protected $firstField;
}

Yaml format:

fields:
    firstField:
        name: firstField
        setter: 
            name: setterName

Php format:

[
    'fields'    => [
        'firstField'    => [
            'name'      => 'firstField',
            'setter'    => ['name' => 'setterName'],
        ]
    ]
];

ToExclude config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * Optional because it's the default settings for fieldExclusionPolicy.
 * @DM\Object(fieldExclusionPolicy="include_all")
 */
class SomeClass
{
    /**
     * @DM\ToExclude
     */
    protected $excludedField;

    /**
     * @DM\Field
     */
    protected $anotherField;
}

Yaml format:

# Optional because it's the default settings for fieldExclusionPolicy.
# object:
#    fieldExclusionPolicy: include_all
excludedFields: [excludedField]
fields:
  excludedField:
    name: excludedField
  anotherField:
    name: anotherField

Php format:

[
    /*
    //Optional because it's the default settings for fieldExclusionPolicy.
    'object' => [
        'fieldExclusionPolicy' => 'include_all'
    ],
    */
    'excludedFields' => [
        'excludedField'
    ],
    'fields'  => [
        'excludedField' => [
            'name'     => 'excludedField'
        ],
        'anotherField'         => [
            'name'     => 'anotherField'
        ]
    ]
];

ToInclude config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(fieldExclusionPolicy="exclude_all")
 */
class SomeClass
{
    /**
     * @DM\Include
     */
    protected $includedField;

    /**
     * @DM\Field
     */
    protected $field;
}

Yaml format:

fieldExclusionPolicy: exclude_all
fieldsToInclude: [includedField]
fields:
    includedField:
        name: includedField
    anotherField:
        name: anotherField

Php format:

[
    'fieldExclusionPolicy' => 'exclude_all'
    'fieldsToInclude' => [
        'includedField'
    ],
    'fields'  => [
        'includedField' => [
            'name'     => 'includedField'
        ],
        'anotherField' => [
            'name'     => 'anotherField'
        ],
    ]
];

Transient config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Transient
     */
    protected $firstField;

    /**
     * @var string
     */
    protected $secondField;
}

Yaml format:

transient: [firstField]
fields: [firstField]

Php format:

[
    'transient' => ['firstField'],
    'fields'    => [
        'firstField'    => [
            'name'      => 'firstFieldName'
        ]
    ]
];

ValueObject config - DEPRECATED - SEE Config Config

Deprecated. Use Config config instead.

Annotation format: Annotation @Kassko\DataMapper\Annotation\ValueObject becomes @Kassko\DataMapper\Annotation\Config.

Yaml and Php format: Now, there's a field key 'fields.some_field.config'. Uses it instead of the global key 'valueObjects'.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class ValueObject
{
    /**
     * @DM\Config(
     *      class="\ValueObjectClass",
     *      mappingResourceName="valueObjectResourceName",
     *      mappingResourcePath="valueObjectResourcePath",
     *      mappingResourceType="valueObjectResourceType"
     * )
     */
    protected $firstField;
}

Yaml format:

fields:
  firstField:
    name: firstField
valueObjects:
    firstField:
        class: "\\\ValueObjectClass"
        mappingResourceName: valueObjectResourceName
        mappingResourcePath: valueObjectResourcePath
        mappingResourceType: valueObjectResourceType

Php format:

[
    'fields' => [
        'firstField' => [
            'name'         => 'firstField',
        ]
    ],
    'valueObjects' => [
        'firstField'    => [
            'class' => '\ValueObjectClass',
            'mappingResourceName' => 'valueObjectResourceName',
            'mappingResourcePath' => 'valueObjectResourcePath',
            'mappingResourceType' => 'valueObjectResourceType'
        ]
    ]
];

Version config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Version
     */
    protected $firstField;
}

Yaml format:

version: firstField
fields: [firstField]

Php format:

[
    'version'   => 'firstField',
    'fields'    => ['firstField']
];

========================================