Skip to content

Commit

Permalink
feat: add backed enum cast
Browse files Browse the repository at this point in the history
  • Loading branch information
edipoReboucas committed Oct 18, 2023
1 parent 60406ab commit 7fde7a1
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/Model/Casts/CastInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

interface CastInterface
{
public static function get(mixed $value): mixed;
public function get(mixed $value): mixed;

public static function set(mixed $value): mixed;
public function set(mixed $value): mixed;
}
11 changes: 5 additions & 6 deletions src/Model/Casts/CastResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Mongolid\Model\Casts;

use BackedEnum;
use Mongolid\Model\Casts\DateTime\BackedEnumCast;
use Mongolid\Model\Casts\DateTime\DateTimeCast;
use Mongolid\Model\Casts\DateTime\ImmutableDateTimeCast;
use Mongolid\Model\Casts\Exceptions\InvalidCastException;
Expand All @@ -11,11 +13,6 @@ class CastResolver
private const DATE_TIME = 'datetime';
private const IMMUTABLE_DATE_TIME = 'immutable_datetime';

public static array $validCasts = [
self::DATE_TIME,
self::IMMUTABLE_DATE_TIME,
];

public static function resolve(?string $cast): ?object
{
if (is_null($cast)) {
Expand All @@ -25,7 +22,9 @@ public static function resolve(?string $cast): ?object
return match($cast) {
self::DATE_TIME => new DateTimeCast(),
self::IMMUTABLE_DATE_TIME => new ImmutableDateTimeCast(),
default => throw new InvalidCastException($cast),
default => is_a($cast, BackedEnum::class)
? new BackedEnumCast($cast)
: throw new InvalidCastException($cast)
};
}
}
42 changes: 42 additions & 0 deletions src/Model/Casts/DateTime/BackedEnumCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Mongolid\Model\Casts\DateTime;

use DateTime;
use MongoDB\BSON\UTCDateTime;
use Mongolid\Model\Casts\CastInterface;
use Mongolid\Model\Casts\Exceptions\InvalidTypeException;
use Mongolid\Util\LocalDateTime;

class BackedEnumCast implements CastInterface
{
/**
* @param class-string<\BackedEnum> $backedEnum
*/
public function __construct(
private string $backedEnum
) {
}

public function get(mixed $value): mixed
{
if (is_null($value)) {
return null;
}

return ($this->backedEnum)::from($value);
}

public function set(mixed $value): mixed
{
if (is_null($value)) {
return null;
}

if (!$value instanceof $this->backedEnum) {
throw new InvalidTypeException("$this->backedEnum|null", $value);
}

return $value->value;
}
}
3 changes: 1 addition & 2 deletions src/Model/Casts/Exceptions/InvalidCastException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ class InvalidCastException extends InvalidArgumentException
{
public function __construct(string $cast)
{
$available = implode(',', CastResolver::$validCasts);
$message = "Invalid cast attribute: $cast. Use a valid one like $available";
$message = "Invalid cast attribute: $cast";

parent::__construct($message);
}
Expand Down
18 changes: 18 additions & 0 deletions src/Model/Casts/Exceptions/InvalidTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Mongolid\Model\Casts\Exceptions;

use InvalidArgumentException;
use Mongolid\Model\Casts\CastResolver;

class InvalidTypeException extends InvalidArgumentException
{
public function __construct(string $expectedType, mixed $value)
{
$invalidType = is_object($value)
? $value::class
: gettype($value);

parent::__construct("Value expected type $expectedType, given $invalidType");
}
}
70 changes: 70 additions & 0 deletions tests/Integration/BackedEnumCastTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Mongolid\Tests\Integration;

use Mongolid\Tests\Stubs\Box;
use Mongolid\Tests\Stubs\Legacy\Box as LegacyBox;
use Mongolid\Tests\Stubs\Size;

/**
* @requires PHP >= 8.1
*/
class BackedEnumCastTest extends IntegrationTestCase
{
public function testShouldCreateAndSaveBoxWithCastedAttributes(): void
{
// Set
$box = new Box();
$box->box_size = Size::Big;

// Actions
$box->save();

// Assertions
$this->assertEquals( Size::Big, $box->boxSize);
$this->assertEquals( 'big', $box->getOriginalDocumentAttributes()['box_size']);
}

public function testShouldUpdateBoxWithCastedAttributes(): void
{
// Set
$box = new Box();
$box->box_size = Size::Small;

// Actions
$box->update();

// Assertions
$this->assertEquals( Size::Small, $box->boxSize);
$this->assertEquals( 'small', $box->getOriginalDocumentAttributes()['box_size']);
}

public function testShouldCreateAndSaveLegacyBoxWithCastedAttributes(): void
{
// Set
$legacyBox = new LegacyBox();
$legacyBox->box_size = Size::Big;

// Actions
$legacyBox->save();

// Assertions
$this->assertEquals( Size::Big, $legacyBox->boxSize);
$this->assertEquals( 'big', $legacyBox->getOriginalDocumentAttributes()['box_size']);
}

public function testShouldUpdateLegacyBoxWithCastedAttributes(): void
{
// Set
$legacyBox = new LegacyBox();
$legacyBox->box_size = Size::Small;

// Actions
$legacyBox->save();

// Assertions
$this->assertEquals( Size::Small, $legacyBox->boxSize);
$this->assertEquals( 'small', $legacyBox->getOriginalDocumentAttributes()['box_size']);
}
}

17 changes: 17 additions & 0 deletions tests/Stubs/Box.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Mongolid\Tests\Stubs;

use Mongolid\Model\AbstractModel;

class Box extends AbstractModel
{
/**
* @var string
*/
protected $collection = 'boxes';

protected $casts = [
'box_size' => Size::class,
];
}
17 changes: 17 additions & 0 deletions tests/Stubs/Legacy/Box.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Mongolid\Tests\Stubs\Legacy;

use Mongolid\LegacyRecord;

class Box extends LegacyRecord
{
/**
* @var string
*/
protected $collection = 'boxes';

protected $casts = [
'box_size' => Size::class,
];
}
9 changes: 9 additions & 0 deletions tests/Stubs/Size.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Mongolid\Tests\Stubs;

enum Size: string
{
case Small = 'small';
case Big = 'big';
}
88 changes: 88 additions & 0 deletions tests/Unit/Model/Casts/BackedEnumCastTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Mongolid\Model\Casts;

use Mongolid\Model\Casts\DateTime\BackedEnumCast;
use Mongolid\Model\Casts\Exceptions\InvalidTypeException;
use Mongolid\TestCase;
use Mongolid\Tests\Stubs\Size;
use ValueError;

/**
* @requires PHP >= 8.1
*/
class BackedEnumCastTest extends TestCase
{
public function testShouldGetValue(): void
{
// Set
$cast = new BackedEnumCast(Size::class);

// Actions
$result = $cast->get('small');

// Asserts
$this->assertEquals(Size::Small, $result);
}

public function testShouldGetNull(): void
{
// Set
$cast = new BackedEnumCast(Size::class);

// Actions
$result = $cast->get(null);

// Asserts
$this->assertNull($result);
}

public function testShoulThrowErrorWhenGetWithUnknownValue(): void
{
// Set
$cast = new BackedEnumCast(Size::class);

// Expects
$this->expectException(ValueError::class);

// Actions
$cast->get('unknow value');
}

public function testShouldSet(): void
{
// Set
$cast = new BackedEnumCast(Size::class);

// Actions
$result = $cast->set(Size::Big);

// Asserts
$this->assertEquals('big', $result);
}

public function testShouldSetNull(): void
{
// Set
$cast = new BackedEnumCast(Size::class);

// Actions
$result = $cast->set(null);

// Asserts
$this->assertNull($result);
}

public function testShouldThrowErrorWhenSetWithUnknownValue(): void
{
// Set
$cast = new BackedEnumCast(Size::class);

// Expectations
$this->expectException(InvalidTypeException::class);
$this->expectExceptionMessage('Value expected type ' . Size::class . '|null, given string');

// Actions
$cast->set('unknow value');
}
}

0 comments on commit 7fde7a1

Please sign in to comment.