Skip to content

Commit f501663

Browse files
committed
FC for NS case update II.
1 parent 024fe64 commit f501663

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+9778
-4
lines changed

composer.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"homepage": "https://mvorisek.cz/"
3333
}
3434
],
35-
"version": "2.3.2",
35+
"version": "2.3.5",
3636
"minimum-stability": "dev",
3737
"prefer-stable": true,
3838
"config": {
@@ -41,8 +41,8 @@
4141
"require": {
4242
"php": ">=7.3.0",
4343
"ext-intl": "*",
44-
"atk4/core": "~2.3.2",
45-
"atk4/dsql": "~2.3.2"
44+
"atk4/core": "~2.3.5",
45+
"atk4/dsql": "~2.3.5"
4646
},
4747
"require-dev": {
4848
"atk4/schema": "~2.3.0",
@@ -54,7 +54,7 @@
5454
"autoload": {
5555
"psr-4": {
5656
"atk4\\data\\": "src/",
57-
"Atk4\\Data\\": "src/"
57+
"Atk4\\Data\\": "srcAtk4/"
5858
}
5959
},
6060
"autoload-dev": {

srcAtk4/Action/Iterator.php

+315
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Atk4\Data\Action;
6+
7+
use atk4\data\Exception;
8+
use atk4\data\Field;
9+
use atk4\data\Model;
10+
11+
/**
12+
* Class Array_ is returned by $model->action(). Compatible with DSQL to a certain point as it implements
13+
* specific actions such as getOne() or get().
14+
*/
15+
class Iterator
16+
{
17+
/**
18+
* @var \ArrayIterator
19+
*/
20+
public $generator;
21+
22+
/**
23+
* Iterator constructor.
24+
*/
25+
public function __construct(array $data)
26+
{
27+
$this->generator = new \ArrayIterator($data);
28+
}
29+
30+
/**
31+
* Applies FilterIterator making sure that values of $field equal to $value.
32+
*
33+
* @param string $field
34+
* @param string $value
35+
*
36+
* @return $this
37+
*/
38+
public function filter(Model\Scope\AbstractScope $condition)
39+
{
40+
if (!$condition->isEmpty()) {
41+
// bug in php, see:
42+
// https://github.com/atk4/data/pull/735
43+
// https://bugs.php.net/bug.php?id=80125
44+
// $this->generator = new \CallbackFilterIterator($this->generator, function ($row) use ($condition) {
45+
// return $this->match($row, $condition);
46+
// });
47+
// remove code below once fixed
48+
$data = [];
49+
foreach ($this->generator as $k => $row) {
50+
if ($this->match($row, $condition)) {
51+
$data[$k] = $row;
52+
}
53+
}
54+
$this->generator = new \ArrayIterator($data);
55+
}
56+
57+
return $this;
58+
}
59+
60+
/**
61+
* Calculates SUM|AVG|MIN|MAX aggragate values for $field.
62+
*
63+
* @param string $fx
64+
* @param string $field
65+
* @param bool $coalesce
66+
*
67+
* @return \atk4\data\Action\Iterator
68+
*/
69+
public function aggregate($fx, $field, $coalesce = false)
70+
{
71+
$result = 0;
72+
$column = array_column($this->get(), $field);
73+
74+
switch (strtoupper($fx)) {
75+
case 'SUM':
76+
$result = array_sum($column);
77+
78+
break;
79+
case 'AVG':
80+
$column = $coalesce ? $column : array_filter($column, function ($value) {
81+
return $value !== null;
82+
});
83+
84+
$result = array_sum($column) / count($column);
85+
86+
break;
87+
case 'MAX':
88+
$result = max($column);
89+
90+
break;
91+
case 'MIN':
92+
$result = min($column);
93+
94+
break;
95+
default:
96+
throw (new Exception('Persistence\Array_ driver action unsupported format'))
97+
->addMoreInfo('action', $fx);
98+
}
99+
100+
$this->generator = new \ArrayIterator([[$result]]);
101+
102+
return $this;
103+
}
104+
105+
/**
106+
* Checks if $row matches $condition.
107+
*
108+
* @return bool
109+
*/
110+
protected function match(array $row, Model\Scope\AbstractScope $condition)
111+
{
112+
$match = false;
113+
114+
// simple condition
115+
if ($condition instanceof Model\Scope\Condition) {
116+
$args = $condition->toQueryArguments();
117+
118+
$field = $args[0];
119+
$operator = $args[1] ?? null;
120+
$value = $args[2] ?? null;
121+
if (count($args) === 2) {
122+
$value = $operator;
123+
124+
$operator = '=';
125+
}
126+
127+
if (!is_a($field, Field::class)) {
128+
throw (new Exception('Persistence\Array_ driver condition unsupported format'))
129+
->addMoreInfo('reason', 'Unsupported object instance ' . get_class($field))
130+
->addMoreInfo('condition', $condition);
131+
}
132+
133+
$match = $this->evaluateIf($row[$field->short_name] ?? null, $operator, $value);
134+
}
135+
136+
// nested conditions
137+
if ($condition instanceof Model\Scope) {
138+
$matches = [];
139+
140+
foreach ($condition->getNestedConditions() as $nestedCondition) {
141+
$matches[] = $subMatch = (bool) $this->match($row, $nestedCondition);
142+
143+
// do not check all conditions if any match required
144+
if ($condition->isOr() && $subMatch) {
145+
break;
146+
}
147+
}
148+
149+
// any matches && all matches the same (if all required)
150+
$match = array_filter($matches) && ($condition->isAnd() ? count(array_unique($matches)) === 1 : true);
151+
}
152+
153+
return $match;
154+
}
155+
156+
protected function evaluateIf($v1, $operator, $v2): bool
157+
{
158+
switch (strtoupper((string) $operator)) {
159+
case '=':
160+
$result = is_array($v2) ? $this->evaluateIf($v1, 'IN', $v2) : $v1 === $v2;
161+
162+
break;
163+
case '>':
164+
$result = $v1 > $v2;
165+
166+
break;
167+
case '>=':
168+
$result = $v1 >= $v2;
169+
170+
break;
171+
case '<':
172+
$result = $v1 < $v2;
173+
174+
break;
175+
case '<=':
176+
$result = $v1 <= $v2;
177+
178+
break;
179+
case '!=':
180+
case '<>':
181+
$result = !$this->evaluateIf($v1, '=', $v2);
182+
183+
break;
184+
case 'LIKE':
185+
$pattern = str_ireplace('%', '(.*?)', preg_quote($v2));
186+
187+
$result = (bool) preg_match('/^' . $pattern . '$/', (string) $v1);
188+
189+
break;
190+
case 'NOT LIKE':
191+
$result = !$this->evaluateIf($v1, 'LIKE', $v2);
192+
193+
break;
194+
case 'IN':
195+
$result = is_array($v2) ? in_array($v1, $v2, true) : $this->evaluateIf($v1, '=', $v2);
196+
197+
break;
198+
case 'NOT IN':
199+
$result = !$this->evaluateIf($v1, 'IN', $v2);
200+
201+
break;
202+
case 'REGEXP':
203+
$result = (bool) preg_match('/' . $v2 . '/', $v1);
204+
205+
break;
206+
case 'NOT REGEXP':
207+
$result = !$this->evaluateIf($v1, 'REGEXP', $v2);
208+
209+
break;
210+
default:
211+
throw (new Exception('Unsupported operator'))
212+
->addMoreInfo('operator', $operator);
213+
}
214+
215+
return $result;
216+
}
217+
218+
/**
219+
* Applies sorting on Iterator.
220+
*
221+
* @param array $fields
222+
*
223+
* @return $this
224+
*/
225+
public function order($fields)
226+
{
227+
$data = $this->get();
228+
229+
// prepare arguments for array_multisort()
230+
$args = [];
231+
foreach ($fields as [$field, $direction]) {
232+
$args[] = array_column($data, $field);
233+
$args[] = strtolower($direction) === 'desc' ? SORT_DESC : SORT_ASC;
234+
}
235+
$args[] = &$data;
236+
237+
// call sorting
238+
array_multisort(...$args);
239+
240+
// put data back in generator
241+
$this->generator = new \ArrayIterator(array_pop($args));
242+
243+
return $this;
244+
}
245+
246+
/**
247+
* Limit Iterator.
248+
*
249+
* @return $this
250+
*/
251+
public function limit(int $limit = null, int $offset = 0)
252+
{
253+
$data = array_slice($this->get(), $offset, $limit, true);
254+
255+
// put data back in generator
256+
$this->generator = new \ArrayIterator($data);
257+
258+
return $this;
259+
}
260+
261+
/**
262+
* Counts number of rows and replaces our generator with just a single number.
263+
*
264+
* @return $this
265+
*/
266+
public function count()
267+
{
268+
$this->generator = new \ArrayIterator([[iterator_count($this->generator)]]);
269+
270+
return $this;
271+
}
272+
273+
/**
274+
* Checks if iterator has any rows.
275+
*
276+
* @return $this
277+
*/
278+
public function exists()
279+
{
280+
$this->generator = new \ArrayIterator([[$this->generator->valid() ? 1 : 0]]);
281+
282+
return $this;
283+
}
284+
285+
/**
286+
* Return all data inside array.
287+
*/
288+
public function get(): array
289+
{
290+
return iterator_to_array($this->generator, true);
291+
}
292+
293+
/**
294+
* Return one row of data.
295+
*/
296+
public function getRow(): ?array
297+
{
298+
$row = $this->generator->current();
299+
$this->generator->next();
300+
301+
return $row;
302+
}
303+
304+
/**
305+
* Return one value from one row of data.
306+
*
307+
* @return mixed
308+
*/
309+
public function getOne()
310+
{
311+
$data = $this->getRow();
312+
313+
return reset($data);
314+
}
315+
}

srcAtk4/Exception.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Atk4\Data;
6+
7+
class Exception extends \atk4\core\Exception
8+
{
9+
}

0 commit comments

Comments
 (0)