Skip to content

Commit

Permalink
Add NullTypeCaster (#74)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
  • Loading branch information
vjik and samdark authored Feb 9, 2024
1 parent f2e4ed9 commit 750a7f4
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 3 deletions.
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Yii Hydrator Change Log

## 1.0.1 under development
## 1.1.0 under development

- no changes in this release.
- New #74: Add `NullTypeCaster` (@vjik)

## 1.0.0 January 29, 2024

- Initial release.
- Initial release.
1 change: 1 addition & 0 deletions docs/guide/en/typecasting.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Out of the box, the following type-casters are available:
- `CompositeTypeCaster` allows combining multiple type-casters
- `PhpNativeTypeCaster` casts based on PHP types defined in the class
- `HydratorTypeCaster` casts arrays to objects
- `NullTypeCaster` configurable type caster for casting `null`, empty string and empty array to `null`
- `NoTypeCaster` does not cast anything

## Your own type-casting
Expand Down
62 changes: 62 additions & 0 deletions src/TypeCaster/NullTypeCaster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\TypeCaster;

use ReflectionNamedType;
use ReflectionType;
use ReflectionUnionType;
use Yiisoft\Hydrator\Result;

/**
* Configurable type caster for casting value to `null`.
*/
final class NullTypeCaster implements TypeCasterInterface
{
public function __construct(
private bool $null = true,
private bool $emptyString = false,
private bool $emptyArray = false,
) {
}

public function cast(mixed $value, TypeCastContext $context): Result
{
if (!$this->isAllowNull($context->getReflectionType())) {
return Result::fail();
}

if (
($this->null && $value === null)
|| ($this->emptyString && $value === '')
|| ($this->emptyArray && $value === [])
) {
return Result::success(null);
}

return Result::fail();
}

private function isAllowNull(?ReflectionType $type): bool
{
if ($type === null) {
return true;
}

if ($type instanceof ReflectionNamedType) {
return $type->allowsNull();
}

if ($type instanceof ReflectionUnionType) {
/** @psalm-suppress RedundantConditionGivenDocblockType Needed for PHP less than 8.2 */
foreach ($type->getTypes() as $subtype) {
if ($subtype instanceof ReflectionNamedType && $type->allowsNull()) {
return true;
}
}
}

return false;
}
}
158 changes: 158 additions & 0 deletions tests/TypeCaster/NullTypeCasterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

declare(strict_types=1);

namespace TypeCaster;

use Closure;
use PHPUnit\Framework\TestCase;
use Yiisoft\Hydrator\Tests\Support\TestHelper;
use Yiisoft\Hydrator\TypeCaster\NullTypeCaster;

final class NullTypeCasterTest extends TestCase
{
public function dataBase(): array
{
return [
'default, null to non-type' => [
true,
new NullTypeCaster(),
null,
fn($a) => null,
],
'default, null to ?int' => [
true,
new NullTypeCaster(),
null,
fn(?int $a) => null,
],
'default, null to int' => [
false,
new NullTypeCaster(),
null,
fn(int $a) => null,
],
'default, empty string to ?string' => [
false,
new NullTypeCaster(),
'',
fn(?string $a) => null,
],
'default, empty array to ?array' => [
false,
new NullTypeCaster(),
[],
fn(?array $a) => null,
],
'default, empty array to array|string|null' => [
false,
new NullTypeCaster(),
[],
fn(array|string|null $a) => null,
],
'null=false, null to non-type' => [
false,
new NullTypeCaster(null: false),
null,
fn($a) => null,
],
'null=false, null to ?int' => [
false,
new NullTypeCaster(null: false),
null,
fn(?int $a) => null,
],
'null=false, null to int|string|null' => [
false,
new NullTypeCaster(null: false),
null,
fn(int|string|null $a) => null,
],
'emptyString=true, empty string to non-type' => [
true,
new NullTypeCaster(emptyString: true),
'',
fn($a) => null,
],
'emptyString=true, empty string to ?string' => [
true,
new NullTypeCaster(emptyString: true),
'',
fn(?string $a) => null,
],
'emptyString=true, empty string to string|int|null' => [
true,
new NullTypeCaster(emptyString: true),
'',
fn(string|int|null $a) => null,
],
'emptyString=true, empty string to string' => [
false,
new NullTypeCaster(emptyString: true),
'',
fn(string $a) => null,
],
'emptyString=true, empty string to string|int' => [
false,
new NullTypeCaster(emptyString: true),
'',
fn(string|int $a) => null,
],
'emptyArray=true, empty array to non-type' => [
true,
new NullTypeCaster(emptyArray: true),
[],
fn($a) => null,
],
'emptyArray=true, empty array to ?array' => [
true,
new NullTypeCaster(emptyArray: true),
[],
fn(?array $a) => null,
],
'emptyArray=true, empty array to array|string|null' => [
true,
new NullTypeCaster(emptyArray: true),
[],
fn(array|string|null $a) => null,
],
'emptyArray=true, empty array to array' => [
false,
new NullTypeCaster(emptyArray: true),
[],
fn(array $a) => null,
],
'emptyArray=true, empty array to array|string' => [
false,
new NullTypeCaster(emptyArray: true),
[],
fn(array|string $a) => null,
],
];
}

/**
* @dataProvider dataBase
*/
public function testBase(bool $success, NullTypeCaster $typeCaster, mixed $value, Closure $closure): void
{
$context = TestHelper::createTypeCastContext($closure);

$result = $typeCaster->cast($value, $context);

$this->assertSame($success, $result->isResolved());
if ($success) {
$this->assertNull($result->getValue());
}
}

public function testConstructor(): void
{
$typeCaster = new NullTypeCaster();
$context = TestHelper::createTypeCastContext(fn($a) => null);

$result = $typeCaster->cast('hello', $context);

$this->assertSame(false, $result->isResolved());
}
}

0 comments on commit 750a7f4

Please sign in to comment.