Passed
Push — master ( f06de2...0ad1df )
by Alexander
02:43 queued 01:20
created

IterableDataReader::getSort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
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 59
    public function __construct(iterable $data)
47
    {
48 59
        $this->data = $data;
49 59
        $this->filterProcessors = $this->withFilterProcessors(
50 59
            new All(),
51 59
            new Any(),
52 59
            new Equals(),
53 59
            new GreaterThan(),
54 59
            new GreaterThanOrEqual(),
55 59
            new In(),
56 59
            new LessThan(),
57 59
            new LessThanOrEqual(),
58 59
            new Like(),
59 59
            new Not()
60 59
        )->filterProcessors;
61 59
    }
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 19
    protected function matchFilter(array $item, array $filter): bool
93
    {
94 19
        $operation = array_shift($filter);
95 19
        $arguments = $filter;
96
97 19
        $processor = $this->filterProcessors[$operation] ?? null;
98 19
        if ($processor === null) {
99
            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 21
    public function withFilter(?FilterInterface $filter): self
106
    {
107 21
        $new = clone $this;
108 21
        $new->filter = $filter;
109 21
        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 48
    public function read(): iterable
120
    {
121 48
        $filter = null;
122 48
        if ($this->filter !== null) {
123 20
            $filter = $this->filter->toArray();
124
        }
125
126 48
        $data = [];
127 48
        $skipped = 0;
128
129 48
        $sortedData = $this->sort === null
130 21
            ? $this->data
131 48
            : $this->sortItems($this->data, $this->sort);
132
133 48
        foreach ($sortedData as $item) {
134
            // do not return more than limit items
135 45
            if (count($data) === $this->limit) {
136 12
                break;
137
            }
138
139
            // skip offset items
140 45
            if ($skipped < $this->offset) {
141 3
                $skipped++;
142 3
                continue;
143
            }
144
145
            // filter items
146 45
            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 59
    public function withFilterProcessors(FilterProcessorInterface... $filterProcessors): self
172
    {
173 59
        $new = clone $this;
174 59
        $processors = [];
175 59
        foreach ($filterProcessors as $filterProcessor) {
176 59
            if ($filterProcessor instanceof IterableProcessorInterface) {
177 59
                $processors[$filterProcessor->getOperator()] = $filterProcessor;
178
            }
179
        }
180 59
        $new->filterProcessors = array_merge($this->filterProcessors, $processors);
181 59
        return $new;
182
    }
183
}
184