From 4e57b931d8d161fe817e4ca7b973a802359448e6 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 3 Apr 2024 13:04:26 +0300 Subject: [PATCH] Add `Trim`, `LeftTrim` and `RightTrim` parameter attributes (#79) --- CHANGELOG.md | 1 + docs/guide/en/typecasting.md | 18 +++ docs/guide/ru/typecasting.md | 18 +++ src/Attribute/Parameter/LeftTrim.php | 30 +++++ src/Attribute/Parameter/LeftTrimResolver.php | 41 ++++++ src/Attribute/Parameter/RightTrim.php | 30 +++++ src/Attribute/Parameter/RightTrimResolver.php | 41 ++++++ src/Attribute/Parameter/Trim.php | 30 +++++ src/Attribute/Parameter/TrimResolver.php | 41 ++++++ tests/Attribute/Parameter/LeftTrimTest.php | 124 ++++++++++++++++++ tests/Attribute/Parameter/RightTrimTest.php | 124 ++++++++++++++++++ tests/Attribute/Parameter/TrimTest.php | 124 ++++++++++++++++++ 12 files changed, 622 insertions(+) create mode 100644 src/Attribute/Parameter/LeftTrim.php create mode 100644 src/Attribute/Parameter/LeftTrimResolver.php create mode 100644 src/Attribute/Parameter/RightTrim.php create mode 100644 src/Attribute/Parameter/RightTrimResolver.php create mode 100644 src/Attribute/Parameter/Trim.php create mode 100644 src/Attribute/Parameter/TrimResolver.php create mode 100644 tests/Attribute/Parameter/LeftTrimTest.php create mode 100644 tests/Attribute/Parameter/RightTrimTest.php create mode 100644 tests/Attribute/Parameter/TrimTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9889b24..7c286f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.2.0 under development - New #77: Add `ToDateTime` parameter attribute (@vjik) +- New #79: Add `Trim`, `LeftTrim` and `RightTrim` parameter attributes (@vjik) - Enh #76: Raise the minimum version of PHP to 8.1 (@vjik) ## 1.1.0 February 09, 2024 diff --git a/docs/guide/en/typecasting.md b/docs/guide/en/typecasting.md index 2abda87..8f39370 100644 --- a/docs/guide/en/typecasting.md +++ b/docs/guide/en/typecasting.md @@ -155,3 +155,21 @@ class Person $person = $hydrator->create(Person::class, ['birthday' => '27.01.1986']); ``` + +To strip whitespace (or other characters) from the beginning and/or end of a resolved string value, you can use `Trim`, +`LeftTrim` or `RightTrim` attributes: + +```php +use DateTimeImmutable; +use Yiisoft\Hydrator\Attribute\Parameter\Trim; + +class Person +{ + public function __construct( + #[Trim] // ' John ' → 'John' + private ?string $name = null, + ) {} +} + +$person = $hydrator->create(Person::class, ['name' => ' John ']); +``` diff --git a/docs/guide/ru/typecasting.md b/docs/guide/ru/typecasting.md index dd4f600..3994156 100644 --- a/docs/guide/ru/typecasting.md +++ b/docs/guide/ru/typecasting.md @@ -155,3 +155,21 @@ class Person $person = $hydrator->create(Person::class, ['birthday' => '27.01.1986']); ``` + +Для удаления пробелов (или других символов) из начала и/или конца строки, вы можете использовать атрибуты `Trim`, +`LeftTrim` или `RightTrim`: + +```php +use DateTimeImmutable; +use Yiisoft\Hydrator\Attribute\Parameter\Trim; + +class Person +{ + public function __construct( + #[Trim] // ' John ' → 'John' + private ?string $name = null, + ) {} +} + +$person = $hydrator->create(Person::class, ['name' => ' John ']); +``` diff --git a/src/Attribute/Parameter/LeftTrim.php b/src/Attribute/Parameter/LeftTrim.php new file mode 100644 index 0000000..80e9bb5 --- /dev/null +++ b/src/Attribute/Parameter/LeftTrim.php @@ -0,0 +1,30 @@ +isResolved()) { + return Result::fail(); + } + + $resolvedValue = $context->getResolvedValue(); + if (!is_string($resolvedValue)) { + return Result::fail(); + } + + $characters = $attribute->characters ?? $this->characters; + + return Result::success( + $characters === null ? ltrim($resolvedValue) : ltrim($resolvedValue, $characters) + ); + } +} diff --git a/src/Attribute/Parameter/RightTrim.php b/src/Attribute/Parameter/RightTrim.php new file mode 100644 index 0000000..663ae5d --- /dev/null +++ b/src/Attribute/Parameter/RightTrim.php @@ -0,0 +1,30 @@ +isResolved()) { + return Result::fail(); + } + + $resolvedValue = $context->getResolvedValue(); + if (!is_string($resolvedValue)) { + return Result::fail(); + } + + $characters = $attribute->characters ?? $this->characters; + + return Result::success( + $characters === null ? rtrim($resolvedValue) : rtrim($resolvedValue, $characters) + ); + } +} diff --git a/src/Attribute/Parameter/Trim.php b/src/Attribute/Parameter/Trim.php new file mode 100644 index 0000000..7c21ec4 --- /dev/null +++ b/src/Attribute/Parameter/Trim.php @@ -0,0 +1,30 @@ +isResolved()) { + return Result::fail(); + } + + $resolvedValue = $context->getResolvedValue(); + if (!is_string($resolvedValue)) { + return Result::fail(); + } + + $characters = $attribute->characters ?? $this->characters; + + return Result::success( + $characters === null ? trim($resolvedValue) : trim($resolvedValue, $characters) + ); + } +} diff --git a/tests/Attribute/Parameter/LeftTrimTest.php b/tests/Attribute/Parameter/LeftTrimTest.php new file mode 100644 index 0000000..ac832f0 --- /dev/null +++ b/tests/Attribute/Parameter/LeftTrimTest.php @@ -0,0 +1,124 @@ + null), + Result::success($value), + new ArrayData(), + ); + + $result = $resolver->getParameterValue($attribute, $context); + + $this->assertTrue($result->isResolved()); + $this->assertEquals($expected, $result->getValue()); + } + + public function testWithHydrator(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[LeftTrim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => ' hello ']); + + $this->assertSame('hello ', $object->a); + } + + public function testNotResolve(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[LeftTrim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => new stdClass()]); + + $this->assertNull($object->a); + } + + public function testNotResolvedValue(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[LeftTrim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['b' => ' test ']); + + $this->assertNull($object->a); + } + + public function testUnexpectedAttributeException(): void + { + $hydrator = new Hydrator( + attributeResolverFactory: new ContainerAttributeResolverFactory( + new SimpleContainer([ + CounterResolver::class => new LeftTrimResolver(), + ]), + ), + ); + $object = new CounterClass(); + + $this->expectException(UnexpectedAttributeException::class); + $this->expectExceptionMessage( + 'Expected "' . LeftTrim::class . '", but "' . Counter::class . '" given.' + ); + $hydrator->hydrate($object); + } + + public function testOverrideDefaultCharacters(): void + { + $hydrator = new Hydrator( + attributeResolverFactory: new ContainerAttributeResolverFactory( + new SimpleContainer([ + LeftTrimResolver::class => new LeftTrimResolver(characters: '_-'), + ]), + ), + ); + $object = new class () { + #[LeftTrim(characters: '*')] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => '*test*']); + + $this->assertSame('test*', $object->a); + } +} diff --git a/tests/Attribute/Parameter/RightTrimTest.php b/tests/Attribute/Parameter/RightTrimTest.php new file mode 100644 index 0000000..dbad4b3 --- /dev/null +++ b/tests/Attribute/Parameter/RightTrimTest.php @@ -0,0 +1,124 @@ + null), + Result::success($value), + new ArrayData(), + ); + + $result = $resolver->getParameterValue($attribute, $context); + + $this->assertTrue($result->isResolved()); + $this->assertEquals($expected, $result->getValue()); + } + + public function testWithHydrator(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[RightTrim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => ' hello ']); + + $this->assertSame(' hello', $object->a); + } + + public function testNotResolve(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[RightTrim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => new stdClass()]); + + $this->assertNull($object->a); + } + + public function testNotResolvedValue(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[RightTrim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['b' => ' test ']); + + $this->assertNull($object->a); + } + + public function testUnexpectedAttributeException(): void + { + $hydrator = new Hydrator( + attributeResolverFactory: new ContainerAttributeResolverFactory( + new SimpleContainer([ + CounterResolver::class => new RightTrimResolver(), + ]), + ), + ); + $object = new CounterClass(); + + $this->expectException(UnexpectedAttributeException::class); + $this->expectExceptionMessage( + 'Expected "' . RightTrim::class . '", but "' . Counter::class . '" given.' + ); + $hydrator->hydrate($object); + } + + public function testOverrideDefaultCharacters(): void + { + $hydrator = new Hydrator( + attributeResolverFactory: new ContainerAttributeResolverFactory( + new SimpleContainer([ + RightTrimResolver::class => new RightTrimResolver(characters: '_-'), + ]), + ), + ); + $object = new class () { + #[RightTrim(characters: '*')] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => '*test*']); + + $this->assertSame('*test', $object->a); + } +} diff --git a/tests/Attribute/Parameter/TrimTest.php b/tests/Attribute/Parameter/TrimTest.php new file mode 100644 index 0000000..1fc776c --- /dev/null +++ b/tests/Attribute/Parameter/TrimTest.php @@ -0,0 +1,124 @@ + null), + Result::success($value), + new ArrayData(), + ); + + $result = $resolver->getParameterValue($attribute, $context); + + $this->assertTrue($result->isResolved()); + $this->assertEquals($expected, $result->getValue()); + } + + public function testWithHydrator(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[Trim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => ' hello ']); + + $this->assertSame('hello', $object->a); + } + + public function testNotResolve(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[Trim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => new stdClass()]); + + $this->assertNull($object->a); + } + + public function testNotResolvedValue(): void + { + $hydrator = new Hydrator(); + $object = new class () { + #[Trim] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['b' => ' test ']); + + $this->assertNull($object->a); + } + + public function testUnexpectedAttributeException(): void + { + $hydrator = new Hydrator( + attributeResolverFactory: new ContainerAttributeResolverFactory( + new SimpleContainer([ + CounterResolver::class => new TrimResolver(), + ]), + ), + ); + $object = new CounterClass(); + + $this->expectException(UnexpectedAttributeException::class); + $this->expectExceptionMessage( + 'Expected "' . Trim::class . '", but "' . Counter::class . '" given.' + ); + $hydrator->hydrate($object); + } + + public function testOverrideDefaultCharacters(): void + { + $hydrator = new Hydrator( + attributeResolverFactory: new ContainerAttributeResolverFactory( + new SimpleContainer([ + TrimResolver::class => new TrimResolver(characters: '_-'), + ]), + ), + ); + $object = new class () { + #[Trim(characters: '*')] + public ?string $a = null; + }; + + $hydrator->hydrate($object, ['a' => '*test*']); + + $this->assertSame('test', $object->a); + } +}