Skip to content

Commit

Permalink
Improve Serializer feature, nullable can be configured per field
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Sep 20, 2024
1 parent fb8af3e commit 02dae2d
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 11 deletions.
4 changes: 4 additions & 0 deletions src/Serializer/CallbackCasting.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public function setOptions(?string $type = null, mixed ...$options): void
*/
public function toVariable(mixed $value): mixed
{
if ('' === $value && isset($this->options['allowEmptyStringAsNull']) && $this->options['allowEmptyStringAsNull']) {
$value = null;
}

try {
return ($this->callback)($value, $this->isNullable, ...$this->options);
} catch (Throwable $exception) {
Expand Down
7 changes: 7 additions & 0 deletions src/Serializer/CastToArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ final class CastToArray implements TypeCasting
private int $depth = 512;
private int $flags = 0;
private ?array $default = null;
private bool $allowEmptyStringAsNull = false;

/**
* @throws MappingFailed
Expand Down Expand Up @@ -71,6 +72,7 @@ public function setOptions(
int $depth = 512,
int $flags = 0,
Type|string $type = Type::String,
?bool $allowEmptyStringAsNull = null,
): void {
if (!$shape instanceof ArrayShape) {
$shape = ArrayShape::tryFrom($shape) ?? throw new MappingFailed('Unable to resolve the array shape; Verify your options arguments.');
Expand All @@ -94,10 +96,15 @@ public function setOptions(
1 !== strlen($this->enclosure) && $this->shape->equals(ArrayShape::Csv) => throw new MappingFailed('expects enclosure to be a single character; `'.$this->enclosure.'` given.'),
default => $this->resolveFilterFlag($type),
};
$this->allowEmptyStringAsNull = $allowEmptyStringAsNull ?? false;
}

public function toVariable(mixed $value): ?array
{
if ('' === $value && $this->allowEmptyStringAsNull) {
$value = null;
}

if (null === $value) {
return match (true) {
$this->isNullable,
Expand Down
12 changes: 10 additions & 2 deletions src/Serializer/CastToBool.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,30 @@ final class CastToBool implements TypeCasting
private readonly bool $isNullable;
private readonly Type $type;
private ?bool $default = null;
private bool $allowEmptyStringAsNull = false;

public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty)
{
[$this->type, $this->isNullable] = $this->init($reflectionProperty);
}

public function setOptions(?bool $default = null): void
{
public function setOptions(
?bool $default = null,
?bool $allowEmptyStringAsNull = null,
): void {
$this->default = $default;
$this->allowEmptyStringAsNull = $allowEmptyStringAsNull ?? false;
}

/**
* @throws TypeCastingFailed
*/
public function toVariable(mixed $value): ?bool
{
if ('' === $value && $this->allowEmptyStringAsNull) {
$value = null;
}

$returnValue = match (true) {
is_bool($value) => $value,
null !== $value => filter_var($value, Type::Bool->filterFlag()),
Expand Down
9 changes: 8 additions & 1 deletion src/Serializer/CastToDate.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ final class CastToDate implements TypeCasting
private readonly string $propertyName;
private ?DateTimeZone $timezone = null;
private ?string $format = null;
private bool $allowEmptyStringAsNull = false;

/**
* @throws MappingFailed
Expand All @@ -58,7 +59,8 @@ public function setOptions(
?string $default = null,
?string $format = null,
DateTimeZone|string|null $timezone = null,
?string $className = null
?string $className = null,
?bool $allowEmptyStringAsNull = null,
): void {
$this->class = match (true) {
!interface_exists($this->class) && !Type::Mixed->equals($this->type) => $this->class,
Expand All @@ -74,6 +76,7 @@ interface_exists($this->class) && null !== $className && class_exists($className
} catch (Throwable $exception) {
throw new MappingFailed('The `timezone` and/or `format` options used for `'.self::class.'` are invalud.', 0, $exception);
}
$this->allowEmptyStringAsNull = $allowEmptyStringAsNull ?? false;
}

/**
Expand All @@ -93,6 +96,10 @@ public function toVariable(mixed $value): DateTimeImmutable|DateTime|null
*/
private function cast(mixed $value): DateTimeImmutable|DateTime
{
if ('' === $value && $this->allowEmptyStringAsNull) {
$value = null;
}

if ($value instanceof DateTimeInterface) {
if ($value instanceof $this->class) {
return $value;
Expand Down
14 changes: 12 additions & 2 deletions src/Serializer/CastToEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class CastToEnum implements TypeCasting
private readonly string $propertyName;
/** @var class-string<UnitEnum|BackedEnum> */
private string $class;
private bool $allowEmptyStringAsNull = false;

/**
* @throws MappingFailed
Expand All @@ -46,8 +47,11 @@ public function __construct(ReflectionProperty|ReflectionParameter $reflectionPr
*
* @throws MappingFailed
*/
public function setOptions(?string $default = null, ?string $className = null): void
{
public function setOptions(
?string $default = null,
?string $className = null,
?bool $allowEmptyStringAsNull = null,
): void {
if (Type::Mixed->equals($this->type) || in_array($this->class, [BackedEnum::class , UnitEnum::class], true)) {
if (null === $className || !enum_exists($className)) {
throw new MappingFailed('`'.$this->propertyName.'` type is `'.($this->class ?? 'mixed').'` but the specified class via the `$className` argument is invalid or could not be found.');
Expand All @@ -61,13 +65,19 @@ public function setOptions(?string $default = null, ?string $className = null):
} catch (TypeCastingFailed $exception) {
throw new MappingFailed(message:'The `default` option is invalid.', previous: $exception);
}

$this->allowEmptyStringAsNull = $allowEmptyStringAsNull ?? false;
}

/**
* @throws TypeCastingFailed
*/
public function toVariable(mixed $value): BackedEnum|UnitEnum|null
{
if ('' === $value && $this->allowEmptyStringAsNull) {
$value = null;
}

return match (true) {
null !== $value => $this->cast($value),
$this->isNullable => $this->default,
Expand Down
12 changes: 10 additions & 2 deletions src/Serializer/CastToFloat.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,30 @@ final class CastToFloat implements TypeCasting
{
private readonly bool $isNullable;
private ?float $default = null;
private bool $allowEmptyStringAsNull = false;

public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty)
{
$this->isNullable = $this->init($reflectionProperty);
}

public function setOptions(int|float|null $default = null): void
{
public function setOptions(
int|float|null $default = null,
?bool $allowEmptyStringAsNull = null,
): void {
$this->default = $default;
$this->allowEmptyStringAsNull = $allowEmptyStringAsNull ?? false;
}

/**
* @throws TypeCastingFailed
*/
public function toVariable(mixed $value): ?float
{
if ('' === $value && $this->allowEmptyStringAsNull) {
$value = null;
}

if (null === $value) {
return match ($this->isNullable) {
true => $this->default,
Expand Down
12 changes: 10 additions & 2 deletions src/Serializer/CastToInt.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,30 @@ final class CastToInt implements TypeCasting
{
private readonly bool $isNullable;
private ?int $default = null;
private bool $allowEmptyStringAsNull = false;

public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty)
{
$this->isNullable = $this->init($reflectionProperty);
}

public function setOptions(?int $default = null): void
{
public function setOptions(
?int $default = null,
?bool $allowEmptyStringAsNull = null,
): void {
$this->default = $default;
$this->allowEmptyStringAsNull = $allowEmptyStringAsNull ?? false;
}

/**
* @throws TypeCastingFailed
*/
public function toVariable(mixed $value): ?int
{
if ('' === $value && $this->allowEmptyStringAsNull) {
$value = null;
}

if (null === $value) {
return match ($this->isNullable) {
true => $this->default,
Expand Down
12 changes: 10 additions & 2 deletions src/Serializer/CastToString.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,30 @@ final class CastToString implements TypeCasting
private readonly bool $isNullable;
private readonly Type $type;
private ?string $default = null;
private bool $allowEmptyStringAsNull = false;

public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty)
{
[$this->type, $this->isNullable] = $this->init($reflectionProperty);
}

public function setOptions(?string $default = null): void
{
public function setOptions(
?string $default = null,
?bool $allowEmptyStringAsNull = null,
): void {
$this->default = $default;
$this->allowEmptyStringAsNull = $allowEmptyStringAsNull ?? false;
}

/**
* @throws TypeCastingFailed
*/
public function toVariable(mixed $value): ?string
{
if ('' === $value && $this->allowEmptyStringAsNull) {
$value = null;
}

$returnedValue = match (true) {
is_string($value) => $value,
$this->isNullable => $this->default,
Expand Down

0 comments on commit 02dae2d

Please sign in to comment.