Passed
Push — master ( 163cc9...d937aa )
by Alexander
01:28
created

IterableDataReader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

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