Skip to content

Commit 77c79be

Browse files
committed
Array casting bugfixes and more testing
1 parent 694ee1b commit 77c79be

9 files changed

+318
-27
lines changed

src/Casts/EnumCaster.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Matteoc99\LaravelPreference\Casts;
44

55
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
6+
use Illuminate\Database\Eloquent\Model;
67
use Illuminate\Support\Facades\App;
78
use Illuminate\Support\Facades\Log;
89
use Matteoc99\LaravelPreference\Contracts\CastableEnum;
@@ -11,7 +12,7 @@ class EnumCaster implements CastsAttributes
1112
{
1213

1314

14-
public function get($model, string $key, mixed $value, array $attributes): CastableEnum
15+
public function get(Model $model, string $key, mixed $value, array $attributes): CastableEnum|null
1516
{
1617
return $this->deserializeEnum($value);
1718
}
@@ -23,7 +24,7 @@ protected function deserializeEnum($value)
2324
}
2425
$value = json_decode($value, true);
2526

26-
$enumClass = $value['class'];
27+
$enumClass = $value['class']??null;
2728

2829
if (!class_exists($enumClass)) {
2930
throw new \InvalidArgumentException("Enum class $enumClass does not exist.");
@@ -32,7 +33,7 @@ protected function deserializeEnum($value)
3233
return $enumClass::tryFrom($value['value']);
3334
}
3435

35-
public function set($model, string $key, mixed $value, array $attributes)
36+
public function set(Model $model, string $key, mixed $value, array $attributes)
3637
{
3738
return json_encode($this->serializeEnum($value));
3839
}

src/Enums/Cast.php

+23-21
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function castFromString(string $value): mixed
4141
self::FLOAT => (float)$value,
4242
self::STRING => $value,
4343
self::BOOL => !empty($value),
44-
self::ARRAY => json_encode($value, 1),
44+
self::ARRAY => json_decode($value, 1),
4545
self::DATE, self::DATETIME => new Carbon($value),
4646
self::TIME => Carbon::now()->setTimeFromTimeString($value),
4747
self::TIMESTAMP => Carbon::createFromTimestamp($value),
@@ -50,7 +50,7 @@ public function castFromString(string $value): mixed
5050

5151
public function castToString(mixed $value): string
5252
{
53-
$this->ensureType($value);
53+
$value = $this->ensureType($value);
5454

5555
return match ($this) {
5656
self::BOOL, self::INT, self::FLOAT, self::STRING => (string)$value,
@@ -62,45 +62,47 @@ public function castToString(mixed $value): string
6262
};
6363
}
6464

65-
private function ensureType(mixed $value): void
65+
private function ensureType(mixed $value): mixed
6666
{
67-
$type = gettype($value);
6867

6968
switch ($this) {
7069
case self::INT:
71-
if ($type !== 'integer') {
72-
throw ValidationException::withMessages(["Expected an integer for cast INT, got $type"]);
73-
}
70+
$value = intval($value);
7471
break;
7572
case self::FLOAT:
76-
if (!in_array($type, ['double', 'float'])) {
77-
throw ValidationException::withMessages(["Expected a float or double for cast FLOAT, got $type"]);
78-
}
73+
$value = floatval($value);
7974
break;
8075
case self::STRING:
81-
if ($type !== 'string') {
82-
throw ValidationException::withMessages(["Expected a string for cast STRING, got $type"]);
83-
}
76+
$value = (string)$value;
8477
break;
8578
case self::BOOL:
86-
if ($type !== 'boolean') {
87-
throw ValidationException::withMessages(["Expected a boolean for cast BOOL, got $type"]);
88-
}
89-
break;
79+
return !empty($value);
9080
case self::ARRAY:
91-
if ($type !== 'array') {
92-
throw ValidationException::withMessages(["Expected an array for cast ARRAY, got $type"]);
81+
if (!is_array($value)) {
82+
$value = json_decode($value, true);
9383
}
9484
break;
95-
case self::DATETIME:
9685
case self::TIMESTAMP:
86+
if (!($value instanceof Carbon)) {
87+
$value = Carbon::createFromTimestamp($value);
88+
}
89+
case self::DATETIME:
9790
case self::DATE:
9891
if (!($value instanceof Carbon)) {
99-
throw ValidationException::withMessages(["Expected a Carbon instance to cast, got $type"]);
92+
try {
93+
$value = Carbon::parse($value); // Attempt to parse various date/time formats
94+
} catch (\Exception $e) {
95+
throw ValidationException::withMessages(["Invalid format for cast to DATETIME, DATE, or TIME"]);
96+
}
97+
}
98+
case self::TIME:
99+
if (!($value instanceof Carbon)) {
100+
$value = Carbon::now()->setTimeFromTimeString($value);
100101
}
101102
break;
102103
default:
103104
throw ValidationException::withMessages(["Unknown casting type"]);
104105
}
106+
return $value;
105107
}
106108
}

tests/Casts/CasterTestCase.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Matteoc99\LaravelPreference\Tests\Casts;
4+
5+
use Matteoc99\LaravelPreference\Factory\PreferenceBuilder;
6+
use Matteoc99\LaravelPreference\Models\Preference;
7+
use Matteoc99\LaravelPreference\Tests\TestCase;
8+
9+
class CasterTestCase extends TestCase
10+
{
11+
12+
protected Preference $dummyPref;
13+
14+
public function setUp(): void
15+
{
16+
parent::setUp();
17+
18+
$this->dummyPref = PreferenceBuilder::init('test')->create();
19+
}
20+
21+
22+
}

tests/Casts/EnumCasterTest.php

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace Matteoc99\LaravelPreference\Tests\Casts;
4+
5+
use Illuminate\Foundation\Testing\RefreshDatabase;
6+
use Illuminate\Foundation\Testing\WithFaker;
7+
use Matteoc99\LaravelPreference\Casts\EnumCaster;
8+
use Matteoc99\LaravelPreference\Enums\Cast;
9+
10+
class EnumCasterTest extends CasterTestCase
11+
{
12+
use WithFaker, RefreshDatabase;
13+
14+
/** @test */
15+
public function test_get()
16+
{
17+
$caster = new EnumCaster();
18+
// Successful deserialization
19+
$result = $caster->get($this->dummyPref, '', json_encode(['class' => Cast::class, 'value' => 'int']), []);
20+
$this->assertEquals(Cast::INT, $result);
21+
22+
// Null handling
23+
$result = $caster->get($this->dummyPref, '', null, []);
24+
$this->assertNull($result);
25+
26+
// Missing class
27+
$this->expectException(\InvalidArgumentException::class);
28+
$caster->get($this->dummyPref, '', json_encode(['class' => 'Foo\Bar', 'value' => 'int']), []);
29+
30+
// Invalid value
31+
$this->expectException(\InvalidArgumentException::class);
32+
$caster->get($this->dummyPref, '', json_encode(['class' => Cast::class, 'value' => 'xyz']), []);
33+
}
34+
35+
/** @test */
36+
public function test_set()
37+
{
38+
$caster = new EnumCaster();
39+
40+
// Successful serialization
41+
$result = $caster->set($this->dummyPref, '', Cast::STRING, []);
42+
$this->assertEquals(json_encode(['class' => Cast::class, 'value' => 'string']), $result);
43+
44+
// Invalid input
45+
$this->expectException(\InvalidArgumentException::class);
46+
$caster->set($this->dummyPref, '', 'not an enum', []);
47+
}
48+
49+
/** @test */
50+
public function test_get_with_invalid_json()
51+
{
52+
$caster = new EnumCaster();
53+
54+
// Missing 'class' field
55+
$this->expectException(\InvalidArgumentException::class);
56+
$caster->get($this->dummyPref, '', json_encode(['value' => 'int']), []);
57+
58+
// 'value' field with the wrong type
59+
$this->expectException(\InvalidArgumentException::class);
60+
$caster->get($this->dummyPref, '', json_encode(['class' => Cast::class, 'value' => 123]), []);
61+
}
62+
}

tests/Casts/RuleCasterTest.php

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace Matteoc99\LaravelPreference\Tests\Casts;
4+
5+
use Illuminate\Foundation\Testing\RefreshDatabase;
6+
use Illuminate\Foundation\Testing\WithFaker;
7+
use Matteoc99\LaravelPreference\Casts\RuleCaster;
8+
use Matteoc99\LaravelPreference\Tests\Models\LowerThanRule;
9+
10+
class RuleCasterTest extends CasterTestCase
11+
{
12+
use WithFaker, RefreshDatabase;
13+
14+
/** @test */
15+
public function test_get()
16+
{
17+
$caster = new RuleCaster();
18+
19+
// Successful deserialization
20+
$result = $caster->get($this->dummyPref, '', json_encode(['class' => LowerThanRule::class, 'data' => [10]]), []);
21+
$this->assertInstanceOf(LowerThanRule::class, $result);
22+
$this->assertEquals([10], $result->getData());
23+
24+
// DataRule Handling with various data
25+
$result = $caster->get($this->dummyPref, '', json_encode(['class' => LowerThanRule::class, 'data' => [50]]), []);
26+
$this->assertTrue($result->passes('test', 40));
27+
$this->assertFalse($result->passes('test', 60));
28+
}
29+
30+
/** @test */
31+
public function test_set()
32+
{
33+
$caster = new RuleCaster();
34+
35+
// Successful serialization
36+
$rule = new LowerThanRule(20);
37+
$result = $caster->set($this->dummyPref, '', $rule, []);
38+
$this->assertEquals(json_encode(['class' => LowerThanRule::class, 'data' => [20]]), $result);
39+
40+
// Edge Case: Non-HasValidation input
41+
$this->expectException(\InvalidArgumentException::class);
42+
$caster->set($this->dummyPref, '', new \stdClass(), []); // Not a HasValidation object
43+
}
44+
}

tests/Casts/ValueCasterTest.php

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace Matteoc99\LaravelPreference\Tests\Casts;
4+
5+
use Illuminate\Foundation\Testing\RefreshDatabase;
6+
use Illuminate\Foundation\Testing\WithFaker;
7+
use Matteoc99\LaravelPreference\Casts\ValueCaster;
8+
use Matteoc99\LaravelPreference\Enums\Cast;
9+
10+
class ValueCasterTest extends CasterTestCase
11+
{
12+
use WithFaker, RefreshDatabase;
13+
14+
/** @test */
15+
public function test_get()
16+
{
17+
$caster = new ValueCaster();
18+
19+
// With Int Cast
20+
$this->dummyPref->cast = Cast::INT;
21+
$result = $caster->get($this->dummyPref, '', '123', []);
22+
$this->assertEquals(123, $result); // Assert integer conversion
23+
24+
// With Float Cast
25+
$this->dummyPref->cast = Cast::FLOAT;
26+
$result = $caster->get($this->dummyPref, '', '3.14', []);
27+
$this->assertEquals(3.14, $result); // Assert float conversion
28+
29+
// With String Cast (or unknown)
30+
$this->dummyPref->cast = Cast::STRING;
31+
$result = $caster->get($this->dummyPref, '', 'hello', []);
32+
$this->assertEquals('hello', $result); // Assert no change
33+
34+
35+
$this->dummyPref->cast = Cast::ARRAY;
36+
$result = $caster->get($this->dummyPref, '', "[1, \"hello\"]", []);
37+
$this->assertIsArray($result); // Assert valid JSON representation
38+
39+
// With Date Cast
40+
$this->dummyPref->cast = Cast::DATE;
41+
$result = $caster->get($this->dummyPref, '', '2023-12-25', []);
42+
$this->assertInstanceOf(\Carbon\Carbon::class, $result);
43+
$this->assertEquals('2023-12-25', $result->toDateString());
44+
45+
// With Time Cast
46+
$this->dummyPref->cast = Cast::TIME;
47+
$result = $caster->get($this->dummyPref, '', '15:30:00', []);
48+
$this->assertInstanceOf(\Carbon\Carbon::class, $result);
49+
$this->assertEquals('15:30:00', $result->toTimeString());
50+
51+
// With DateTime Cast
52+
$this->dummyPref->cast = Cast::DATETIME;
53+
$result = $caster->get($this->dummyPref, '', '2023-12-25 15:30:00', []);
54+
$this->assertInstanceOf(\Carbon\Carbon::class, $result);
55+
$this->assertEquals('2023-12-25 15:30:00', $result->toDateTimeString());
56+
57+
// With Timestamp Cast
58+
$this->dummyPref->cast = Cast::TIMESTAMP;
59+
$timestamp = 1679164665;
60+
$result = $caster->get($this->dummyPref, '', (string)$timestamp, []);
61+
$this->assertInstanceOf(\Carbon\Carbon::class, $result);
62+
$this->assertEquals($timestamp, $result->getTimestamp());
63+
64+
}
65+
66+
/** @test */
67+
public function test_set()
68+
{
69+
$caster = new ValueCaster();
70+
71+
// With Bool Cast
72+
$this->dummyPref->cast = Cast::BOOL;
73+
$result = $caster->set($this->dummyPref, '', true, []);
74+
$this->assertEquals(true, $result);
75+
76+
// With Array cast
77+
$this->dummyPref->cast = Cast::ARRAY;
78+
$result = $caster->set($this->dummyPref, '', [1, "hello"], []);
79+
$this->assertJson($result); // Assert valid JSON representation
80+
81+
$this->dummyPref->cast = Cast::DATE;
82+
$date = \Carbon\Carbon::now();
83+
$result = $caster->set($this->dummyPref, '', $date, []);
84+
$this->assertEquals($date->toDateString(), $result);
85+
86+
// With Time Cast
87+
$this->dummyPref->cast = Cast::TIME;
88+
$time = \Carbon\Carbon::parse('10:30:00');
89+
$result = $caster->set($this->dummyPref, '', $time, []);
90+
$this->assertEquals($time->toTimeString(), $result);
91+
92+
// With Datetime Cast
93+
$this->dummyPref->cast = Cast::DATETIME;
94+
$datetime = \Carbon\Carbon::now();
95+
$result = $caster->set($this->dummyPref, '', $datetime, []);
96+
$this->assertEquals($datetime->toDateTimeString(), $result);
97+
98+
// With Timestamp Cast
99+
$this->dummyPref->cast = Cast::TIMESTAMP;
100+
$timestamp = time();
101+
$result = $caster->set($this->dummyPref, '', $timestamp, []);
102+
$this->assertEquals((string)$timestamp, $result);
103+
104+
}
105+
106+
}

tests/PreferenceBasicTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Validation\ValidationException;
88
use Matteoc99\LaravelPreference\Factory\PreferenceBuilder;
99
use Matteoc99\LaravelPreference\Rules\InRule;
10+
use Matteoc99\LaravelPreference\Tests\Models\User;
1011

1112
class PreferenceBasicTest extends TestCase
1213
{
@@ -20,7 +21,6 @@ public function setUp(): void
2021
->withDefaultValue("en")
2122
->withRule(new InRule("en", "it", "de"))
2223
->create();
23-
2424
}
2525

2626
public function tearDown(): void

0 commit comments

Comments
 (0)