Skip to content

Commit 0b306f2

Browse files
committed
Add __() and the Builder
1 parent 76a478a commit 0b306f2

File tree

8 files changed

+372
-1
lines changed

8 files changed

+372
-1
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
### 0.1.5 (2016-01-14)
4+
5+
* Added: `Builder` for prettier object/array traversal.
6+
* Added: `__()` to create a builder instance.
7+
38
### 0.1.4 (2016-01-13)
49

510
* Fixed: `getProperty($name)` was incorrectly implemented and tested.

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@ function ($object) {
5757

5858
```
5959

60+
## The Traversal Builder
61+
62+
If you want to chain together a collection of `callMethod`, `getProperty` and
63+
`getElement` calls, the `__()` function provides a builder to write this in
64+
a more elegant way.
65+
66+
Consider:
67+
68+
```php
69+
$dobs = array_map(
70+
function (User $user) {
71+
return $user->getMetaData()['dob']->format('Y-m-d');
72+
},
73+
$users
74+
);
75+
```
76+
77+
With the builder you can simply write:
78+
79+
```php
80+
use function TomPHP\Transform\__;
81+
82+
$dobs = array_map(__()->getMetaData()['dob']->format('Y-m-d'), $users);
83+
```
84+
6085
## Transformations
6186

6287
### Object Transformations

src/Builder.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
namespace TomPHP\Transform;
4+
5+
use ArrayAccess;
6+
use TomPHP\Transform\Exception\MethodNotImplementedException;
7+
8+
final class Builder implements ArrayAccess
9+
{
10+
/** @var callable[] */
11+
private $transforms = [];
12+
13+
/**
14+
* @param string $name
15+
* @param array $args
16+
*
17+
* @return self
18+
*/
19+
public function __call($name, $args)
20+
{
21+
$new = clone $this;
22+
$new->transforms[] = callMethod($name, ...$args);
23+
24+
return $new;
25+
}
26+
27+
/**
28+
* @param string $name
29+
*
30+
* @return self
31+
*/
32+
public function __get($name)
33+
{
34+
$new = clone $this;
35+
$new->transforms[] = getProperty($name);
36+
37+
return $new;
38+
}
39+
40+
/**
41+
* @internal
42+
*
43+
* @param mixed $offset
44+
*
45+
* @throws MethodNotImplementedException
46+
*/
47+
public function offsetExists($offset)
48+
{
49+
throw MethodNotImplementedException::arrayAccessReadOnly(__CLASS__);
50+
}
51+
52+
/**
53+
* @param mixed $offset
54+
*
55+
* @return \Closure
56+
*/
57+
public function offsetGet($offset)
58+
{
59+
$new = clone $this;
60+
$new->transforms[] = getElement($offset);
61+
62+
return $new;
63+
}
64+
65+
/**
66+
* @internal
67+
*
68+
* @param mixed $offset
69+
* @param mixed $value
70+
*
71+
* @throws MethodNotImplementedException
72+
*/
73+
public function offsetSet($offset, $value)
74+
{
75+
throw MethodNotImplementedException::arrayAccessReadOnly(__CLASS__);
76+
}
77+
78+
/**
79+
* @internal
80+
*
81+
* @param mixed $offset
82+
*
83+
* @throws MethodNotImplementedException
84+
*/
85+
public function offsetUnset($offset)
86+
{
87+
throw MethodNotImplementedException::arrayAccessReadOnly(__CLASS__);
88+
}
89+
90+
/**
91+
* @param mixed $value
92+
*
93+
* @return mixed
94+
*/
95+
public function __invoke($value)
96+
{
97+
$fn = chain(...$this->transforms);
98+
99+
return $fn($value);
100+
}
101+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace TomPHP\Transform\Exception;
4+
5+
use LogicException;
6+
7+
class MethodNotImplementedException extends LogicException implements Exception
8+
{
9+
/**
10+
* @param string $class
11+
*
12+
* @return self
13+
*/
14+
public static function arrayAccessReadOnly($class)
15+
{
16+
return new self("Array access on $class is read only.");
17+
}
18+
}

src/transform.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ function ($carry, $fn) {
2525
};
2626
}
2727

28+
/**
29+
* Returns a Builder instance.
30+
*
31+
* @return Builder
32+
*/
33+
function __()
34+
{
35+
return new Builder();
36+
}
37+
2838
/**
2939
* Returns a transformer which calls $methodName on its argument and returns
3040
* the result.

tests/BuilderTest.php

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<?php
2+
3+
namespace tests\TomPHP\Transform;
4+
5+
use PHPUnit_Framework_TestCase;
6+
use function TomPHP\Transform\__;
7+
use TomPHP\Transform\Exception\MethodNotImplementedException;
8+
9+
final class BuildTest extends PHPUnit_Framework_TestCase
10+
{
11+
/** @var BuilderExample */
12+
private $example;
13+
14+
protected function setUp()
15+
{
16+
$this->example = new BuilderExample();
17+
}
18+
19+
/** @test */
20+
public function it_builds_a_single_callMethod_transformation()
21+
{
22+
$fn = __()->getValue();
23+
24+
$this->assertSame('method \'getValue\' level1', $fn($this->example));
25+
}
26+
27+
/** @test */
28+
public function it_builds_a_single_callMethod_transformation_with_arguments()
29+
{
30+
$fn = __()->getArgument('test argument');
31+
32+
$this->assertSame('test argument', $fn($this->example));
33+
}
34+
35+
/** @test */
36+
public function it_builds_a_multiple_callMethod_transformation()
37+
{
38+
$fn = __()->getChild()->getValue();
39+
40+
$this->assertSame('method \'getValue\' level2', $fn($this->example));
41+
}
42+
43+
/** @test */
44+
public function it_returns_a_unique_builder_at_each_callMethod_step()
45+
{
46+
$step1 = __()->getChild();
47+
$step2 = $step1->getValue();
48+
49+
$this->assertNotSame($step1($this->example), $step2($this->example));
50+
}
51+
52+
/** @test */
53+
public function it_builds_a_getProperty_transformation()
54+
{
55+
$fn = __()->property;
56+
57+
$this->assertSame('property \'property\' level1', $fn($this->example));
58+
}
59+
60+
/** @test */
61+
public function it_builds_multiple_level_getProperty_transformation()
62+
{
63+
$fn = __()->child->property;
64+
65+
$this->assertSame('property \'property\' level2', $fn($this->example));
66+
}
67+
68+
/** @test */
69+
public function it_returns_a_unique_builder_at_each_getProperty_step()
70+
{
71+
$step1 = __()->child;
72+
$step2 = $step1->property;
73+
74+
$this->assertNotSame($step1($this->example), $step2($this->example));
75+
}
76+
77+
/** @test */
78+
public function it_builds_a_getElement_transformation()
79+
{
80+
$fn = __()['key'];
81+
82+
$this->assertSame('value', $fn(['key' => 'value']));
83+
}
84+
85+
/** @test */
86+
public function it_builds_multiple_level_getElement_transformation()
87+
{
88+
$fn = __()['user']['name'];
89+
90+
$this->assertSame('Tom', $fn(['user' => ['name' => 'Tom']]));
91+
}
92+
93+
/** @test */
94+
public function it_returns_a_unique_builder_at_each_getElement_step()
95+
{
96+
$step1 = __()['user'];
97+
$step2 = $step1['name'];
98+
99+
$example = ['user' => ['name' => 'Tom']];
100+
101+
$this->assertNotSame($step1($example), $step2($example));
102+
}
103+
104+
/** @test */
105+
public function it_throws_MethodNotImplemented_exception_for_offsetExists()
106+
{
107+
$this->setExpectedException(MethodNotImplementedException::class);
108+
109+
__()->offsetExists(1);
110+
}
111+
112+
/** @test */
113+
public function it_throws_MethodNotImplemented_exception_for_offsetSet()
114+
{
115+
$this->setExpectedException(MethodNotImplementedException::class);
116+
117+
__()->offsetSet(1, 'value');
118+
}
119+
120+
/** @test */
121+
public function it_throws_MethodNotImplemented_exception_for_offsetUnset()
122+
{
123+
$this->setExpectedException(MethodNotImplementedException::class);
124+
125+
__()->offsetUnset(1);
126+
}
127+
}
128+
129+
final class BuilderExample
130+
{
131+
/** @var int */
132+
private $level;
133+
134+
public function __construct($level = 1)
135+
{
136+
$this->level = $level;
137+
}
138+
139+
/**
140+
* @param string $name
141+
*
142+
* @return string
143+
*/
144+
public function __get($name)
145+
{
146+
if ($name === 'child') {
147+
return new self($this->level + 1);
148+
}
149+
150+
return "property '$name' level{$this->level}";
151+
}
152+
153+
/**
154+
* @return string
155+
*/
156+
public function getValue()
157+
{
158+
return "method 'getValue' level{$this->level}";
159+
}
160+
161+
/**
162+
* @return self
163+
*/
164+
public function getChild()
165+
{
166+
return new self($this->level + 1);
167+
}
168+
169+
/**
170+
* @param mixed $argument
171+
*
172+
* @return mixed
173+
*/
174+
public function getArgument($argument)
175+
{
176+
return $argument;
177+
}
178+
}

tests/Exceptions/InvalidArgumentExceptionTest.php renamed to tests/Exception/InvalidArgumentExceptionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace tests\TomPHP\Transform;
3+
namespace tests\TomPHP\Transform\Exception;
44

55
use PHPUnit_Framework_TestCase;
66
use TomPHP\Transform\Exception\Exception;

0 commit comments

Comments
 (0)