Skip to content

Commit 662ac6e

Browse files
committed
Add dot notation support
1 parent c978a4b commit 662ac6e

33 files changed

+562
-103
lines changed

README.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,12 @@ Get typed (strict mode) values from an Array / XML with basic validation.
1010
composer require wrkflow/php-get-typed-value
1111
```
1212

13-
I've created this project as part of my mission to create `work flow` tools / libraries to make my (and yours) **dev
14-
life easier and more enjoyable**.
15-
16-
Want more tools or want to help? Check [wrk-flow.com](https://wrk-flow.com) or [CONTRIBUTE](CONTRIBUTION.md) (I need
17-
help with the documentation, new features, tests).
18-
1913
## Main features
2014

21-
- 🚀 Retrieve values from Array (JSON) / XML with correct return type
22-
- 🏆 Makes PHPStan / IDE happy due the return types
23-
- 🤹‍ Validation: Ensures that desired value is in correct type (without additional loop validation. Validation is always on
24-
while calling get* method).
25-
- 🛠 Transformers: Ensures that values are in expected type (ensures that string is trimmed and empty string converted to
26-
null, accepts bool as string, can be changed.)
15+
- 🚀 Retrieve values from Array (JSON) / XML with correct return type with **dot notation** support.
16+
- 🏆 **Makes PHPStan / IDE** happy due the type strict return types.
17+
- 🤹‍ **Validation:** Ensures that desired value is in correct type (without additional loop validation).
18+
- 🛠 **Transformers:** Ensures that values are in expected type
2719

2820
```php
2921
$data = new \Wrkflow\GetValue\GetValue(new \Wrkflow\GetValue\DataHolders\ArrayData([
@@ -52,3 +44,12 @@ foreach ($items as $item) {
5244
## Documentation
5345

5446
Documentation is hosted on [GitHub Pages](https://php-get-typed-value.wrk-flow.com).
47+
48+
## Comment
49+
50+
I've created this project as part of my mission to create `work flow` tools / libraries to make my (and yours) **dev
51+
life easier and more enjoyable**.
52+
53+
Want more tools or want to help? Check [wrk-flow.com](https://wrk-flow.com) or [CONTRIBUTE](CONTRIBUTION.md). You can
54+
help me improve the documentation, add new tests and features. Are you junior developer? Don't be scared, get in touch
55+
and I will guide you in your first contribution.

docs/content/en/customization/custom-data-holder.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ position: 11
77

88
1. Create an object in DataHolders namespace
99
2. Extend `\Wrkflow\GetValue\DataHolders`
10-
3. Implement `getValue(string $key): mixed` that should return required value (make it nullable).
10+
3. Implement `getValue(string|array $key): mixed` that should return required value (make it nullable).
1111
4. Implement `public function get(): array` that should return whole data when you need it.
1212

1313
```php
@@ -19,18 +19,18 @@ namespace Wrkflow\GetValue\DataHolders;
1919

2020
class ArrayData extends AbstractData
2121
{
22-
public function __construct(private readonly array $array)
22+
public function __construct(private readonly array $flatArray)
2323
{
2424
}
2525

26-
public function getValue(string $key): mixed
26+
public function getValue(string|array $key): mixed
2727
{
28-
return $this->array[$key] ?? null;
28+
return $this->flatArray[implode('.', $key)] ?? null;
2929
}
3030

3131
public function get(): array
3232
{
33-
return $this->array;
33+
return $this->flatArray;
3434
}
3535
}
3636

docs/content/en/customization/custom-exceptions.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,25 @@ class CustomExceptionBuilder extends
1414
{
1515
public function __construct(private readonly array $customLogContext) {}
1616

17-
public function missingValue(string $key): Exception
17+
public function missingValue(string|array $key): Exception
1818
{
1919
return new MissingValueForKeyException($key, $this->customLogContext);
2020
}
2121

22-
public function arrayIsEmpty(string $key): Exception
22+
public function arrayIsEmpty(string|array $key): Exception
2323
{
2424
return new ArrayIsEmptyException($key, $this->customLogContext);
2525
}
2626

27-
public function notAnArray(string $key): Exception
27+
public function notAnArray(string|array $key): Exception
2828
{
2929
return new NotAnArrayException($key, $this->customLogContext);
3030
}
3131

3232
/**
3333
* @param class-string<RuleContract> $ruleClassName
3434
*/
35-
public function validationFailed(string $key, string $ruleClassName): Exception;
35+
public function validationFailed(string|array $key, string $ruleClassName): Exception;
3636
{
3737
return new ValidationFailedException($key, $this->customLogContext);
3838
}

docs/content/en/index.md

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ position: 1
1010

1111
## Main features
1212

13-
- 🚀 Retrieve values from Array (JSON) / XML with correct return type
14-
- 🏆 Makes PHPStan / IDE happy due the return types
15-
- 🤹‍ Validation: Ensures that desired value is in correct type (without additional loop validation. Validation is always
16-
on
17-
while calling get* method).
18-
- 🛠 Transformers: Ensures that values are in expected type (ensures that string is trimmed and empty string converted to
19-
null, accepts bool as string, can be changed.)
13+
- 🚀 Retrieve values from Array (JSON) / XML with correct return type with **safe dot notation** support.
14+
- 🏆 **Makes PHPStan / IDE** happy due the type strict return types.
15+
- 🤹‍ **Validation:** Ensures that desired value is in correct type (without additional loop validation).
16+
- 🛠 **Transformers:** Ensures that values are in expected type
2017

2118
## Installation
2219

@@ -54,6 +51,42 @@ $simpleXMLElement = new SimpleXMLElement('<root><title>test</title><test attribu
5451
$data = new \Wrkflow\GetValue\GetValue(new \Wrkflow\GetValue\DataHolders\XMLData($simpleXMLElement));
5552
```
5653

54+
## Dot notation
55+
56+
Dot notation is implemented in safe manner (may differ from Laravel and other implementations when edge cases occurs).
57+
58+
String that is separated by '.' will always be converted to path of exact path keys. If you '.' in your array key / XML
59+
node name
60+
then you need to use array as a key. Examples below:
61+
62+
```php
63+
$getValue = new \Wrkflow\GetValue\GetValue(new \Wrkflow\GetValue\DataHolders\ArrayData([
64+
'get.key' => 'test',
65+
'get' => [
66+
'key' => 'child'
67+
],
68+
'co.uk' => 'domain',
69+
]));
70+
$getValue->getString('get.key') // Returns: child
71+
$getValue->getString('co.uk') // Returns: null
72+
$getValue->getString(['get.key']) // Returns: test
73+
$getValue->getString(['co.uk']) // Returns: domain
74+
```
75+
76+
This implementation ensures.
77+
78+
You can instead of dot notation use array path:
79+
80+
```php
81+
$getValue = new \Wrkflow\GetValue\GetValue(new \Wrkflow\GetValue\DataHolders\ArrayData([
82+
'get' => [
83+
'key' => 'child'
84+
],
85+
]));
86+
$getValue->getString(['get', 'key']) // Returns: test
87+
$getValue->getString(['get', 'no_value']) // Returns: null
88+
```
89+
5790
## Values
5891

5992
> All values are validated within its type definition (int will be checked by IntegerRule, string by StringRule, etc).
@@ -73,7 +106,7 @@ Check [Validation documentation](/validation) for more.
73106
Get nullable int.
74107

75108
```php
76-
$value = $data->getInt('key', rules: [new \Wrkflow\GetValue\Rules\MinRule(0)]);
109+
$value = $data->getInt('key');
77110
```
78111

79112
Get required int value. Throws `MissingValueForKeyException` exception if missing.
@@ -100,7 +133,8 @@ $value = $data->getRequiredFloat('key');
100133

101134
### Bool
102135

103-
> Throws `ValidationFailedException` if value is not bool (only on non-null values).
136+
> **In default strategy string [bool variants](https://php-get-typed-value.wrk-flow.com/transformers/#transformtobool)
137+
are converted to bool**. Throws `ValidationFailedException` if value is not bool (only on non-null values).
104138

105139
Get nullable bool value.
106140

@@ -116,8 +150,8 @@ $value = $data->getRequiredBool('key');
116150

117151
### String
118152

119-
> Throws `ValidationFailedException` if value is not string (only on non-null values). In default strategy empty string
120-
> is treated as null.
153+
> **In default strategy string is trimmed and empty string is transformed to null**. Throws `ValidationFailedException`
154+
> if value is not string (only on non-null values).
121155
122156
Get nullable string value.
123157

@@ -209,3 +243,20 @@ missing.
209243
```php
210244
$value = $data->getRequiredArrayGetter('key');
211245
```
246+
247+
## Exceptions
248+
249+
> All exceptions receive full key that was used for getting data. You can receive it by using `$exception->getKey()`
250+
251+
- ArrayIsEmptyException
252+
- MissingValueForKeyException
253+
- NotAnArrayException
254+
- ValidationFailedException
255+
256+
## Notes
257+
258+
- **Full key format**:
259+
- Parent full key is prepended to the key with '.' separator (if the GetValue instance was constructed from parent
260+
data).
261+
- Array notation is converted to dot notation string.
262+

docs/content/en/transformers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ You can create your own transformer by extending:
176176

177177
- For array `Wrkflow\GetValue\Contracts\TransformerArrayContract`
178178
- Reset of values `Wrkflow\GetValue\Contracts\TransformerContract`
179+
- `$key` contains full path key from the root data. Array notation is converted to dot notation.
179180

180181
Then implement `public function transform(mixed $value, string $key): mixed;`. Expect invalid value and make do not
181182
transform the value if it is invalid. Just return it.

src/Actions/GetValidatedValueAction.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ public function __construct(
1919
* @param array<RuleContract> $rules
2020
* @param array<TransformerContract> $transforms
2121
*/
22-
public function execute(GetValue $getValue, string $key, array $rules, array $transforms): mixed
22+
public function execute(GetValue $getValue, string|array $key, array $rules, array $transforms): mixed
2323
{
2424
$value = $getValue->data->getValue($key);
25+
$fullKey = $getValue->data->getKey($key);
2526

2627
$afterValidationTransforms = [];
2728

2829
foreach ($transforms as $transform) {
29-
if ($rules === [] || $transform->beforeValidation(value: $value, key: $key)) {
30-
$value = $transform->transform(value: $value, key: $key, getValue: $getValue);
30+
if ($rules === [] || $transform->beforeValidation(value: $value, key: $fullKey)) {
31+
$value = $transform->transform(value: $value, key: $fullKey, getValue: $getValue);
3132
} else {
3233
$afterValidationTransforms[] = $transform;
3334
}
@@ -42,10 +43,10 @@ public function execute(GetValue $getValue, string $key, array $rules, array $tr
4243
return null;
4344
}
4445

45-
$this->validateAction->execute(rules: $rules, value: $value, key: $key);
46+
$this->validateAction->execute(rules: $rules, value: $value, key: $fullKey);
4647

4748
foreach ($afterValidationTransforms as $transform) {
48-
$value = $transform->transform(value: $value, key: $key, getValue: $getValue);
49+
$value = $transform->transform(value: $value, key: $fullKey, getValue: $getValue);
4950
}
5051

5152
return $value;

src/DataHolders/AbstractData.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,29 @@
66

77
abstract class AbstractData
88
{
9-
abstract public function getValue(string $key): mixed;
9+
public function __construct(private readonly string $parentKey = '')
10+
{
11+
}
12+
13+
abstract public function getValue(string|array $key): mixed;
1014

1115
abstract public function get(): mixed;
16+
17+
/**
18+
* Builds full key path with parent key
19+
*/
20+
public function getKey(string|array $key = ''): string
21+
{
22+
if ($key === '') {
23+
return $this->parentKey;
24+
}
25+
26+
$fullKey = is_array($key) ? implode('.', $key) : $key;
27+
28+
if ($this->parentKey === '') {
29+
return $fullKey;
30+
}
31+
32+
return $this->parentKey . '.' . $fullKey;
33+
}
1234
}

src/DataHolders/ArrayData.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,36 @@
66

77
class ArrayData extends AbstractData
88
{
9-
public function __construct(private readonly array $array)
10-
{
9+
public function __construct(
10+
private readonly array $array,
11+
string $parentKey = ''
12+
) {
13+
parent::__construct($parentKey);
1114
}
1215

13-
public function getValue(string $key): mixed
16+
public function getValue(string|array $key): mixed
1417
{
15-
return $this->array[$key] ?? null;
18+
if (is_string($key) && str_contains($key, '.')) {
19+
$key = explode('.', $key);
20+
} elseif (is_string($key)) {
21+
if (array_key_exists($key, $this->array)) {
22+
return $this->array[$key];
23+
}
24+
25+
return null;
26+
}
27+
28+
$items = $this->array;
29+
30+
foreach ($key as $segment) {
31+
if (is_array($items) === false || array_key_exists($segment, $items) === false) {
32+
return null;
33+
}
34+
35+
$items = $items[$segment];
36+
}
37+
38+
return $items;
1639
}
1740

1841
public function get(): array

src/DataHolders/XMLData.php

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,40 @@
88

99
class XMLData extends AbstractData
1010
{
11-
public function __construct(private readonly SimpleXMLElement $data)
12-
{
11+
public function __construct(
12+
private readonly SimpleXMLElement $data,
13+
string $parentKey = ''
14+
) {
15+
parent::__construct($parentKey);
1316
}
1417

15-
public function getValue(string $key): string
18+
public function getValue(string|array $key): ?string
1619
{
17-
$value = $this->data->{$key};
20+
if (is_string($key) && str_contains($key, '.')) {
21+
$key = explode('.', $key);
22+
} elseif (is_string($key)) {
23+
$value = $this->data->{$key};
24+
25+
if ($value->count() !== 0) {
26+
return (string) $value;
27+
}
28+
29+
return null;
30+
}
31+
32+
$element = $this->data;
33+
34+
foreach ($key as $segment) {
35+
$value = $element->{$segment};
36+
37+
if ($value->count() === 0) {
38+
return null;
39+
}
40+
41+
$element = $value;
42+
}
1843

19-
// TODO figure out how to properly detect "child" exists instead of getting empty string...
20-
return (string) $value;
44+
return (string) $element;
2145
}
2246

2347
public function get(): SimpleXMLElement

src/Exceptions/AbstractGetValueException.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,21 @@
55
namespace Wrkflow\GetValue\Exceptions;
66

77
use Exception;
8+
use Throwable;
89

910
abstract class AbstractGetValueException extends Exception
1011
{
12+
public function __construct(
13+
private readonly string $key,
14+
string $message,
15+
int $code = 400,
16+
?Throwable $previous = null
17+
) {
18+
parent::__construct($message, $code, $previous);
19+
}
20+
21+
public function getKey(): string
22+
{
23+
return $this->key;
24+
}
1125
}

0 commit comments

Comments
 (0)