Skip to content

Commit

Permalink
Merge fbafb1e into 8f58dd5
Browse files Browse the repository at this point in the history
  • Loading branch information
0xPaul committed Jan 6, 2017
2 parents 8f58dd5 + fbafb1e commit e0fec21
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 51 deletions.
74 changes: 53 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,29 @@ Contents
--------

1. [Mappings](#mappings)
2. [Strategies](#strategies)
3. [Practical example](#practical-example)
4. [Strategy reference](#strategy-reference)
1. [Strategies](#strategies)
1. [Practical example](#practical-example)
1. [Strategy reference](#strategy-reference)
1. [Copy](#copy)
2. [CopyContext](#copycontext)
3. [Callback](#callback)
4. [Collection](#collection)
5. [Context](#context)
6. [Either](#either)
7. [Filter](#filter)
8. [Flatten](#flatten)
9. [IfExists](#ifexists)
10. [Merge](#merge)
11. [TakeFirst](#takefirst)
12. [ToList](#tolist)
13. [TryCatch](#trycatch)
14. [Type](#type)
15. [Unique](#unique)
16. [Walk](#walk)
5. [Requirements](#requirements)
6. [Limitations](#limitations)
7. [Testing](#testing)
1. [CopyContext](#copycontext)
1. [CopyKey](#copykey)
1. [Callback](#callback)
1. [Collection](#collection)
1. [Context](#context)
1. [Either](#either)
1. [Filter](#filter)
1. [Flatten](#flatten)
1. [IfExists](#ifexists)
1. [Merge](#merge)
1. [TakeFirst](#takefirst)
1. [ToList](#tolist)
1. [TryCatch](#trycatch)
1. [Type](#type)
1. [Unique](#unique)
1. [Walk](#walk)
1. [Requirements](#requirements)
1. [Limitations](#limitations)
1. [Testing](#testing)

Mappings
--------
Expand Down Expand Up @@ -281,6 +282,7 @@ The following strategies ship with Mapper and provide a suite of commonly used f

- [Copy](#copy) – Copies a portion of input data.
- [CopyContext](#copycontext) – Copies a portion of context data.
- [CopyKey](#copykey) – Copies the current key.

#### Augmenters

Expand Down Expand Up @@ -358,6 +360,34 @@ $context = ['foo' => 456];

> 456
### CopyKey

Copies the current key from the key context. This strategy requires the key context to be set by another strategy. By default the key context is `null`. Currently only the [collection strategy](#collection) sets a key context.

#### Signature

```php
CopyKey()
```

#### Example

```php
(new Mapper)->map(
[
'foo' => [
'bar' => 'baz',
],
],
new Collection(
new Copy('foo'),
new CopyKey
)
)
```

> ['bar' => 'bar']
### Callback

Augments data using the return value of the specified callback.
Expand Down Expand Up @@ -397,6 +427,8 @@ Callback(callable $callback)

Maps a collection of data by applying a transformation to each datum using a callback. The data collection must be an expression that maps to an array otherwise null is returned.

For each item in the collection, this strategy sets the context to the current datum and the key context to the current key, which can be retrieved using [CopyKey](#copykey).

#### Signature

```php
Expand Down
14 changes: 14 additions & 0 deletions src/KeyAware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
namespace ScriptFUSION\Mapper;

interface KeyAware
{
/**
* Sets the key to the specified value.
*
* @param string|int $key Key.
*
* @return void
*/
public function setKey($key);
}
15 changes: 15 additions & 0 deletions src/KeyAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
namespace ScriptFUSION\Mapper;

trait KeyAwareTrait
{
/**
* @var string|int
*/
private $key;

public function setKey($key)
{
$this->key = $key;
}
}
52 changes: 35 additions & 17 deletions src/Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,31 @@
class Mapper
{
/**
* Maps the specified record according to the specified expression type. May be called recursively if the expression
* embeds other expressions. If context is specified it is passed to the expression and any descendant expressions.
* Maps the specified record according to the specified expression type. Optionally, used-defined contextual data
* may be passed to the expression. The record key is for internal use only and represents the current array key.
*
* May be called recursively if the expression embeds more expressions.
*
* @param array $record Record.
* @param Strategy|Mapping|array|mixed $expression Expression.
* @param mixed $context Context.
* @param mixed $context Optional. Contextual data.
* @param string|int|null $key Internal. Record key.
*
* @return mixed
*
* @throws InvalidExpressionException An invalid strategy or mapping object was specified.
*/
public function map(array $record, $expression, $context = null)
public function map(array $record, $expression, $context = null, $key = null)
{
/* Strategy. */
if ($expression instanceof Strategy) {
return $this->mapStrategy($record, $expression, $context);
return $this->mapStrategy($record, $expression, $context, $key);
} /* Mapping. */
elseif ($expression instanceof Mapping) {
return $this->mapMapping($record, $expression, $context);
return $this->mapMapping($record, $expression, $context, $key);
} /* Mapping fragment. */
elseif (is_array($expression)) {
return $this->mapFragment($record, $expression, $context);
return $this->mapFragment($record, $expression, $context, $key);
} /* Null or scalar values. */
elseif (null === $expression || is_scalar($expression)) {
return $expression;
Expand All @@ -42,15 +45,16 @@ public function map(array $record, $expression, $context = null)
/**
* @param array $record Record.
* @param Mapping $mapping Mapping.
* @param mixed $context Contextual data.
* @param mixed $context Optional. Contextual data.
* @param string|int|null $key Internal. Record key.
*
* @return array Mapped record.
*
* @throws \Exception
*/
protected function mapMapping(array $record, Mapping $mapping, $context = null)
protected function mapMapping(array $record, Mapping $mapping, $context = null, $key = null)
{
$mapped = $this->mapFragment($record, $mapping->toArray(), $context);
$mapped = $this->mapFragment($record, $mapping->toArray(), $context, $key);

if ($mapping->isWrapped()) {
// Unwrap.
Expand All @@ -60,12 +64,23 @@ protected function mapMapping(array $record, Mapping $mapping, $context = null)
return $mapped;
}

protected function mapFragment(array $record, array $fragment, $context = null)
/**
* @param array $record Record.
* @param array $fragment Mapping.
* @param null $context Optional. Contextual data.
* @param string|int|null $key Internal. Record key.
*
* @return array Mapped record.
*
* @throws \Exception Mapping failed for an unknown reason.
*/
protected function mapFragment(array $record, array $fragment, $context = null, $key = null)
{
if (array_walk(
$fragment,
function (&$strategy, $key, array $record) use ($context) {
$strategy = $this->map($record, $strategy, $context);
// Mapping fragment keys are not useful because they are hard-coded.
function (&$expression, $_, array $record) use ($context, $key) {
$expression = $this->map($record, $expression, $context, $key);
},
$record
)) {
Expand All @@ -76,14 +91,17 @@ function (&$strategy, $key, array $record) use ($context) {
}

/**
* @param array $record
* @param Strategy $strategy
* @param mixed $context
* @param array $record Record.
* @param Strategy $strategy Strategy.
* @param mixed $context Optional. Contextual data.
* @param string|int|null $key Internal. Record key.
*
* @return mixed
*/
protected function mapStrategy(array $record, Strategy $strategy, $context = null)
protected function mapStrategy(array $record, Strategy $strategy, $context = null, $key = null)
{
$strategy instanceof KeyAware && $strategy->setKey($key);

$this->injectDependencies($strategy);

return $strategy($record, $context);
Expand Down
8 changes: 5 additions & 3 deletions src/Strategy/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ public function __invoke($data, $context = null)
return null;
}

return array_map(function ($context) use ($data) {
return $this->delegate($this->transformation, $data, $context);
}, $collection);
array_walk($collection, function (&$value, $key) use ($data) {
$value = $this->delegate($this->transformation, $data, $value, $key);
});

return $collection;
}
}
18 changes: 18 additions & 0 deletions src/Strategy/CopyKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
namespace ScriptFUSION\Mapper\Strategy;

use ScriptFUSION\Mapper\KeyAware;
use ScriptFUSION\Mapper\KeyAwareTrait;

/**
* Copies the current key.
*/
class CopyKey implements Strategy, KeyAware
{
use KeyAwareTrait;

public function __invoke($data, $context = null)
{
return $this->key;
}
}
4 changes: 2 additions & 2 deletions src/Strategy/Delegate.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public function __invoke($data, $context = null)
return $this->delegate($this->expression, $data, $context);
}

protected function delegate($strategy, $data, $context)
protected function delegate($strategy, $data, $context, $key = null)
{
return $this->mapper->map($data, $strategy, $context);
return $this->mapper->map($data, $strategy, $context, $key);
}
}
19 changes: 19 additions & 0 deletions test/Functional/DocumentationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use ScriptFUSION\Mapper\Strategy\Context;
use ScriptFUSION\Mapper\Strategy\Copy;
use ScriptFUSION\Mapper\Strategy\CopyContext;
use ScriptFUSION\Mapper\Strategy\CopyKey;
use ScriptFUSION\Mapper\Strategy\Either;
use ScriptFUSION\Mapper\Strategy\Filter;
use ScriptFUSION\Mapper\Strategy\Flatten;
Expand Down Expand Up @@ -104,6 +105,24 @@ public function testCopyContext()
self::assertSame(456, (new Mapper)->map($data, new CopyContext('foo'), $context));
}

public function testCopyKey()
{
self::assertSame(
['bar' => 'bar'],
(new Mapper)->map(
[
'foo' => [
'bar' => 'baz',
],
],
new Collection(
new Copy('foo'),
new CopyKey
)
)
);
}

public function testCallback()
{
self::assertSame(
Expand Down
20 changes: 12 additions & 8 deletions test/Integration/Mapper/Strategy/CollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,26 @@ public function testCollection()
{
/** @var Collection $collection */
$collection = (new Collection(
$this->createCollection('foo'),
$this->createArray('foo'),
[
'bar' => new CopyContext('foo'),
]
))->setMapper(new Mapper);

self::assertSame($this->createCollection('bar'), $collection([]));
self::assertSame($this->createArray('bar'), $collection([]));
}

private function createCollection($key)
private function createArray($key)
{
return array_map(
function ($number) use ($key) {
return [$key => $number];
},
range(1, 10)
return array_combine(
// Keys must not change.
range('a', 'j'),
array_map(
function ($number) use ($key) {
return [$key => $number];
},
range('k', 't')
)
);
}
}
38 changes: 38 additions & 0 deletions test/Integration/Mapper/Strategy/CopyKeyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
namespace ScriptFUSIONTest\Integration\Mapper\Strategy;

use ScriptFUSION\Mapper\Mapper;
use ScriptFUSION\Mapper\Strategy\Collection;
use ScriptFUSION\Mapper\Strategy\Copy;
use ScriptFUSION\Mapper\Strategy\CopyKey;

final class CopyKeyTest extends \PHPUnit_Framework_TestCase
{
public function test()
{
$mapped = (new Mapper)->map(
[
'foo' => 'bar',
'baz' => [
'qux' => 'quux',
'corge' => 123,
],
],
[
'foo' => new CopyKey,
'bar' => new Collection(
new Copy('baz'),
new CopyKey
),
]
);

self::assertSame([
'foo' => null,
'bar' => [
'qux' => 'qux',
'corge' => 'corge',
],
], $mapped);
}
}

0 comments on commit e0fec21

Please sign in to comment.