From fc37d323d52472b7463d91dcc11a79a9c0d873db Mon Sep 17 00:00:00 2001 From: heqiming Date: Fri, 2 Aug 2024 16:58:02 +0800 Subject: [PATCH] support customize dot of array keys --- .gitignore | 2 ++ src/Attribute.php | 2 +- src/DataBag.php | 4 ++-- src/Helper.php | 33 ++++++++++++++++++-------------- src/Validation.php | 39 +++++++++++++++++++++++++++----------- tests/FactoryTest.php | 22 +++++++++++++++++++++ tests/HelperTest.php | 34 ++++++++++++++++++++++++++++++--- tests/UploadedFileTest.php | 2 +- 8 files changed, 106 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 06b17b2..9d07305 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,5 @@ Temporary Items .apdisk /.phpunit.result.cache /.phpunit.cache + +dev.sh diff --git a/src/Attribute.php b/src/Attribute.php index b4c9f4f..5af76c6 100644 --- a/src/Attribute.php +++ b/src/Attribute.php @@ -86,7 +86,7 @@ public function key(): string public function isUsingDotNotation(): bool { - return str_contains($this->key(), '.'); + return str_contains($this->key(), $this->validation->getKeyDot()); } public function parent(): ?Attribute diff --git a/src/DataBag.php b/src/DataBag.php index 7e4d244..eb2dfb5 100644 --- a/src/DataBag.php +++ b/src/DataBag.php @@ -86,9 +86,9 @@ public function flatten(): self return new self(Helper::arrayDot($this->data)); } - public function get(?string $key, mixed $default = null): mixed + public function get(?string $key, mixed $default = null, string $delimiter = '.'): mixed { - return Helper::arrayGet($this->data, $key, $default); + return Helper::arrayGet($this->data, $key, $default, $delimiter); } public function has(string $key): bool diff --git a/src/Helper.php b/src/Helper.php index 3a4970a..80f20ec 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -17,16 +17,17 @@ class Helper * * @param array $array * @param string $key + * @param string $delimiter * * @return bool */ - public static function arrayHas(array $array, string $key): bool + public static function arrayHas(array $array, string $key, string $delimiter = '.'): bool { if (array_key_exists($key, $array)) { return true; } - foreach (explode('.', $key) as $segment) { + foreach (explode($delimiter, $key) as $segment) { if (is_array($array) && array_key_exists($segment, $array)) { $array = $array[$segment]; } else { @@ -44,10 +45,11 @@ public static function arrayHas(array $array, string $key): bool * @param array $array * @param string|null $key * @param mixed $default + * @param string $delimiter * * @return mixed */ - public static function arrayGet(array $array, mixed $key, mixed $default = null): mixed + public static function arrayGet(array $array, mixed $key, mixed $default = null, string $delimiter = '.'): mixed { if (is_null($key)) { return $array; @@ -57,7 +59,7 @@ public static function arrayGet(array $array, mixed $key, mixed $default = null) return $array[$key]; } - foreach (explode('.', $key) as $segment) { + foreach (explode($delimiter, $key) as $segment) { if (is_array($array) && array_key_exists($segment, $array)) { $array = $array[$segment]; } else { @@ -74,16 +76,17 @@ public static function arrayGet(array $array, mixed $key, mixed $default = null) * * @param array $array * @param string $prepend + * @param string $glue * * @return array */ - public static function arrayDot(array $array, string $prepend = ''): array + public static function arrayDot(array $array, string $prepend = '', string $glue = '.'): array { $results = []; foreach ($array as $key => $value) { if (is_array($value) && !empty($value)) { - $results = array_merge($results, static::arrayDot($value, $prepend . $key . '.')); + $results = array_merge($results, static::arrayDot($value, $prepend . $key . $glue, $glue)); } else { $results[$prepend . $key] = $value; } @@ -100,10 +103,11 @@ public static function arrayDot(array $array, string $prepend = ''): array * @param string|array|null $key * @param mixed $value * @param bool $overwrite + * @param string $delimiter * * @return mixed */ - public static function arraySet(mixed &$target, mixed $key, mixed $value, bool $overwrite = true): array + public static function arraySet(mixed &$target, mixed $key, mixed $value, bool $overwrite = true, string $delimiter = '.'): array { if (is_null($key)) { if ($overwrite) { @@ -113,7 +117,7 @@ public static function arraySet(mixed &$target, mixed $key, mixed $value, bool $ return $target = array_merge($value, $target); } - $segments = is_array($key) ? $key : explode('.', $key); + $segments = is_array($key) ? $key : explode($delimiter, $key); if (($segment = array_shift($segments)) === '*') { if (!is_array($target)) { @@ -122,7 +126,7 @@ public static function arraySet(mixed &$target, mixed $key, mixed $value, bool $ if ($segments) { foreach ($target as &$inner) { - static::arraySet($inner, $segments, $value, $overwrite); + static::arraySet($inner, $segments, $value, $overwrite, $delimiter); } } elseif ($overwrite) { foreach ($target as &$inner) { @@ -135,7 +139,7 @@ public static function arraySet(mixed &$target, mixed $key, mixed $value, bool $ $target[$segment] = []; } - static::arraySet($target[$segment], $segments, $value, $overwrite); + static::arraySet($target[$segment], $segments, $value, $overwrite, $delimiter); } elseif ($overwrite || !array_key_exists($segment, $target)) { $target[$segment] = $value; } @@ -143,7 +147,7 @@ public static function arraySet(mixed &$target, mixed $key, mixed $value, bool $ $target = []; if ($segments) { - static::arraySet($target[$segment], $segments, $value, $overwrite); + static::arraySet($target[$segment], $segments, $value, $overwrite, $delimiter); } elseif ($overwrite) { $target[$segment] = $value; } @@ -157,23 +161,24 @@ public static function arraySet(mixed &$target, mixed $key, mixed $value, bool $ * * @param mixed $target * @param string|array $key + * @param string $delimiter * * @return mixed */ - public static function arrayUnset(mixed &$target, string|array $key): mixed + public static function arrayUnset(mixed &$target, string|array $key, string $delimiter = '.'): mixed { if (!is_array($target)) { return $target; } - $segments = is_array($key) ? $key : explode('.', $key); + $segments = is_array($key) ? $key : explode($delimiter, $key); $segment = array_shift($segments); if ($segment == '*') { $target = []; } elseif ($segments) { if (array_key_exists($segment, $target)) { - static::arrayUnset($target[$segment], $segments); + static::arrayUnset($target[$segment], $segments, $delimiter); } } elseif (array_key_exists($segment, $target)) { unset($target[$segment]); diff --git a/src/Validation.php b/src/Validation.php index 473cb7a..3ebfed6 100644 --- a/src/Validation.php +++ b/src/Validation.php @@ -43,6 +43,7 @@ class Validation private array $invalidData = []; private string $separator = ':'; private ?string $lang = null; + private string $keyDot = '.'; public function __construct(Factory $factory, array $inputs, array $rules) { @@ -223,8 +224,8 @@ protected function isArrayAttribute(Attribute $attribute): bool protected function parseArrayAttribute(Attribute $attribute): array { $attributeKey = $attribute->key(); - $data = Helper::arrayDot($this->initializeAttributeOnData($attributeKey)); - $pattern = str_replace('\*', '([^\.]+)', preg_quote($attributeKey)); + $data = Helper::arrayDot($this->initializeAttributeOnData($attributeKey), glue: $this->keyDot); + $pattern = str_replace('\*', '([^('. preg_quote($this->keyDot) .')]+)', preg_quote($attributeKey)); $data = array_merge($data, $this->extractValuesForWildcards( $data, @@ -262,7 +263,7 @@ protected function initializeAttributeOnData(string $attributeKey): array return $data; } - return Helper::arraySet($data, $attributeKey, null); + return Helper::arraySet($data, $attributeKey, null, delimiter: $this->keyDot); } /** @@ -276,7 +277,11 @@ protected function initializeAttributeOnData(string $attributeKey): array */ protected function getLeadingExplicitAttributePath(string $attributeKey): ?string { - return rtrim(explode('*', $attributeKey)[0], '.') ?: null; + return preg_replace( + '/('. preg_quote($this->keyDot) .')*$/', + '', + explode('*', $attributeKey)[0], + ) ?: null; } /** @@ -290,10 +295,10 @@ protected function extractDataFromPath(?string $attributeKey): array { $results = []; - $value = $this->input->get($attributeKey, '__missing__'); + $value = $this->input->get($attributeKey, '__missing__', $this->keyDot); if ($value != '__missing__') { - Helper::arraySet($results, $attributeKey, $value); + Helper::arraySet($results, $attributeKey, $value, delimiter: $this->keyDot); } return $results; @@ -308,7 +313,7 @@ private function extractValuesForWildcards(array $data, string $attributeKey): a { $keys = []; - $pattern = str_replace('\*', '[^\.]+', preg_quote($attributeKey)); + $pattern = str_replace('\*', '[^\('. preg_quote($this->keyDot) .')]+', preg_quote($attributeKey)); foreach ($data as $key => $value) { if (preg_match('/^' . $pattern . '/', $key, $matches)) { @@ -427,8 +432,8 @@ protected function setValidData(Attribute $attribute, $value): void $key = $attribute->key(); if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) { - Helper::arraySet($this->validData, $key, $value); - Helper::arrayUnset($this->invalidData, $key); + Helper::arraySet($this->validData, $key, $value, delimiter: $this->keyDot); + Helper::arrayUnset($this->invalidData, $key, delimiter: $this->keyDot); } else { $this->validData[$key] = $value; } @@ -444,10 +449,22 @@ protected function setInvalidData(Attribute $attribute, $value): void $key = $attribute->key(); if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) { - Helper::arraySet($this->invalidData, $key, $value); - Helper::arrayUnset($this->validData, $key); + Helper::arraySet($this->invalidData, $key, $value, delimiter: $this->keyDot); + Helper::arrayUnset($this->validData, $key, delimiter: $this->keyDot); } else { $this->invalidData[$key] = $value; } } + + public function setKeyDot(string $keyDot): static + { + $this->keyDot = $keyDot; + + return $this; + } + + public function getKeyDot(): string + { + return $this->keyDot; + } } diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index f3244a9..4e5ad9b 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -750,4 +750,26 @@ public function testLoadMessagesFromFile() $this->assertEquals('yar, number neigh thar', $validation->errors()->first('number')); } + + public function testCustomizedDelimiterValidation() + { + $inputs = [ + 'foo' => [ + 'bar' => 'text', + 'baz' => 123, + 'qux' => 'long text' + ], + 'foo.bar' => 999, + 'foo/jaz' => 'jaz_text', + ]; + $rules = [ + 'foo/*' => 'required|max:1', + 'foo.bar' => 'required|integer', + 'foo/jaz' => 'required', + ]; + $validation = $this->validator->make($inputs, $rules); + $validation->setKeyDot('/'); + + $this->assertTrue($validation->passes()); + } } diff --git a/tests/HelperTest.php b/tests/HelperTest.php index e652963..05c40ed 100644 --- a/tests/HelperTest.php +++ b/tests/HelperTest.php @@ -23,11 +23,13 @@ public function testArrayHas() $this->assertTrue(Helper::arrayHas($array, 'foo.bar')); $this->assertTrue(Helper::arrayHas($array, 'foo.bar.baz')); $this->assertTrue(Helper::arrayHas($array, 'one.two.three')); + $this->assertTrue(Helper::arrayHas($array, 'foo/bar/baz', '/')); $this->assertFalse(Helper::arrayHas($array, 'foo.baz')); $this->assertFalse(Helper::arrayHas($array, 'bar.baz')); $this->assertFalse(Helper::arrayHas($array, 'foo.bar.qux')); $this->assertFalse(Helper::arrayHas($array, 'one.two')); + $this->assertFalse(Helper::arrayHas($array, 'one/two', '/')); } public function testArrayGet() @@ -48,6 +50,10 @@ public function testArrayGet() $this->assertNull(Helper::arrayGet($array, 'foo.bar.baz.qux')); $this->assertNull(Helper::arrayGet($array, 'one.two')); + + $this->assertEquals(Helper::arrayGet($array, 'foo', delimiter: '/'), $array['foo']); + $this->assertEquals(Helper::arrayGet($array, 'foo/bar', delimiter: '/'), $array['foo']['bar']); + $this->assertEquals(123, Helper::arrayGet($array, 'one.two.three', delimiter: '/')); } public function testArrayDot() @@ -78,6 +84,18 @@ public function testArrayDot() 'comments.2.text' => 'baz', 'one.two.three' => 789 ], Helper::arrayDot($array)); + + $this->assertEquals([ + 'foo/bar/baz' => 123, + 'foo/bar/qux' => 456, + 'comments/0/id' => 1, + 'comments/0/text' => 'foo', + 'comments/1/id' => 2, + 'comments/1/text' => 'bar', + 'comments/2/id' => 3, + 'comments/2/text' => 'baz', + 'one.two.three' => 789 + ], Helper::arrayDot($array, glue: '/')); } public function testArraySet() @@ -87,17 +105,20 @@ public function testArraySet() ['text' => 'foo'], ['id' => 2, 'text' => 'bar'], ['id' => 3, 'text' => 'baz'], + ['bar' => 'jax'], ] ]; Helper::arraySet($array, 'comments.*.id', null, false); Helper::arraySet($array, 'comments.*.x.y', 1, false); + Helper::arraySet($array, 'comments/*/bar', 'flux', true, '/'); $this->assertEquals([ 'comments' => [ - ['id' => null, 'text' => 'foo', 'x' => ['y' => 1]], - ['id' => 2, 'text' => 'bar', 'x' => ['y' => 1]], - ['id' => 3, 'text' => 'baz', 'x' => ['y' => 1]], + ['id' => null, 'text' => 'foo', 'x' => ['y' => 1], 'bar' => 'flux'], + ['id' => 2, 'text' => 'bar', 'x' => ['y' => 1], 'bar' => 'flux'], + ['id' => 3, 'text' => 'baz', 'x' => ['y' => 1], 'bar' => 'flux'], + ['id' => null, 'x' => ['y' => 1], 'bar' => 'flux'], ] ], $array); } @@ -130,6 +151,13 @@ public function testArrayUnset() 'stuffs' => [], 'message' => "lorem ipsum", ], $array); + + Helper::arrayUnset($array, 'users/*', '/'); + $this->assertEquals([ + 'users' => [], + 'stuffs' => [], + 'message' => "lorem ipsum", + ], $array); } public function testJoin() diff --git a/tests/UploadedFileTest.php b/tests/UploadedFileTest.php index 1aed50e..3473f98 100644 --- a/tests/UploadedFileTest.php +++ b/tests/UploadedFileTest.php @@ -89,7 +89,7 @@ public static function getSamplesMissingKeyFromUploadedFileValue(): array foreach ($validUploadedFile as $key => $value) { $uploadedFile = $validUploadedFile; unset($uploadedFile[$key]); - $samples[] = $uploadedFile; + $samples[] = [$uploadedFile]; } return $samples;