Skip to content

Commit

Permalink
Add Trim, LeftTrim and RightTrim parameter attributes (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed Apr 3, 2024
1 parent c51e692 commit 4e57b93
Show file tree
Hide file tree
Showing 12 changed files with 622 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions docs/guide/en/typecasting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ']);
```
18 changes: 18 additions & 0 deletions docs/guide/ru/typecasting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ']);
```
30 changes: 30 additions & 0 deletions src/Attribute/Parameter/LeftTrim.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Attribute\Parameter;

use Attribute;

/**
* Strip whitespace (or other characters) from the beginning of a resolved string value.
*
* @see https://www.php.net/manual/function.ltrim.php
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)]
final class LeftTrim implements ParameterAttributeInterface
{
/**
* @param string|null $characters The list all characters that you want to be stripped. With `..` you can specify
* a range of characters.
*/
public function __construct(
public readonly ?string $characters = null,
) {
}

public function getResolver(): string
{
return LeftTrimResolver::class;
}
}
41 changes: 41 additions & 0 deletions src/Attribute/Parameter/LeftTrimResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Attribute\Parameter;

use Yiisoft\Hydrator\AttributeHandling\Exception\UnexpectedAttributeException;
use Yiisoft\Hydrator\AttributeHandling\ParameterAttributeResolveContext;
use Yiisoft\Hydrator\Result;

final class LeftTrimResolver implements ParameterAttributeResolverInterface
{
public function __construct(
private readonly ?string $characters = null,
) {
}

public function getParameterValue(
ParameterAttributeInterface $attribute,
ParameterAttributeResolveContext $context
): Result {
if (!$attribute instanceof LeftTrim) {
throw new UnexpectedAttributeException(LeftTrim::class, $attribute);
}

if (!$context->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)
);
}
}
30 changes: 30 additions & 0 deletions src/Attribute/Parameter/RightTrim.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Attribute\Parameter;

use Attribute;

/**
* Strip whitespace (or other characters) from the end of a resolved string value.
*
* @see https://www.php.net/manual/function.rtrim.php
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)]
final class RightTrim implements ParameterAttributeInterface
{
/**
* @param string|null $characters The list all characters that you want to be stripped. With `..` you can specify
* a range of characters.
*/
public function __construct(
public readonly ?string $characters = null,
) {
}

public function getResolver(): string
{
return RightTrimResolver::class;
}
}
41 changes: 41 additions & 0 deletions src/Attribute/Parameter/RightTrimResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Attribute\Parameter;

use Yiisoft\Hydrator\AttributeHandling\Exception\UnexpectedAttributeException;
use Yiisoft\Hydrator\AttributeHandling\ParameterAttributeResolveContext;
use Yiisoft\Hydrator\Result;

final class RightTrimResolver implements ParameterAttributeResolverInterface
{
public function __construct(
private readonly ?string $characters = null,
) {
}

public function getParameterValue(
ParameterAttributeInterface $attribute,
ParameterAttributeResolveContext $context
): Result {
if (!$attribute instanceof RightTrim) {
throw new UnexpectedAttributeException(RightTrim::class, $attribute);
}

if (!$context->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)
);
}
}
30 changes: 30 additions & 0 deletions src/Attribute/Parameter/Trim.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Attribute\Parameter;

use Attribute;

/**
* Strip whitespace (or other characters) from the beginning and end of a resolved string value.
*
* @see https://www.php.net/manual/function.trim.php
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)]
final class Trim implements ParameterAttributeInterface
{
/**
* @param string|null $characters The list all characters that you want to be stripped. With `..` you can specify
* a range of characters.
*/
public function __construct(
public readonly ?string $characters = null,
) {
}

public function getResolver(): string
{
return TrimResolver::class;
}
}
41 changes: 41 additions & 0 deletions src/Attribute/Parameter/TrimResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Attribute\Parameter;

use Yiisoft\Hydrator\AttributeHandling\Exception\UnexpectedAttributeException;
use Yiisoft\Hydrator\AttributeHandling\ParameterAttributeResolveContext;
use Yiisoft\Hydrator\Result;

final class TrimResolver implements ParameterAttributeResolverInterface
{
public function __construct(
private readonly ?string $characters = null,
) {
}

public function getParameterValue(
ParameterAttributeInterface $attribute,
ParameterAttributeResolveContext $context
): Result {
if (!$attribute instanceof Trim) {
throw new UnexpectedAttributeException(Trim::class, $attribute);
}

if (!$context->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)
);
}
}
124 changes: 124 additions & 0 deletions tests/Attribute/Parameter/LeftTrimTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Tests\Attribute\Parameter;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use stdClass;
use Yiisoft\Hydrator\ArrayData;
use Yiisoft\Hydrator\Attribute\Parameter\LeftTrim;
use Yiisoft\Hydrator\Attribute\Parameter\LeftTrimResolver;
use Yiisoft\Hydrator\AttributeHandling\Exception\UnexpectedAttributeException;
use Yiisoft\Hydrator\AttributeHandling\ParameterAttributeResolveContext;
use Yiisoft\Hydrator\AttributeHandling\ResolverFactory\ContainerAttributeResolverFactory;
use Yiisoft\Hydrator\Hydrator;
use Yiisoft\Hydrator\Result;
use Yiisoft\Hydrator\Tests\Support\Attribute\Counter;
use Yiisoft\Hydrator\Tests\Support\Attribute\CounterResolver;
use Yiisoft\Hydrator\Tests\Support\Classes\CounterClass;
use Yiisoft\Hydrator\Tests\Support\TestHelper;
use Yiisoft\Test\Support\Container\SimpleContainer;

final class LeftTrimTest extends TestCase
{
public static function dataBase(): iterable
{
yield ['test ', new LeftTrim(), ' test '];
yield [' test ', new LeftTrim('t'), ' test '];
yield ['est', new LeftTrim('t'), 'test'];
}

#[DataProvider('dataBase')]
public function testBase(string $expected, LeftTrim $attribute, mixed $value): void
{
$resolver = new LeftTrimResolver();
$context = new ParameterAttributeResolveContext(
TestHelper::getFirstParameter(static fn(?string $a) => 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);
}
}

0 comments on commit 4e57b93

Please sign in to comment.