Skip to content

Commit dafee01

Browse files
committed
Added Regex strategy.
1 parent 39f9720 commit dafee01

File tree

8 files changed

+109
-51
lines changed

8 files changed

+109
-51
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
!/.github/
33
/vendor/
44
/composer.lock
5+
*.cache

README.md

+34-18
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Contents
3737
1. [IfExists](#ifexists)
3838
1. [Join](#join)
3939
1. [Merge](#merge)
40+
1. [Regex](#regex)
4041
1. [Replace](#replace)
4142
1. [TakeFirst](#takefirst)
4243
1. [ToList](#tolist)
@@ -227,16 +228,8 @@ class BarBucketAddressToAddresesMapping extends Mapping
227228
{
228229
return [
229230
'line1' => new Copy('Addresses->0->1'),
230-
'city' => new Callback(
231-
function (array $data) {
232-
return $this->extractCity($data['Addresses'][0][2]);
233-
}
234-
),
235-
'postcode' => new Callback(
236-
function (array $data) {
237-
return $this->extractZipCode($data['Addresses'][0][2]);
238-
}
239-
),
231+
'city' => new Callback(fn (array $data) => $this->extractCity($data['Addresses'][0][2])),
232+
'postcode' => new Regex(new Copy('Addresses->0->2'), '[.*\b(\d{5})]', 1),
240233
'country' => 'US',
241234
];
242235
}
@@ -245,19 +238,12 @@ class BarBucketAddressToAddresesMapping extends Mapping
245238
{
246239
return explode(',', $line, 2)[0];
247240
}
248-
249-
private function extractZipCode($line)
250-
{
251-
if (preg_match('[.*\b(\d{5})]', $line, $matches)) {
252-
return $matches[1];
253-
}
254-
}
255241
}
256242
```
257243

258244
*Line1* can be copied straight from the input data and *country* can be hard-coded with a constant value because we assume it does not change.
259245

260-
City and postcode must be extracted from the last line of the address. For this we use `Callback` strategies that indirectly point to private methods of our mapping. Callbacks are only necessary because there are currently no included strategies to perform string splitting or regular expression matching.
246+
City and postcode must be extracted from the last line of the address. For _city_, we use the `Callback` strategy that points to a private method of our mapping. A callback is necessary because there are currently no included strategies to perform string splitting. For _postcode_, we can use the [`Regex`](#regex) strategy.
261247

262248
The anonymous function wrapper picks the relevant part of the input data to pass to our methods. The weakness of this solution is dereferencing non-existent values will cause PHP to generate *undefined index* notices whereas injecting `Copy` strategies would gracefully resolve to `null` if any part of the path does not exist. Therefore, the most elegant solution would be to create custom strategies to promote code reuse and avoid errors, but is beyond the scope of this demonstration. For more information see [writing strategies](#writing-strategies).
263249

@@ -302,6 +288,7 @@ The following strategies ship with Mapper and provide a suite of commonly used f
302288
- [IfExists](#ifexists) – Delegates to one expression or another depending on whether the specified condition maps to null.
303289
- [Join](#join) – Joins sub-string expressions together with a glue string.
304290
- [Merge](#merge) – Merges two data sets together giving precedence to the latter if keys collide.
291+
- [Regex](#regex) – Captures a portion of a string using regular expression matching.
305292
- [Replace](#replace) – Replaces one or more substrings.
306293
- [TakeFirst](#takefirst) – Takes the first value from a collection one or more times.
307294
- [ToList](#tolist) – Converts data to a single-element list unless it is already a list.
@@ -740,6 +727,35 @@ Merge(Strategy|Mapping|array|mixed $first, Strategy|Mapping|array|mixed $second)
740727

741728
> [1, 2, 3, 3, 4, 5]
742729
730+
### Regex
731+
732+
Captures a portion of a string using regular expression matching.
733+
734+
#### Signature
735+
736+
```php
737+
Regex(Strategy|Mapping|array|mixed $expression, string $regex, int $capturingGroup = 0)
738+
```
739+
740+
1. `$expression` – Expression to search in.
741+
2. `$regex` – Regular expression, including delimiters.
742+
3. `$capturingGroup` – Optional. Capturing group index to return. Defaults to whole matched expression.
743+
744+
#### Example
745+
746+
```php
747+
(new Mapper)->map(
748+
['foo bar baz'],
749+
new Replace(
750+
new Copy(0),
751+
'[\h(.+)\h]',
752+
1,
753+
)
754+
)
755+
```
756+
757+
> 'bar'
758+
743759
### Replace
744760

745761
Replaces all occurrences of one or more substrings.

src/Strategy/Join.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<?php
22
namespace ScriptFUSION\Mapper\Strategy;
33

4+
use ScriptFUSION\Mapper\Mapping;
5+
46
/**
57
* Joins sub-string expressions together with a glue string.
68
*/
@@ -12,7 +14,8 @@ class Join extends Delegate
1214
* Initializes this instance with the specified glue to join the specified expressions together.
1315
*
1416
* @param string $glue Glue.
15-
* @param array ...$expressions Expressions to join or a single expression that resolves to an array to join.
17+
* @param Strategy|Mapping|string[]|string ...$expressions Expressions to join or a single expression that resolves
18+
* to an array to join.
1619
*/
1720
public function __construct($glue, ...$expressions)
1821
{

src/Strategy/Regex.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace ScriptFUSION\Mapper\Strategy;
5+
6+
/**
7+
* Captures a portion of a string using regular expression matching.
8+
*/
9+
final class Regex extends Delegate
10+
{
11+
public function __construct($expression, private readonly string $regex, private readonly int $capturingGroup = 0)
12+
{
13+
parent::__construct($expression);
14+
}
15+
16+
public function __invoke($data, $context = null)
17+
{
18+
if (preg_match($this->regex, parent::__invoke($data, $context), $matches)) {
19+
return $matches[$this->capturingGroup];
20+
}
21+
}
22+
}

test/Fixture/BarBucketAddressToAddresesMapping.php

+3-17
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,16 @@
44
use ScriptFUSION\Mapper\Mapping;
55
use ScriptFUSION\Mapper\Strategy\Callback;
66
use ScriptFUSION\Mapper\Strategy\Copy;
7+
use ScriptFUSION\Mapper\Strategy\Regex;
78

89
class BarBucketAddressToAddresesMapping extends Mapping
910
{
1011
protected function createMapping()
1112
{
1213
return [
1314
'line1' => new Copy('Addresses->0->1'),
14-
'city' => new Callback(
15-
function (array $data) {
16-
return $this->extractCity($data['Addresses'][0][2]);
17-
}
18-
),
19-
'postcode' => new Callback(
20-
function (array $data) {
21-
return $this->extractZipCode($data['Addresses'][0][2]);
22-
}
23-
),
15+
'city' => new Callback(fn (array $data) => $this->extractCity($data['Addresses'][0][2])),
16+
'postcode' => new Regex(new Copy('Addresses->0->2'), '[.*\b(\d{5})]', 1),
2417
'country' => 'US',
2518
];
2619
}
@@ -29,11 +22,4 @@ private function extractCity($line)
2922
{
3023
return explode(',', $line, 2)[0];
3124
}
32-
33-
private function extractZipCode($line)
34-
{
35-
if (preg_match('[.*\b(\d{5})]', $line, $matches)) {
36-
return $matches[1];
37-
}
38-
}
3925
}

test/Functional/DocumentationTest.php

+16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use ScriptFUSION\Mapper\Strategy\IfExists;
1919
use ScriptFUSION\Mapper\Strategy\Join;
2020
use ScriptFUSION\Mapper\Strategy\Merge;
21+
use ScriptFUSION\Mapper\Strategy\Regex;
2122
use ScriptFUSION\Mapper\Strategy\Replace;
2223
use ScriptFUSION\Mapper\Strategy\TakeFirst;
2324
use ScriptFUSION\Mapper\Strategy\ToList;
@@ -306,6 +307,21 @@ public function testMerge()
306307
);
307308
}
308309

310+
public function testRegex(): void
311+
{
312+
self::assertSame(
313+
'bar',
314+
(new Mapper)->map(
315+
['foo bar baz'],
316+
new Regex(
317+
new Copy(0),
318+
'[\h(.+)\h]',
319+
1,
320+
)
321+
)
322+
);
323+
}
324+
309325
public function testReplace()
310326
{
311327
self::assertSame(

test/MockFactory.php

+4-15
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,19 @@
33

44
use Mockery\MockInterface;
55
use ScriptFUSION\Mapper\Mapper;
6-
use ScriptFUSION\Mapper\Strategy\Strategy;
76
use ScriptFUSION\StaticClass;
87

98
final class MockFactory
109
{
1110
use StaticClass;
1211

13-
/**
14-
* @param mixed $data
15-
*
16-
* @return Strategy|MockInterface
17-
*/
18-
public static function mockStrategy($data)
12+
public static function mockMapper(mixed $data): Mapper&MockInterface
1913
{
20-
return \Mockery::mock(Strategy::class)->shouldReceive('__invoke')->andReturn($data)->getMock();
14+
return \Mockery::mock(Mapper::class)->shouldReceive('map')->andReturn($data)->getMock();
2115
}
2216

23-
/**
24-
* @param mixed $data
25-
*
26-
* @return Mapper|MockInterface
27-
*/
28-
public static function mockMapper($data)
17+
public static function mockMapperEcho(): Mapper&MockInterface
2918
{
30-
return \Mockery::mock(Mapper::class)->shouldReceive('map')->andReturn($data)->getMock();
19+
return \Mockery::mock(Mapper::class)->expects('map')->andReturnArg(1)->getMock();
3120
}
3221
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace ScriptFUSIONTest\Unit\Mapper\Strategy;
5+
6+
use PHPUnit\Framework\TestCase;
7+
use ScriptFUSION\Mapper\Strategy\Regex;
8+
use ScriptFUSIONTest\MockFactory;
9+
10+
final class RegexTest extends TestCase
11+
{
12+
public function testRegexMatch(): void
13+
{
14+
$regex = (new Regex('Alfa Beta Charlie', '[\h(.+)\h]', 1))->setMapper(MockFactory::mockMapperEcho());
15+
16+
self::assertSame('Beta', $regex([]));
17+
}
18+
19+
public function testRegexNonMatch(): void
20+
{
21+
$regex = (new Regex('Alfa', '[Beta]'))->setMapper(MockFactory::mockMapperEcho());
22+
23+
self::assertNull($regex([]));
24+
}
25+
}

0 commit comments

Comments
 (0)