Skip to content
This repository was archived by the owner on Dec 28, 2023. It is now read-only.

Commit 4144d6c

Browse files
committed
Implement symfony/property-access like property accessor
1 parent 64d5e71 commit 4144d6c

22 files changed

+249
-71
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Emonkak\Collection\Benchmarks\Selector;
4+
5+
use Athletic\AthleticEvent;
6+
use Emonkak\Collection\Selector\PropertySelectorParser;
7+
8+
class PropertySelectorParserEvent extends AthleticEvent
9+
{
10+
public function setUp()
11+
{
12+
$this->properySelector = PropertySelectorParser::parse('[foo]');
13+
$this->lambdaSelector = function($v) {
14+
return isset($v['foo']) ? $v['foo'] : null;
15+
};
16+
}
17+
18+
/**
19+
* @iterations 1000
20+
*/
21+
public function parse()
22+
{
23+
PropertySelectorParser::parse('[foo].bar.baz[foo].bar.baz[foo].bar.baz');
24+
}
25+
26+
/**
27+
* @iterations 1000
28+
*/
29+
public function properySelector()
30+
{
31+
assert('bar' === call_user_func($this->properySelector, ['foo' => 'bar']));
32+
}
33+
34+
/**
35+
* @iterations 1000
36+
*/
37+
public function lambdaSelector()
38+
{
39+
assert('bar' === call_user_func($this->lambdaSelector, ['foo' => 'bar']));
40+
}
41+
}

src/Collection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Emonkak\Collection\Provider\GeneratorProvider;
66
use Emonkak\Collection\Provider\ICollectionProvider;
77
use Emonkak\Collection\Provider\IteratorProvider;
8-
use Emonkak\Collection\Util\Iterators;
8+
use Emonkak\Collection\Utils\Iterators;
99

1010
class Collection implements \IteratorAggregate
1111
{

src/Comparer/ComparerResolver.php

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Comparer;
44

5-
use Emonkak\Collection\Util\Singleton;
5+
use Emonkak\Collection\Utils\Singleton;
66

77
class ComparerResolver implements IComparerResolver
88
{
@@ -15,16 +15,7 @@ private function __construct()
1515
public function resolveComparer($src)
1616
{
1717
if ($src === null) {
18-
return function($v0, $v1) {
19-
if (is_string($v0) && is_string($v1)) {
20-
return strcmp($v0, $v1);
21-
}
22-
if (is_numeric($v0) && is_numeric($v1)) {
23-
if ($v0 == $v1) return 0;
24-
return ($v0 < $v1) ? -1 : 1;
25-
}
26-
return 0;
27-
};
18+
return DefaultComparer::getInstance();
2819
}
2920
if (is_callable($src)) {
3021
return $src;

src/Comparer/DefaultComparer.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Emonkak\Collection\Comparer;
4+
5+
use Emonkak\Collection\Utils\Singleton;
6+
7+
class DefaultComparer
8+
{
9+
use Singleton;
10+
11+
private function __construct()
12+
{
13+
}
14+
15+
public function __invoke($x, $y)
16+
{
17+
if (is_string($x) && is_string($y)) {
18+
return strcmp($x, $y);
19+
}
20+
if (is_numeric($x) && is_numeric($y)) {
21+
if ($x == $y) return 0;
22+
return ($x < $y) ? -1 : 1;
23+
}
24+
return 0;
25+
}
26+
}

src/Comparer/EqualityComparer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Comparer;
44

5-
use Emonkak\Collection\Util\Singleton;
5+
use Emonkak\Collection\Utils\Singleton;
66

77
class EqualityComparer implements IEqualityComparer
88
{

src/Enumerable.php

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Emonkak\Collection\Predicate\PredicateResolver;
88
use Emonkak\Collection\Selector\KeySelectorResolver;
99
use Emonkak\Collection\Selector\SelectorResolver;
10-
use Emonkak\Collection\Util\Iterators;
10+
use Emonkak\Collection\Utils\Iterators;
1111

1212
trait Enumerable
1313
{
@@ -119,10 +119,17 @@ public function filter($predicate)
119119

120120
public function where($properties)
121121
{
122-
return $this->filter(function($x) use ($properties) {
123-
foreach ($properties as $key => $value) {
124-
if (!((isset($x->$key) && $x->$key == $value)
125-
|| (isset($x[$key]) && $x[$key] == $value))) {
122+
$predicates = [];
123+
foreach ($properties as $key => $value) {
124+
$accessor = $this->resolveSelector($key);
125+
$predicates[] = function($x) use ($accessor, $value) {
126+
return call_user_func($accessor, $x) === $value;
127+
};
128+
}
129+
130+
return $this->filter(function($x) use ($predicates) {
131+
foreach ($predicates as $predicate) {
132+
if (!call_user_func($predicate, $x)) {
126133
return false;
127134
}
128135
}
@@ -132,10 +139,17 @@ public function where($properties)
132139

133140
public function findWhere($properties)
134141
{
135-
return $this->find(function($x) use ($properties) {
136-
foreach ($properties as $key => $value) {
137-
if (!((isset($x->$key) && $x->$key == $value)
138-
|| (isset($x[$key]) && $x[$key] == $value))) {
142+
$predicates = [];
143+
foreach ($properties as $key => $value) {
144+
$accessor = $this->resolveSelector($key);
145+
$predicates[] = function($x) use ($accessor, $value) {
146+
return call_user_func($accessor, $x) === $value;
147+
};
148+
}
149+
150+
return $this->find(function($x) use ($predicates) {
151+
foreach ($predicates as $predicate) {
152+
if (!call_user_func($predicate, $x)) {
139153
return false;
140154
}
141155
}
@@ -201,15 +215,10 @@ public function invoke($method)
201215

202216
public function pluck($property)
203217
{
204-
return $this->map(function($x) use ($property) {
205-
if (isset($x->$property)) {
206-
return $x->$property;
207-
} elseif (isset($x[$property])) {
208-
return $x[$property];
209-
} else {
210-
return null;
211-
}
212-
});
218+
$xs = $this->getSource();
219+
$valueSelector = $this->resolveSelector($property);
220+
$keySelector = $this->resolveKeySelector(null);
221+
return $this->newCollection($this->getProvider()->map($xs, $valueSelector, $keySelector));
213222
}
214223

215224
/**
@@ -399,7 +408,7 @@ public function sortBy($selector)
399408
}
400409
});
401410

402-
return $this->newCollection($result)->pluck('value');
411+
return $this->newCollection($result)->pluck('[value]');
403412
});
404413
}
405414

src/Iterator/ConcatMapIterator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Iterator;
44

5-
use Emonkak\Collection\Util\Iterators;
5+
use Emonkak\Collection\Utils\Iterators;
66

77
class ConcatMapIterator implements \RecursiveIterator
88
{

src/Iterator/FlattenIterator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Iterator;
44

5-
use Emonkak\Collection\Util\Iterators;
5+
use Emonkak\Collection\Utils\Iterators;
66

77
class FlattenIterator extends \IteratorIterator implements \RecursiveIterator
88
{

src/Iterator/LazyIterator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Iterator;
44

5-
use Emonkak\Collection\Util\Iterators;
5+
use Emonkak\Collection\Utils\Iterators;
66

77
class LazyIterator implements \IteratorAggregate
88
{

src/Predicate/PredicateResolver.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
namespace Emonkak\Collection\Predicate;
44

5+
use Emonkak\Collection\Selector\PropertySelectorParser;
56
use Emonkak\Collection\Selector\ValueSelector;
6-
use Emonkak\Collection\Util\Singleton;
7+
use Emonkak\Collection\Utils\Singleton;
78

89
class PredicateResolver implements IPredicateResolver
910
{
@@ -19,10 +20,7 @@ public function resolvePredicate($src)
1920
return ValueSelector::getInstance();
2021
}
2122
if (is_string($src)) {
22-
// key or property selector
23-
return function($v) use ($src) {
24-
return is_array($v) ? $v[$src] : $v->$src;
25-
};
23+
return PropertySelectorParser::parse($src);
2624
}
2725
if (is_callable($src)) {
2826
return $src;

src/Provider/ArrayProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
use Emonkak\Collection\Comparer\EqualityComparer;
66
use Emonkak\Collection\Set;
7-
use Emonkak\Collection\Util\Iterators;
8-
use Emonkak\Collection\Util\Singleton;
7+
use Emonkak\Collection\Utils\Iterators;
8+
use Emonkak\Collection\Utils\Singleton;
99

1010
class ArrayProvider implements ICollectionProvider
1111
{

src/Provider/GeneratorProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
use Emonkak\Collection\Comparer\EqualityComparer;
66
use Emonkak\Collection\Set;
7-
use Emonkak\Collection\Util\Iterators;
8-
use Emonkak\Collection\Util\Singleton;
7+
use Emonkak\Collection\Utils\Iterators;
8+
use Emonkak\Collection\Utils\Singleton;
99

1010
class GeneratorProvider implements ICollectionProvider
1111
{

src/Provider/IteratorProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
use Emonkak\Collection\Iterator\TakeWhileIterator;
2222
use Emonkak\Collection\Iterator\UniqueIterator;
2323
use Emonkak\Collection\Iterator\ZipIterator;
24-
use Emonkak\Collection\Util\Iterators;
25-
use Emonkak\Collection\Util\Singleton;
24+
use Emonkak\Collection\Utils\Iterators;
25+
use Emonkak\Collection\Utils\Singleton;
2626

2727
class IteratorProvider implements ICollectionProvider
2828
{

src/Selector/KeySelector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Selector;
44

5-
use Emonkak\Collection\Util\Singleton;
5+
use Emonkak\Collection\Utils\Singleton;
66

77
/**
88
* Represents the Identity function.

src/Selector/KeySelectorResolver.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Selector;
44

5-
use Emonkak\Collection\Util\Singleton;
5+
use Emonkak\Collection\Utils\Singleton;
66

77
class KeySelectorResolver implements IKeySelectorResolver
88
{
@@ -18,10 +18,7 @@ public function resolveKeySelector($src)
1818
return KeySelector::getInstance();
1919
}
2020
if (is_string($src)) {
21-
// key or property selector
22-
return function($v) use ($src) {
23-
return is_array($v) ? $v[$src] : $v->$src;
24-
};
21+
return PropertySelectorParser::parse($src);
2522
}
2623
if (is_callable($src)) {
2724
return $src;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Emonkak\Collection\Selector;
4+
5+
class PropertySelectorParser
6+
{
7+
public static function parse($expr)
8+
{
9+
$accessor = '';
10+
$length = preg_match_all('/\[(\w+)\]|(?:^|(?<=.)\.)(\w+)|./', $expr, $matches);
11+
$arrayProps = $matches[1];
12+
$objectProps = $matches[2];
13+
14+
for ($i = 0; $i < $length; $i++) {
15+
if (($prop = $arrayProps[$i]) !== '') {
16+
$accessor .= self::createArrayAccessor($prop);
17+
} elseif (($prop = $objectProps[$i]) !== '') {
18+
$accessor .= self::createObjectAccessor($prop);
19+
} else {
20+
throw new \InvalidArgumentException("Failed to parse '$expr'");
21+
}
22+
}
23+
24+
$accessor .= 'return $v;';
25+
26+
return create_function('$v', $accessor);
27+
}
28+
29+
private static function createArrayAccessor($prop)
30+
{
31+
return "if(isset(\$v['$prop']))\$v=\$v['$prop'];else return null;";
32+
}
33+
34+
private static function createObjectAccessor($prop)
35+
{
36+
return "if(isset(\$v->$prop))\$v=\$v->$prop;else return null;";
37+
}
38+
}

src/Selector/SelectorResolver.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Selector;
44

5-
use Emonkak\Collection\Util\Singleton;
5+
use Emonkak\Collection\Utils\Singleton;
66

77
class SelectorResolver implements ISelectorResolver
88
{
@@ -18,10 +18,7 @@ public function resolveSelector($src)
1818
return ValueSelector::getInstance();
1919
}
2020
if (is_string($src)) {
21-
// key or property selector
22-
return function($v) use ($src) {
23-
return is_array($v) ? $v[$src] : $v->$src;
24-
};
21+
return PropertySelectorParser::parse($src);
2522
}
2623
if (is_callable($src)) {
2724
return $src;

src/Selector/ValueSelector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Emonkak\Collection\Selector;
44

5-
use Emonkak\Collection\Util\Singleton;
5+
use Emonkak\Collection\Utils\Singleton;
66

77
/**
88
* Represents the Identity function.

src/Util/Iterators.php renamed to src/Utils/Iterators.php

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

3-
namespace Emonkak\Collection\Util;
3+
namespace Emonkak\Collection\Utils;
44

55
use Emonkak\Collection\Iterator\LazyIterator;
66
use Emonkak\Collection\Iterator\MemoizeIterator;

src/Util/Singleton.php renamed to src/Utils/Singleton.php

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

3-
namespace Emonkak\Collection\Util;
3+
namespace Emonkak\Collection\Utils;
44

55
trait Singleton
66
{

0 commit comments

Comments
 (0)