PHPackages                             dazet/data-map - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. dazet/data-map

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

dazet/data-map
==============

Library for mapping data structures.

v0.2.0(6y ago)2123.7k4[1 PRs](https://github.com/dazet/data-map/pulls)MITPHPPHP &gt;=7.2

Since Dec 11Pushed 4y ago3 watchersCompare

[ Source](https://github.com/dazet/data-map)[ Packagist](https://packagist.org/packages/dazet/data-map)[ RSS](/packages/dazet-data-map/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (2)Dependencies (4)Versions (3)Used By (0)

Data Mapper
===========

[](#data-mapper)

Library for mapping and transforming data structures.

[![Build Status](https://camo.githubusercontent.com/5c8a5d15adf6c957a29034fe35b6affa7798d6f15c6687ccdc886dbe5c1b2d0b/68747470733a2f2f7472617669732d63692e6f72672f64617a65742f646174612d6d61702e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/dazet/data-map)

Defining mapper
---------------

[](#defining-mapper)

`Mapper` configuration is a description of output structure defined as association:

```
[Key1 => Getter1, Key2 => Getter2 ...]

```

`Key` defines property name in output structure and `Getter` is a function that extracts value from input.

##### Examples

[](#examples)

```
use DataMap\Getter\GetInteger;
use DataMap\Mapper;
use DataMap\Input\Input;

// Input structure is:
$input = [
    'name' => 'John',
    'surname' => 'Doe',
    'date_birth' => '1970-01-01',
    'address' => [
        'street' => 'Foo Street',
        'city' => [
            'name' => 'Bar Town',
            'country' => 'Neverland',
        ],
    ],
    'age' => '47',
];

// Required output structore is:
$output = [
    'firstName' => 'John',
    'fullName' => 'John Doe',
    'street' => 'Foo Street',
    'city' => 'Bar Town',
    'age' => 47,
    'birth' => new \DateTimeImmutable('1970-01-01'),
];

// Then mapping definition is:
$mapper = new Mapper([
    'firstName' => 'name',                          // simply get `name` from input and assign to `firstName` property
    'fullName' => function (Input $input): string {
        return $input->get('name') . ' ' . $input->get('surname');
    },                                              // use Closure as Getter function
    'street' => 'address.street',                   // get values from nested structures
    'city' => 'address.city.name',
    'age' => new GetInteger('age'),                 // use one of predefined getters
    'birth' => new GetDate('date_birth'),           // get date as `\DateTimeImmutable` object
]);

// Map $input to $output:
$output = $mapper->map($input);

// Map collection of entries:
$outputCollection = array_map($mapper, $inputCollection);

// Extend mapper definition:
$newMapper = $mapper->withAddedMap(['country' => 'address.city.country']);
```

Getter function
---------------

[](#getter-function)

`Getter` generally can be described as interface:

```
use DataMap\Input\Input;

interface Getter
{
    /**
     * @return mixed
     */
    public function __invoke(Input $input);
}
```

There are 2 forms of defining map:

- `Getter` can be string which is shorthand for `new GetRaw('key')`.
- `Getter` can also be a closure or any other callable. It will receive `DataMap\Input\Input` as first argument and original input as second argument. `Getter` interface is not required, it's just a hint.

### Predefined Getters

[](#predefined-getters)

##### `new GetRaw($key, $default)`

[](#new-getrawkey-default)

Get value by property path without additional transformation.

```
$mapper = new Mapper([
    'name' => new GetRaw('first_name'),
    // same as:
    'name' => 'first_name',
]);
```

##### `new GetString($key, $default)`

[](#new-getstringkey-default)

Gets value and casts to string (if possible) or returns `$default`.

```
$mapper = new Mapper([
    'name' => new GetString('username', 'anonymous'),
]);
```

##### `new GetInteger($key, $default)`

[](#new-getintegerkey-default)

Gets value and casts to integer (if possible) or `$default`.

```
$mapper = new Mapper([
    'age' => new GetInteger('user.age', null),
]);
```

##### `new GetFloat($key, $default)`

[](#new-getfloatkey-default)

Gets value and casts to float (if possible) or `$default`.

##### `new GetBoolean($key, $default)`

[](#new-getbooleankey-default)

Gets value and casts to boolean (`true`, `false`, `0`, `1`, `'0'`, `'1'`) or `$default`.

##### `new GetDate($key, $default)`

[](#new-getdatekey-default)

Gets value and transform to `\DateTimeImmutable` (if possible) or `$default`.

##### `new GetJoinedStrings($glue, $key1, $key2, ...)`

[](#new-getjoinedstringsglue-key1-key2-)

Gets string value for given keys an join it using `$glue`.

```
$mapper = new Mapper([
    'fullname' => new GetJoinedStrings(' ', 'user.name', 'user.surname'),
]);
```

##### `new GetMappedCollection($key, $callback)`

[](#new-getmappedcollectionkey-callback)

Gets collection under given `$key` and maps it with `$callback` or return `[]` if entry cannot be mapped.

```
$characterMapper = new Mapper([
    'fullname' => new GetJoinedStrings(' ', 'name', 'surname'),
] );

$movieMapper = new Mapper([
    'movie' => 'name',
    'characters' => new GetMappedCollection('characters', $characterMapper),
]);

$mapper->map([
    'name' => 'Lucky Luke',
    'characters' => [
        ['name' => 'Lucky', 'surname' => 'Luke'],
        ['name' => 'Joe', 'surname' => 'Dalton'],
        ['name' => 'William', 'surname' => 'Dalton'],
        ['name' => 'Jack', 'surname' => 'Dalton'],
        ['name' => 'Averell', 'surname' => 'Dalton'],
    ],
]);

// result:
[
   'movie' => 'Lucky Luke',
   'characters' => [
       ['fullname' => 'Lucky Luke'],
       ['fullname' => 'Joe Dalton'],
       ['fullname' => 'William Dalton'],
       ['fullname' => 'Jack Dalton'],
       ['fullname' => 'Averell Dalton'],
   ],
];
```

##### `new GetMappedFlatCollection($key, $callback)`

[](#new-getmappedflatcollectionkey-callback)

Similar to `GetMappedCollection` but result is flattened.

##### `new GetTranslated($key, $map, $default)`

[](#new-gettranslatedkey-map-default)

Gets value and translates it using provided associative array (`$map`) or `$default` when translation for value is not available.

```
$mapper = new Mapper([
    'agree' => new GetTranslated('agree', ['yes' => true, 'no' => false], false),
]);

$mapper->map(['agree' => 'yes']) === ['agree' => true];
$mapper->map(['agree' => 'no']) === ['agree' => false];
$mapper->map(['agree' => 'maybe']) === ['agree' => false];
```

##### `GetFiltered::from('key')->...`

[](#getfilteredfromkey-)

Gets value and transforms it through filters pipeline.

```
$mapper = new Mapper([
    'text' => GetFiltered::from('html')->string()->stripTags()->trim()->ifNull('[empty]'),
    'time' => GetFiltered::from('datetime')->dateFormat('H:i:s'),
    'date' => GetFiltered::from('time_string')->date(),
    'amount' => GetFiltered::from('amount_string')->float()->round(2),
    'amount_int' => GetFiltered::from('amount_string')->round()->int()->ifNull(0),
]);
```

Using function as filter:

```
$greeting = function (string $name): string {
    return "Hello {$name}!";
};

$mapper = new Mapper([
    'greet' => GetFiltered::from('name')->string()->with($greeting),
]);

$mapper->map(['name' => 'John']); // result: ['greet' => 'Hello John!']
```

Regular filters will not be called when value becomes `null`, with exceptions of `ifNull`, `ifEmpty` and `withNullable`.

Custom `null` handling filter:

```
$requireInt = function ($value): int {
    if (!is_int($value)) {
        throw new InvalidArgumentException('I require int!');
    }

    return $value;
};

$mapper = new Mapper([
    'must_be_int' => GetFiltered::from('number')->int()->withNullable($requireInt),
]);

$mapper->map(['number' => 'x']); // throws InvalidArgumentException
$mapper->map(['number' => 1]); // returns ['required_int' => 1]
```

`GetFiltered` has set of built-in filters similar to `FilteredInput`.

- **`with(callable $filter)`**: add to pipeline custom filter functions
- **`withNullable(callable $filter)`**: add to pipeline custom filter functions that will be called even when value has become null
- **`string()`**: try cast to string
- **`int()`**: try cast to int
- **`float()`**: try cast to float
- **`bool()`**: try cast to bool
- **`array()`**: try cast to array
- **`explode(string $delimiter)`**
- **`implode(string $glue)`**
- **`upper()`**
- **`lower()`**
- **`trim()`**
- **`format(string $template)`**: format value with `sprintf` template
- **`replace(string $search, string $replace)`**
- **`stripTags()`**
- **`numberFormat(int $decimals = 0, string $decimalPoint = '.', string $thousandsSeparator = ',')`**
- **`round(int $precision = 0)`**
- **`floor()`**
- **`ceil()`**
- **`date()`**: try cast to `DateTimeImmutable`
- **`dateFormat(string $format)`**
- **`count()`**
- **`ifNull($default)`**
- **`ifEmpty($default)`**

Input abstraction
-----------------

[](#input-abstraction)

`Input` interface defines common abstraction for accessing data from different data structures, so mapping and getters must not depend of underlying data type.

It also allows to create input decorators for additional input processing, like data filtering, transformation, traversing etc.

#### `ArrayInput`

[](#arrayinput)

Wraps associative arrays and ArrayAccess objects.

```
$array = ['one' => 1];
$input = new ArrayInput($array);

$input->get('key'); // is translated to: $array['key'] ?? null
$input->get('one'); // 1
$input->get('two'); // null
$input->get('two', 'default'); // 'default'

$input->has('one'); // true
$input->has('two'); // false
```

#### `ObjectInput`

[](#objectinput)

Wraps generic object and fetches data using object public interface: public properties or getters (a public method without parameters that returns some value).

Access method for key example `name` is resolved in the following order:

- check for public property `name`
- check for getter `name()`
- check for getter `getName()`
- check for getter `isName()`

```
class Example
{
    public $one = 1;
    private $two = 2;
    private $three = 3;

    public function two(): int
    {
        return $this->two;
    }

    public function getThree(): int
    {
        return $this->three;
    }
}

$object = new Example();
$input = new ObjectInput($object);

$input->get('one'); // 1 (public property $object->one)
$input->get('two'); // 2 (getter $object->())
$input->get('three'); // 3 (getter $object->getThree())
$input->get('four'); // null (no property, no getter)
$input->get('four', 'default'); // 'default'

$input->has('one'); // true
$input->has('four'); // false
```

#### `RecursiveInput`

[](#recursiveinput)

`RecursiveInput` allows to traverse trees od data using dot notation (`$input->get('root.branch.leaf')`). It decorates `Input` (current leaf) and requires `Wrapper` to wrap with proper `Input` next visited leafs (which can be arrays or objects).

```
class Example
{
    public $one = ['nested' => 'nested one'];

    public function two(): object
    {
        return (object)['nested' => 'nested two'];
    }
};

$innerInput = new ObjectInput(new Example());
$input = new RecursiveInput($innerInput, MixedWrapper::default());

$input->get('one'); // ['nested' => 'nested one']
$input->get('one.nested'); // 'nested one'
$input->get('one.other'); // null
$input->get('two.nested'); // 'nested two'

$input->has('one'); // true
$input->has('one.nested'); // true
$input->has('one.other'); // false
```

#### `FilteredInput`

[](#filteredinput)

`FilteredInput` is another `Input` decorator that allows to transform data after it is extracted from inner structure.

```
$innerInput = new ArrayInput([
    'amount' => 123,
    'description' => '  string  ',
    'price' => 123.1234,
]);

$input = new FilteredInput($innerInput, InputFilterParser::default());

$input->get('amount | string'); // '123'
$input->get('description | trim | upper'); // 'STRING'
$input->get('description | integer'); // null
$input->get('price | round'); // 123.0
$input->get('description | round'); // null
$input->get('price | round 2'); // 123.12
$input->get('price | ceil | integer'); // 124
```

Default input parser supports given filters:

- **`string`**: cast value to string if possible or return null |
- **`int`, `integer`**: cast to integer or return null
- **`float`**: cast to float or return null
- **`bool`, `boolean`**: resolve value as boolean or return null
- **`array`**: cast value to array if possible (from array or iterable) or return null
- **`explode [delimiter=","]`**: explode string using delimiter (`,` by default)
- **`implode [delimiter=","]`**: implode array of strings using delimiter (`,` by default)
- **`upper`**: upper case string
- **`lower`**: lower case string
- **`trim`, `ltrim`, `rtrim`**: trim string
- **`format`**: format value as string using `sprintf`
- **`replace [search] [replace=""]`**: replace substring in string like `str_replace` function
- **`strip_tags`**: same as `strip_tags` function
- **`number_format [decimals=2] [decimal_point="."] [thousands_separator=","]`**: same as `number_format` function
- **`round [precision=0]`**: same as `round` function
- **`floor`**: floor value, returns `float|null`
- **`ceil`**: floor value, returns `float|null`
- **`datetime`**: try to transform value to `DateTimeImmutable` or return null
- **`date_format [format="Y-m-d H:i:s"]`**: try to transform value to datetime and format as string or return null when value cannot be transformed
- **`date_modify [modifier]`**: try to transform value to `DateTimeImmutable` and then transform it using modifier `$datetime->modify($modifier)`
- **`timestamp`**: try to transform value to datetime and then to timestamp or return null
- **`json_encode`**: encode value to json or return null
- **`json_decode`**: decode array from json string or return null when failed
- **`count`**: return count for array or `Countable` or null when not countable
- **`if_null [then]`**: return default value when mapped value is null
- **`if_empty [then]`**: return default value when mapped value is empty

Examples

- default explode by comma: `string | explode`
- explode by custom string: `string | explode "-"`
- default implode by comma: `array | implode`
- implode by custom string: `array | implode "-"`
- format string like `sprintf`: `string | format "string: %s"`
- format money from float: `float | format "price: $%01.2f"` - transforms `12.3499` to `'price: $12.35'`
- cast to string with default value: `maybe_string | string | if_null "default"`
- cast to date and modify: `date_string | date_modify "+1 day"`
- calculate md5 of mapped value: `key | string | md5`
- wrap string after 20 characters: `key | string | wordwrap 20`
- using native function with custom argument position of mapped value `key | string | preg_replace "/\s+/" " " $$`

##### Function as transformation

[](#function-as-transformation)

Default configuration of `InputFilterParser` allows use any PHP function as transformation. By default mapped value is passed as first argument to that function optionally followed by other arguments defined in filter config. It is also possible to define different argument position of mapped value using `$$` as a placeholder.

Output formatting
-----------------

[](#output-formatting)

Mapping output type depends on `Formatter` used by `Mapper`.

Built-in formatters:

#### `ArrayFormatter`

[](#arrayformatter)

Returns associative array which is raw result of Mapper transformation.

```
$mapper = new Mapper($map);
// same as:
$mapper = new Mapper($map, new ArrayFormatter());
```

#### `ObjectConstructor`

[](#objectconstructor)

Tries to create new instance of object using regular constructor. Keys are matched with constructor parameters by variable name.

There is no value type and correctness checking, so you will get TypeError when mapped types does not match. It also fallback to `null` value when object constructor has parameter that is not in the mapping.

```
// by class constructor:
$mapper = new Mapper($map, new ObjectConstructor(SomeClass::class));

// by static method:
$mapper = new Mapper($map, new ObjectConstructor(SomeClass::class, 'method'));
```

#### `ObjectHydrator`

[](#objecthydrator)

Tries to hydrate instance of object using his public interface, that is:

- by setting public properties values
- by using setters (`setSomething` or `withSomething` assuming immutability)

```
// hydrate instance clone
$mapper = new Mapper($map, new ObjectHydrator(new SomeClass()));

// new instance from class name
$mapper = new Mapper($map, new ObjectHydrator(SomeClass::class));
```

Customizing and extending
-------------------------

[](#customizing-and-extending)

`Mapper` consists of 3 components:

- `GetterMap` that describes mapping as `string => Getter` association,
- `Wrapper` that wraps input mixed structure with proper `Input` implementation,
- `Formatter` that formats raw mapping result (associative array) to array, object, XML, JSON and so on.

```
$mapper = new Mapper($getterMap);

// which is equivalent of:
$mapper = new Mapper(
    $getterMap,
    ArrayFormatter::default(),
    FilteredWrapper::default()
);
```

#### Implement `Input` and `Wrapper` to extract data from specific sources

[](#implement-input-and-wrapper-to-extract-data-from-specific-sources)

It is possible to define data extracting for some object type explicitly.

```
interface Attributes
{
    public function getAttribute($key, $default = null);
}

class AttributesInput implements Input
{
    /** @var Attribiutes */
    private $attributes;

    public function get(string $key, $default = null)
    {
        return $this->attributes->getAttribute($key, $default);
    }
    // ...
}

class AttributesWrapper implements Wrapper
{
    public function supportedTypes(): array
    {
        return [Attributes::class]
    }

    public function wrap($data): Input
    {
        return new AttributesInput($data);
    }
}

$mapper = new Mapper(
    $getterMap,
    ArrayFormatter::default(),
    FilteredWrapper::default()->withWrappers(new AttributesWrapper())
);
```

#### Use only `MixedWrapper` for better performance

[](#use-only-mixedwrapper-for-better-performance)

By default Mapper supports nested structure fetching and value filters, which is nice but has some expense in performance (see BENCHMARK.md). But it is possible to create Mapper only with MixedWrapper when these feature are not needed.

```
$mapper = new Mapper(
    $getterMap,
    ArrayFormatter::default(),
    MixedWrapper::default()
);
```

#### Custom filters for `FilteredInput`

[](#custom-filters-for-filteredinput)

Filter functions list can be extended or overwritten with own implementation.

```
$mapper = new Mapper(
    [
        'slug' => 'title | my_replace "/[\PL]+/u" "-" | trim "-"'
    ],
    ArrayFormatter::default(),
    FilteredWrapper::default()->withFilters([
        'my_replace' => new Filter('preg_replace', ['//', '', '$$'])
    ])
);
```

#### Custom `Formatter`

[](#custom-formatter)

Custom formatter can be used to achieve better object construction performance than generic object formatters. It is also possible to create formatters creating different result types like XML, JSON etc.

```
class Person
{
    /** @var string */
    private $name;

    /** @var string */
    private $surname;

    public function __construct(string $name, string $surname)
    {
        $this->name = $name;
        $this->surname = $surname;
    }
    // ...
}

class PersonFormatter implements Formatter
{
    public function format(array $output): Person
    {
        return new Person($output['name'], $output['surname']);
    }
}

class JsonFormatter implements Formatter
{
    public function format(array $output): string
    {
        return json_encode($output);
    }
}

$map = [
    'name' => 'person.name | string',
    'surname' => 'person.surname | string',
];

$toPerson = new Mapper($map, new PersonFormatter());
$toPerson->map(['person' => ['name' => 'John', 'surname' => 'Doe']]);
// result: new Person('John', 'Doe');

$toJson = new Mapper($map, new JsonFormatter());
$toJson->map(['person' => ['name' => 'John', 'surname' => 'Doe']]);
// result: {"name":"John","surname":"Doe"};
```

###  Health Score

32

—

LowBetter than 72% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity36

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 95% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~728 days

Total

2

Last Release

2352d ago

PHP version history (2 changes)v0.1.0PHP &gt;=7.1

v0.2.0PHP &gt;=7.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/a666727b5929fa9808ae335ac65a816d5d5956b3277a3433c5994a165d5b7215?d=identicon)[dazet](/maintainers/dazet)

---

Top Contributors

[![dazet](https://avatars.githubusercontent.com/u/9936733?v=4)](https://github.com/dazet "dazet (19 commits)")[![bronek89](https://avatars.githubusercontent.com/u/3485209?v=4)](https://github.com/bronek89 "bronek89 (1 commits)")

###  Code Quality

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/dazet-data-map/health.svg)

```
[![Health](https://phpackages.com/badges/dazet-data-map/health.svg)](https://phpackages.com/packages/dazet-data-map)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
