Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Trim, LeftTrim and RightTrim parameter attributes #79

Merged
merged 3 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
Loading
Loading