Passed
Push — master ( 4f0280...b89b05 )
by Alexander
01:15
created

IterableDataReader::withLimit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Yiisoft\Data\Reader\Iterable;
5
6
use Traversable;
7
use Yiisoft\Arrays\ArrayHelper;
8
use Yiisoft\Data\Reader\CountableDataInterface;
9
use Yiisoft\Data\Reader\DataReaderInterface;
10
use Yiisoft\Data\Reader\Filter\FilterInterface;
11
use Yiisoft\Data\Reader\Iterable\Processor\All;
12
use Yiisoft\Data\Reader\Iterable\Processor\Any;
13
use Yiisoft\Data\Reader\Iterable\Processor\Equals;
14
use Yiisoft\Data\Reader\Iterable\Processor\GreaterThan;
15
use Yiisoft\Data\Reader\Iterable\Processor\GreaterThanOrEqual;
16
use Yiisoft\Data\Reader\Iterable\Processor\In;
17
use Yiisoft\Data\Reader\Iterable\Processor\LessThan;
18
use Yiisoft\Data\Reader\Iterable\Processor\LessThanOrEqual;
19
use Yiisoft\Data\Reader\Iterable\Processor\Like;
20
use Yiisoft\Data\Reader\Iterable\Processor\Not;
21
use Yiisoft\Data\Reader\Filter\FilterProcessorInterface;
22
use Yiisoft\Data\Reader\Iterable\Processor\IterableProcessorInterface;
23
use Yiisoft\Data\Reader\FilterableDataInterface;
24
use Yiisoft\Data\Reader\OffsetableDataInterface;
25
use Yiisoft\Data\Reader\Sort;
26
use Yiisoft\Data\Reader\SortableDataInterface;
27
28
class IterableDataReader implements DataReaderInterface, SortableDataInterface, FilterableDataInterface, OffsetableDataInterface, CountableDataInterface
29
{
30
    protected $data;
31
    private $sort;
32
33
    /**
34
     * @var FilterInterface
35
     */
36
    private $filter;
37
38
    private $limit = self::DEFAULT_LIMIT;
39
    private $offset = 0;
40
41
    /**
42
     * @var array
43
     */
44
    private $filterProcessors = [];
45
46 60
    public function __construct(iterable $data)
47
    {
48 60
        $this->data = $data;
49 60
        $this->filterProcessors = $this->withFilterProcessors(
50 60
            new All(),
51 60
            new Any(),
52 60
            new Equals(),
53 60
            new GreaterThan(),
54 60
            new GreaterThanOrEqual(),
55 60
            new In(),
56 60
            new LessThan(),
57 60
            new LessThanOrEqual(),
58 60
            new Like(),
59 60
            new Not()
60 60
        )->filterProcessors;
61 60
    }
62
63 31
    public function withSort(?Sort $sort): self
64
    {
65 31
        $new = clone $this;
66 31
        $new->sort = $sort;
67 31
        return $new;
68
    }
69
70 26
    public function getSort(): ?Sort
71
    {
72 26
        return $this->sort;
73
    }
74
75
    /**
76
     * Sorts data items according to the given sort definition.
77
     * @param iterable $items the items to be sorted
78
     * @param Sort $sort the sort definition
79
     * @return array the sorted items
80
     */
81 27
    private function sortItems(iterable $items, Sort $sort): iterable
82
    {
83 27
        $criteria = $sort->getCriteria();
84 27
        if ($criteria !== []) {
85 27
            $items = $this->iterableToArray($items);
86 27
            ArrayHelper::multisort($items, array_keys($criteria), array_values($criteria));
87
        }
88
89 27
        return $items;
90
    }
91
92 20
    protected function matchFilter(array $item, array $filter): bool
93
    {
94 20
        $operation = array_shift($filter);
95 20
        $arguments = $filter;
96
97 20
        $processor = $this->filterProcessors[$operation] ?? null;
98 20
        if ($processor === null) {
99 1
            throw new \RuntimeException(sprintf('Operation "%s" is not supported', $operation));
100
        }
101
        /* @var $processor IterableProcessorInterface */
102 19
        return $processor->match($item, $arguments, $this->filterProcessors);
103
    }
104
105 22
    public function withFilter(?FilterInterface $filter): self
106
    {
107 22
        $new = clone $this;
108 22
        $new->filter = $filter;
109 22
        return $new;
110
    }
111
112 29
    public function withLimit(int $limit): self
113
    {
114 29
        $new = clone $this;
115 29
        $new->limit = $limit;
116 29
        return $new;
117
    }
118
119 49
    public function read(): iterable
120
    {
121 49
        $filter = null;
122 49
        if ($this->filter !== null) {
123 21
            $filter = $this->filter->toArray();
124
        }
125
126 49
        $data = [];
127 49
        $skipped = 0;
128
129 49
        $sortedData = $this->sort === null
130 22
            ? $this->data
131 49
            : $this->sortItems($this->data, $this->sort);
132
133 49
        foreach ($sortedData as $item) {
134
            // do not return more than limit items
135 46
            if (count($data) === $this->limit) {
136 12
                break;
137
            }
138
139
            // skip offset items
140 46
            if ($skipped < $this->offset) {
141 3
                $skipped++;
142 3
                continue;
143
            }
144
145
            // filter items
146 46
            if ($filter === null || $this->matchFilter($item, $filter)) {
147 45
                $data[] = $item;
148
            }
149
        }
150
151 48
        return $data;
152
    }
153
154 4
    public function withOffset(int $offset): self
155
    {
156 4
        $new = clone $this;
157 4
        $new->offset = $offset;
158 4
        return $new;
159
    }
160
161 5
    public function count(): int
162
    {
163 5
        return count($this->read());
164
    }
165
166 27
    private function iterableToArray(iterable $iterable): array
167
    {
168 27
        return $iterable instanceof Traversable ? iterator_to_array($iterable, true) : (array)$iterable;
169
    }
170
171 60
    public function withFilterProcessors(FilterProcessorInterface... $filterProcessors): self
172
    {
173 60
        $new = clone $this;
174 60
        $processors = [];
175 60
        foreach ($filterProcessors as $filterProcessor) {
176 60
            if ($filterProcessor instanceof IterableProcessorInterface) {
177 60
                $processors[$filterProcessor->getOperator()] = $filterProcessor;
178
            }
179
        }
180 60
        $new->filterProcessors = array_merge($this->filterProcessors, $processors);
181 60
        return $new;
182
    }
183
}
184