Skip to content

Commit 8ee3dab

Browse files
committed
- Fixed DBExprFilter: validator and validationResultHandler are only taken into account if key exists
- Limit and Offset can now take a parameter of Type OptionalValue, which can make both optional
1 parent 81361e3 commit 8ee3dab

11 files changed

+300
-48
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"psr/log": "~1"
1616
},
1717
"require-dev": {
18-
"phpunit/phpunit": "~6",
18+
"phpunit/phpunit": ">= 6.0",
1919
"phpstan/phpstan": "*@stable"
2020
},
2121
"autoload": {

src/Builder/Expr/DBExprFilter.php

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
<?php
22
namespace Kir\MySQL\Builder\Expr;
33

4+
use Kir\MySQL\Builder\Helpers\RecursiveStructureAccess;
45
use RuntimeException;
56

67
class DBExprFilter implements OptionalExpression {
78
/** @var mixed */
89
private $expression;
10+
/** @var bool */
11+
private $hasValue;
912
/** @var mixed */
1013
private $value;
1114
/** @var string[] */
@@ -24,21 +27,18 @@ class DBExprFilter implements OptionalExpression {
2427
*/
2528
public function __construct($expression, array $data, $keyPath, $validator = null, $validationResultHandler = null) {
2629
$this->expression = $expression;
27-
$this->value = $data;
2830
$this->keyPath = $this->buildKey($keyPath);
29-
$this->value = $this->recursiveGet($data, $this->keyPath, null);
31+
$this->hasValue = RecursiveStructureAccess::recursiveHas($data, $this->keyPath);
32+
$this->value = RecursiveStructureAccess::recursiveGet($data, $this->keyPath, null);
3033
if($validator === null) {
31-
$validator = function ($data) {
32-
if(is_array($data)) {
33-
return $this->isValidArray($data);
34-
}
35-
return (string) $data !== '';
34+
$validator = function() {
35+
return true;
3636
};
3737
}
38+
$this->validator = $validator;
3839
if($validationResultHandler === null) {
3940
$validationResultHandler = static function () {};
4041
}
41-
$this->validator = $validator;
4242
$this->validationResultHandler = $validationResultHandler;
4343
}
4444

@@ -53,6 +53,9 @@ public function getExpression() {
5353
* @return bool
5454
*/
5555
public function isValid() {
56+
if(!$this->hasValue) {
57+
return false;
58+
}
5659
$result = call_user_func($this->validator, $this->value);
5760
call_user_func($this->validationResultHandler, $result, [
5861
'value' => $this->value,
@@ -95,25 +98,4 @@ private function isValidArray(array $array) {
9598
});
9699
return count($data) > 0;
97100
}
98-
99-
/**
100-
* @param array $array
101-
* @param array $path
102-
* @param mixed $default
103-
* @return array
104-
*/
105-
private function recursiveGet($array, $path, $default) {
106-
$count = count($path);
107-
if (!$count) {
108-
return $default;
109-
}
110-
foreach($path as $idxValue) {
111-
$part = $idxValue;
112-
if(!array_key_exists($part, $array)) {
113-
return $default;
114-
}
115-
$array = $array[$part];
116-
}
117-
return $array;
118-
}
119101
}

src/Builder/Expr/RequiredDBFilterMap.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<?php
22
namespace Kir\MySQL\Builder\Expr;
33

4+
use Kir\MySQL\Builder\Helpers\RecursiveStructureAccess;
5+
46
class RequiredDBFilterMap {
57
/** @var array */
68
private $map;
@@ -34,10 +36,9 @@ protected function getMap(): array {
3436
* @return DBExprFilter
3537
*/
3638
public function __invoke($expression, $keyPath, $validator = null) {
37-
return new DBExprFilter($expression, $this->map, $keyPath, $validator, static function ($result, array $data) {
38-
if(!$result) {
39-
throw new RequiredValueNotFoundException(sprintf("Required value %s not found", $data['key']));
40-
}
41-
});
39+
if(!RecursiveStructureAccess::recursiveHas($this->map, $keyPath)) {
40+
throw new RequiredValueNotFoundException(sprintf("Required value %s not found", json_encode($keyPath, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)));
41+
}
42+
return new DBExprFilter($expression, $this->map, $keyPath, $validator);
4243
}
4344
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace Kir\MySQL\Builder\Helpers;
4+
5+
use ArrayObject;
6+
7+
class RecursiveStructureAccess {
8+
/**
9+
* @param object|array<string, mixed> $structure
10+
* @param string|array<int, string> $path
11+
* @return bool
12+
*/
13+
public static function recursiveHas($structure, $path): bool {
14+
$arrayPath = self::getArrayPath($path);
15+
return self::_recursiveHas($structure, $arrayPath);
16+
}
17+
18+
/**
19+
* @param object|array<string, mixed> $structure
20+
* @param string|array<int, string> $path
21+
* @param mixed $default
22+
* @return mixed
23+
*/
24+
public static function recursiveGet($structure, $path, $default) {
25+
$arrayPath = self::getArrayPath($path);
26+
$data = self::ensureArrayIsArray($structure);
27+
$count = count($arrayPath ?? []);
28+
if (!$count) {
29+
return $default;
30+
}
31+
foreach($arrayPath as $idxValue) {
32+
$part = $idxValue;
33+
if(!array_key_exists($part, $data)) {
34+
return $default;
35+
}
36+
$data = $data[$part];
37+
}
38+
return $data;
39+
}
40+
41+
/**
42+
* @param mixed $structure
43+
* @param array<int, string> $path
44+
* @return bool
45+
*/
46+
private static function _recursiveHas($structure, array $path): bool {
47+
$data = self::ensureArrayIsArray($structure);
48+
if($data === null) {
49+
return false;
50+
}
51+
if(!count($path)) {
52+
return false;
53+
}
54+
$key = array_shift($path);
55+
if(count($path)) {
56+
return self::_recursiveHas($data[$key] ?? null, $path);
57+
}
58+
return array_key_exists($key, $data);
59+
}
60+
61+
/**
62+
* @param string|array<int, string> $path
63+
* @return array<int, string>
64+
*/
65+
private static function getArrayPath($path): array {
66+
if(is_array($path)) {
67+
return $path;
68+
}
69+
return preg_split('{(?<!\\x5C)(?:\\x5C\\x5C)*\\.}', $path);
70+
}
71+
72+
/**
73+
* @param mixed $array
74+
*/
75+
private static function ensureArrayIsArray($array): ?array {
76+
if($array instanceof ArrayObject) {
77+
return $array->getArrayCopy();
78+
}
79+
if(is_object($array)) {
80+
return (array) $array;
81+
}
82+
if(is_array($array)) {
83+
return $array;
84+
}
85+
return null;
86+
}
87+
}

src/Builder/InvalidValueException.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Kir\MySQL\Builder;
4+
5+
class InvalidValueException extends Exception {
6+
7+
}

src/Builder/Traits/LimitBuilder.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
<?php
22
namespace Kir\MySQL\Builder\Traits;
33

4+
use Kir\MySQL\Builder\InvalidValueException;
5+
use Kir\MySQL\Builder\Value\OptionalValue;
6+
47
trait LimitBuilder {
5-
/** @var int|null */
8+
/** @var null|int|OptionalValue */
69
private $limit;
710

811
/**
9-
* @return int|null
12+
* @return null|int
1013
*/
1114
protected function getLimit() {
15+
if($this->limit instanceof OptionalValue) {
16+
return $this->limit->getValue();
17+
}
1218
return $this->limit;
1319
}
1420

1521
/**
16-
* @param int|null $limit
22+
* @param null|int|OptionalValue $limit
1723
* @return $this
1824
*/
1925
public function limit($limit) {
@@ -27,11 +33,19 @@ public function limit($limit) {
2733
* @return string
2834
*/
2935
protected function buildLimit($query, $offset = null) {
30-
$limit = $this->limit;
36+
$limit = $this->getLimit();
3137
if($limit === null && $offset !== null) {
3238
$limit = '18446744073709551615';
3339
}
34-
if($limit !== null) {
40+
if($this->limit instanceof OptionalValue) {
41+
if($this->limit->isValid()) {
42+
$value = $this->limit->getValue();
43+
if(!preg_match('{\\d+}', $value)) {
44+
throw new InvalidValueException('Value for LIMIT has to be a number');
45+
}
46+
$query .= "LIMIT\n\t{$this->limit->getValue()}\n";
47+
}
48+
} elseif($limit !== null) {
3549
$query .= "LIMIT\n\t{$limit}\n";
3650
}
3751
return $query;

src/Builder/Traits/OffsetBuilder.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
<?php
22
namespace Kir\MySQL\Builder\Traits;
33

4+
use Kir\MySQL\Builder\InvalidValueException;
5+
use Kir\MySQL\Builder\Value\OptionalValue;
6+
47
trait OffsetBuilder {
5-
/** @var int */
6-
private $offset;
8+
/** @var null|int|OptionalValue */
9+
private $offset = null;
710

811
/**
9-
* @return int
12+
* @return null|int
1013
*/
1114
protected function getOffset() {
15+
if($this->offset instanceof OptionalValue) {
16+
return $this->offset->getValue();
17+
}
1218
return $this->offset;
1319
}
1420

1521
/**
16-
* @param int $offset
22+
* @param null|int|OptionalValue $offset
1723
* @return $this
1824
*/
19-
public function offset($offset) {
25+
public function offset($offset = 0) {
2026
$this->offset = $offset;
2127
return $this;
2228
}
@@ -25,8 +31,17 @@ public function offset($offset) {
2531
* @param string $query
2632
* @return string
2733
*/
28-
protected function buildOffset($query) {
29-
if($this->offset !== null) {
34+
protected function buildOffset(string $query): string {
35+
$offset = $this->getOffset();
36+
if($this->offset instanceof OptionalValue) {
37+
if($this->offset->isValid()) {
38+
$value = $this->offset->getValue();
39+
if(!preg_match('{\\d+}', $value)) {
40+
throw new InvalidValueException('Value for OFFSET has to be a number');
41+
}
42+
$query .= "OFFSET\n\t{$this->offset->getValue()}\n";
43+
}
44+
} elseif($offset !== null) {
3045
$query .= "OFFSET\n\t{$this->offset}\n";
3146
}
3247
return $query;

src/Builder/Value/DBOptionalValue.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Kir\MySQL\Builder\Value;
4+
5+
use Kir\MySQL\Builder\Helpers\RecursiveStructureAccess;
6+
7+
class DBOptionalValue implements OptionalValue {
8+
private $data;
9+
/** @var string|string[] */
10+
private $path;
11+
/** @var callable|null */
12+
private $validator;
13+
14+
/**
15+
* @param object|array<string, mixed>
16+
* @param string|string[] $path
17+
* @param null|callable(): bool $validator
18+
*/
19+
public function __construct($data, $path, $validator = null) {
20+
$this->data = $data;
21+
$this->path = $path;
22+
$this->validator = $validator;
23+
}
24+
25+
/**
26+
* @return bool
27+
*/
28+
public function isValid(): bool {
29+
if(!RecursiveStructureAccess::recursiveHas($this->data, $this->path)) {
30+
return false;
31+
}
32+
if($this->validator !== null) {
33+
$value = $this->getValue();
34+
return call_user_func($this->validator, $value);
35+
}
36+
return true;
37+
}
38+
39+
/**
40+
* @return mixed|null
41+
*/
42+
public function getValue() {
43+
return RecursiveStructureAccess::recursiveGet($this->data, $this->path, null);
44+
}
45+
}

src/Builder/Value/OptionalValue.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Kir\MySQL\Builder\Value;
4+
5+
interface OptionalValue {
6+
/**
7+
* @return bool If true, the Value is present
8+
*/
9+
public function isValid(): bool;
10+
11+
/**
12+
* @return mixed
13+
*/
14+
public function getValue();
15+
}

0 commit comments

Comments
 (0)