Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
71.43% |
25 / 35 |
|
60.00% |
3 / 5 |
CRAP | |
0.00% |
0 / 1 |
FilteredSequentialIterator | |
71.43% |
25 / 35 |
|
60.00% |
3 / 5 |
25.56 | |
0.00% |
0 / 1 |
add | |
30.00% |
3 / 10 |
|
0.00% |
0 / 1 |
13.57 | |||
addFilter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIterator | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
createIterator | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
createFilter | |
78.57% |
11 / 14 |
|
0.00% |
0 / 1 |
6.35 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Notifications\Iterator; |
4 | |
5 | use ArrayIterator; |
6 | use CallbackFilterIterator; |
7 | use EmptyIterator; |
8 | use InvalidArgumentException; |
9 | use Iterator; |
10 | use IteratorAggregate; |
11 | use RecursiveIteratorIterator; |
12 | |
13 | /** |
14 | * Allows building a single iterator out of multiple iterators |
15 | * and filtering the results. Accepts plain arrays for the simple |
16 | * use case, also accepts Iterator instances for anything more complex. |
17 | * |
18 | * This exists so that UserLocator implementations can return iterators |
19 | * that return potentially thousands of users without having to grab |
20 | * them all in one giant query. |
21 | * |
22 | * Usage: |
23 | * $users = new FilteredSequentialIterator; |
24 | * $users->add( [ $userA, $userB, $userC ] ); |
25 | * |
26 | * $it = new BatchRowIterator( ... ); |
27 | * ... |
28 | * $it = new RecursiveIteratorIterator( $it ); |
29 | * $users->add( new CallbackIterator( $it, function( $row ) { |
30 | * ... |
31 | * return $user; |
32 | * } ) ); |
33 | * |
34 | * foreach ( $users as $user ) { |
35 | * ... |
36 | * } |
37 | * |
38 | * By default the BatchRowIterator returns an array of rows, this class |
39 | * expects a stream of user objects. To bridge that gap the |
40 | * RecursiveIteratorIterator is used to flatten and the CallbackIterator |
41 | * is used to transform each database $row into a User object. |
42 | * |
43 | * @todo name? |
44 | */ |
45 | class FilteredSequentialIterator implements IteratorAggregate { |
46 | /** |
47 | * @var Iterator[] |
48 | */ |
49 | protected $iterators = []; |
50 | |
51 | /** |
52 | * @var callable[] |
53 | */ |
54 | protected $filters = []; |
55 | |
56 | /** |
57 | * @param Iterator|IteratorAggregate|array $users |
58 | */ |
59 | public function add( $users ) { |
60 | if ( is_array( $users ) ) { |
61 | $it = new ArrayIterator( $users ); |
62 | } elseif ( $users instanceof Iterator ) { |
63 | $it = $users; |
64 | } elseif ( $users instanceof IteratorAggregate ) { |
65 | $it = $users->getIterator(); |
66 | } else { |
67 | throw new InvalidArgumentException( 'Expected array, Iterator or IteratorAggregate but received:' . |
68 | ( is_object( $users ) ? get_class( $users ) : gettype( $users ) ) |
69 | ); |
70 | } |
71 | |
72 | $this->iterators[] = $it; |
73 | } |
74 | |
75 | /** |
76 | * @param callable $callable |
77 | */ |
78 | public function addFilter( $callable ) { |
79 | $this->filters[] = $callable; |
80 | } |
81 | |
82 | /** |
83 | * Satisfies IteratorAggregate interface |
84 | * |
85 | * @return Iterator |
86 | */ |
87 | public function getIterator(): Iterator { |
88 | $it = $this->createIterator(); |
89 | if ( $this->filters ) { |
90 | $it = new CallbackFilterIterator( $it, $this->createFilter() ); |
91 | } |
92 | |
93 | return $it; |
94 | } |
95 | |
96 | /** |
97 | * @return Iterator |
98 | */ |
99 | protected function createIterator() { |
100 | switch ( count( $this->iterators ) ) { |
101 | case 0: |
102 | return new EmptyIterator; |
103 | |
104 | case 1: |
105 | return reset( $this->iterators ); |
106 | |
107 | default: |
108 | return new RecursiveIteratorIterator( new MultipleIterator( $this->iterators ) ); |
109 | } |
110 | } |
111 | |
112 | /** |
113 | * @return callable |
114 | */ |
115 | protected function createFilter() { |
116 | switch ( count( $this->filters ) ) { |
117 | case 0: |
118 | return static function () { |
119 | return true; |
120 | }; |
121 | |
122 | case 1: |
123 | return reset( $this->filters ); |
124 | |
125 | default: |
126 | $filters = $this->filters; |
127 | |
128 | return static function ( $user ) use ( $filters ) { |
129 | foreach ( $filters as $filter ) { |
130 | if ( !call_user_func( $filter, $user ) ) { |
131 | return false; |
132 | } |
133 | } |
134 | |
135 | return true; |
136 | }; |
137 | } |
138 | } |
139 | } |