generated from yiisoft/package-template
-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
ToArrayOfStrings
parameter attribute (#94)
- Loading branch information
Showing
5 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Yiisoft\Hydrator\Attribute\Parameter; | ||
|
||
use Attribute; | ||
|
||
/** | ||
* Casts the resolved value to array of strings. | ||
*/ | ||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)] | ||
final class ToArrayOfStrings implements ParameterAttributeInterface | ||
{ | ||
/** | ||
* @param bool $trim Trim each string of array. | ||
* @param bool $removeEmpty Remove empty strings from array. | ||
* @param bool $splitResolvedValue Split non-array resolved value to array of strings by {@see $separator}. | ||
* @param string $separator The boundary string. It is a part of regular expression | ||
* so should be taken into account or properly escaped with {@see preg_quote()}. | ||
*/ | ||
public function __construct( | ||
public readonly bool $trim = false, | ||
public readonly bool $removeEmpty = false, | ||
public readonly bool $splitResolvedValue = true, | ||
public readonly string $separator = '\R', | ||
) { | ||
} | ||
|
||
public function getResolver(): string | ||
{ | ||
return ToArrayOfStringsResolver::class; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Yiisoft\Hydrator\Attribute\Parameter; | ||
|
||
use Stringable; | ||
use Traversable; | ||
use Yiisoft\Hydrator\AttributeHandling\Exception\UnexpectedAttributeException; | ||
use Yiisoft\Hydrator\AttributeHandling\ParameterAttributeResolveContext; | ||
use Yiisoft\Hydrator\Result; | ||
|
||
final class ToArrayOfStringsResolver implements ParameterAttributeResolverInterface | ||
{ | ||
public function getParameterValue( | ||
ParameterAttributeInterface $attribute, | ||
ParameterAttributeResolveContext $context | ||
): Result { | ||
if (!$attribute instanceof ToArrayOfStrings) { | ||
throw new UnexpectedAttributeException(ToArrayOfStrings::class, $attribute); | ||
} | ||
|
||
if (!$context->isResolved()) { | ||
return Result::fail(); | ||
} | ||
|
||
$resolvedValue = $context->getResolvedValue(); | ||
if (is_iterable($resolvedValue)) { | ||
$array = array_map( | ||
$this->castValueToString(...), | ||
$resolvedValue instanceof Traversable ? iterator_to_array($resolvedValue) : $resolvedValue | ||
); | ||
} else { | ||
$value = $this->castValueToString($resolvedValue); | ||
$array = $attribute->splitResolvedValue | ||
? preg_split('~' . $attribute->separator . '~u', $value) | ||
: [$value]; | ||
} | ||
|
||
if ($attribute->trim) { | ||
$array = array_map(trim(...), $array); | ||
} | ||
|
||
if ($attribute->removeEmpty) { | ||
$array = array_filter( | ||
$array, | ||
static fn(string $value): bool => $value !== '', | ||
); | ||
} | ||
|
||
return Result::success($array); | ||
} | ||
|
||
private function castValueToString(mixed $value): string | ||
{ | ||
return is_scalar($value) || $value instanceof Stringable ? (string) $value : ''; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Yiisoft\Hydrator\Tests\Attribute\Parameter; | ||
|
||
use ArrayObject; | ||
use PHPUnit\Framework\Attributes\DataProvider; | ||
use PHPUnit\Framework\TestCase; | ||
use stdClass; | ||
use Yiisoft\Hydrator\Attribute\Parameter\ToArrayOfStrings; | ||
use Yiisoft\Hydrator\Attribute\Parameter\ToArrayOfStringsResolver; | ||
use Yiisoft\Hydrator\AttributeHandling\Exception\UnexpectedAttributeException; | ||
use Yiisoft\Hydrator\AttributeHandling\ResolverFactory\ContainerAttributeResolverFactory; | ||
use Yiisoft\Hydrator\Hydrator; | ||
use Yiisoft\Hydrator\Tests\Support\Attribute\Counter; | ||
use Yiisoft\Hydrator\Tests\Support\Attribute\CounterResolver; | ||
use Yiisoft\Hydrator\Tests\Support\Classes\CounterClass; | ||
use Yiisoft\Test\Support\Container\SimpleContainer; | ||
|
||
final class ToArrayOfStringsTest extends TestCase | ||
{ | ||
public static function dataBase(): iterable | ||
{ | ||
yield [ | ||
[], | ||
[], | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
[''], | ||
'', | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
[''], | ||
new stdClass(), | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello'], | ||
'hello', | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello'], | ||
['hello'], | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello '], | ||
'hello ', | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello'], | ||
'hello ', | ||
new class () { | ||
#[ToArrayOfStrings(trim: true)] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', 'world'], | ||
"hello\nworld", | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', 'world'], | ||
['hello', 'world'], | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', '42', '1', '2.4'], | ||
['hello', 42, true, 2.4], | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', 'world'], | ||
new ArrayObject(['hello', 'world']), | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
["hello\nworld"], | ||
"hello\nworld", | ||
new class () { | ||
#[ToArrayOfStrings(splitResolvedValue: false)] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', '', 'world'], | ||
"hello\n\nworld", | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', 2 => 'world'], | ||
"hello\n\nworld", | ||
new class () { | ||
#[ToArrayOfStrings(removeEmpty: true)] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', '', ' world', ' good '], | ||
"hello\n\n world\n good ", | ||
new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', 2 => 'world', 3 => 'good'], | ||
"hello\n\n world\n good ", | ||
new class () { | ||
#[ToArrayOfStrings(trim: true, removeEmpty: true)] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
yield [ | ||
['hello', 'world', 'good'], | ||
'hello,world,good', | ||
new class () { | ||
#[ToArrayOfStrings(separator: ',')] | ||
public ?array $value = null; | ||
}, | ||
]; | ||
} | ||
|
||
#[DataProvider('dataBase')] | ||
public function testBase(mixed $expectedValue, mixed $value, object $object) | ||
{ | ||
(new Hydrator())->hydrate($object, ['value' => $value]); | ||
$this->assertSame($expectedValue, $object->value); | ||
} | ||
|
||
public function testNotResolved(): void | ||
{ | ||
$object = new class () { | ||
#[ToArrayOfStrings] | ||
public ?array $value = null; | ||
}; | ||
|
||
(new Hydrator())->hydrate($object); | ||
|
||
$this->assertNull($object->value); | ||
} | ||
|
||
public function testUnexpectedAttributeException(): void | ||
{ | ||
$hydrator = new Hydrator( | ||
attributeResolverFactory: new ContainerAttributeResolverFactory( | ||
new SimpleContainer([ | ||
CounterResolver::class => new ToArrayOfStringsResolver(), | ||
]), | ||
), | ||
); | ||
|
||
$object = new CounterClass(); | ||
|
||
$this->expectException(UnexpectedAttributeException::class); | ||
$this->expectExceptionMessage( | ||
'Expected "' . ToArrayOfStrings::class . '", but "' . Counter::class . '" given.' | ||
); | ||
$hydrator->hydrate($object); | ||
} | ||
} |