Skip to content

Commit dc4fd7f

Browse files
committed
Add enum support to InteractsWithData and Query Builder
- InteractsWithData: date() timezone param accepts UnitEnum - Query/Builder: castBinding() supports UnitEnum (not just BackedEnum) Both use enum_value() for consistent enum handling.
1 parent 795df73 commit dc4fd7f

File tree

4 files changed

+295
-1
lines changed

4 files changed

+295
-1
lines changed

src/core/src/Database/Query/Builder.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
use Hyperf\Database\Query\Builder as BaseBuilder;
99
use Hypervel\Support\Collection as BaseCollection;
1010
use Hypervel\Support\LazyCollection;
11+
use UnitEnum;
12+
13+
use function Hypervel\Support\enum_value;
1114

1215
/**
1316
* @method $this from(\Closure|\Hypervel\Database\Query\Builder|\Hypervel\Database\Eloquent\Builder|string $table, string|null $as = null)
@@ -111,4 +114,18 @@ public function pluck($column, $key = null)
111114
{
112115
return new BaseCollection(parent::pluck($column, $key)->all());
113116
}
117+
118+
/**
119+
* Cast the given binding value.
120+
*
121+
* Overrides Hyperf's implementation to support UnitEnum (not just BackedEnum).
122+
*/
123+
public function castBinding(mixed $value): mixed
124+
{
125+
if ($value instanceof UnitEnum) {
126+
return enum_value($value);
127+
}
128+
129+
return $value;
130+
}
114131
}

src/support/src/Traits/InteractsWithData.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
use Hypervel\Support\Str;
1212
use stdClass;
1313
use Stringable;
14+
use UnitEnum;
15+
16+
use function Hypervel\Support\enum_value;
1417

1518
trait InteractsWithData
1619
{
@@ -231,8 +234,10 @@ public function float(string $key, float $default = 0.0): float
231234
*
232235
* @throws \Carbon\Exceptions\InvalidFormatException
233236
*/
234-
public function date(string $key, ?string $format = null, ?string $tz = null): ?Carbon
237+
public function date(string $key, ?string $format = null, UnitEnum|string|null $tz = null): ?Carbon
235238
{
239+
$tz = enum_value($tz);
240+
236241
if ($this->isNotFilled($key)) {
237242
return null;
238243
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypervel\Tests\Core\Database\Query;
6+
7+
use Hypervel\Database\Query\Builder;
8+
use Mockery as m;
9+
use PHPUnit\Framework\TestCase;
10+
11+
enum BuilderTestStringEnum: string
12+
{
13+
case Active = 'active';
14+
case Inactive = 'inactive';
15+
}
16+
17+
enum BuilderTestIntEnum: int
18+
{
19+
case One = 1;
20+
case Two = 2;
21+
}
22+
23+
enum BuilderTestUnitEnum
24+
{
25+
case Published;
26+
case Draft;
27+
}
28+
29+
/**
30+
* @internal
31+
* @coversNothing
32+
*/
33+
class BuilderTest extends TestCase
34+
{
35+
protected function tearDown(): void
36+
{
37+
m::close();
38+
39+
parent::tearDown();
40+
}
41+
42+
public function testCastBindingWithStringBackedEnum(): void
43+
{
44+
$builder = $this->getBuilder();
45+
46+
$result = $builder->castBinding(BuilderTestStringEnum::Active);
47+
48+
$this->assertSame('active', $result);
49+
}
50+
51+
public function testCastBindingWithIntBackedEnum(): void
52+
{
53+
$builder = $this->getBuilder();
54+
55+
$result = $builder->castBinding(BuilderTestIntEnum::Two);
56+
57+
$this->assertSame(2, $result);
58+
}
59+
60+
public function testCastBindingWithUnitEnum(): void
61+
{
62+
$builder = $this->getBuilder();
63+
64+
$result = $builder->castBinding(BuilderTestUnitEnum::Published);
65+
66+
// UnitEnum uses ->name via enum_value()
67+
$this->assertSame('Published', $result);
68+
}
69+
70+
public function testCastBindingWithString(): void
71+
{
72+
$builder = $this->getBuilder();
73+
74+
$result = $builder->castBinding('test');
75+
76+
$this->assertSame('test', $result);
77+
}
78+
79+
public function testCastBindingWithInt(): void
80+
{
81+
$builder = $this->getBuilder();
82+
83+
$result = $builder->castBinding(42);
84+
85+
$this->assertSame(42, $result);
86+
}
87+
88+
public function testCastBindingWithNull(): void
89+
{
90+
$builder = $this->getBuilder();
91+
92+
$result = $builder->castBinding(null);
93+
94+
$this->assertNull($result);
95+
}
96+
97+
protected function getBuilder(): Builder
98+
{
99+
$grammar = m::mock(\Hyperf\Database\Query\Grammars\Grammar::class);
100+
$processor = m::mock(\Hyperf\Database\Query\Processors\Processor::class);
101+
$connection = m::mock(\Hyperf\Database\ConnectionInterface::class);
102+
103+
$connection->shouldReceive('getQueryGrammar')->andReturn($grammar);
104+
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
105+
106+
return new Builder($connection);
107+
}
108+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypervel\Tests\Support\Traits;
6+
7+
use Hyperf\Context\ApplicationContext;
8+
use Hypervel\Support\Carbon;
9+
use Hypervel\Support\Collection;
10+
use Hypervel\Support\Facades\Date;
11+
use Hypervel\Support\Traits\InteractsWithData;
12+
use Hypervel\Tests\Foundation\Concerns\HasMockedApplication;
13+
use PHPUnit\Framework\TestCase;
14+
15+
enum InteractsWithDataTestStringEnum: string
16+
{
17+
case UTC = 'UTC';
18+
case NewYork = 'America/New_York';
19+
}
20+
21+
enum InteractsWithDataTestIntEnum: int
22+
{
23+
case One = 1;
24+
case Two = 2;
25+
}
26+
27+
enum InteractsWithDataTestUnitEnum
28+
{
29+
case UTC;
30+
case NewYork;
31+
}
32+
33+
/**
34+
* @internal
35+
* @coversNothing
36+
*/
37+
class InteractsWithDataTest extends TestCase
38+
{
39+
use HasMockedApplication;
40+
41+
protected function setUp(): void
42+
{
43+
parent::setUp();
44+
45+
ApplicationContext::setContainer($this->getApplication());
46+
Date::clearResolvedInstances();
47+
}
48+
49+
protected function tearDown(): void
50+
{
51+
Date::clearResolvedInstances();
52+
53+
parent::tearDown();
54+
}
55+
56+
public function testDateReturnsNullWhenKeyIsNotFilled(): void
57+
{
58+
$instance = new TestInteractsWithDataClass(['date' => '']);
59+
60+
$this->assertNull($instance->date('date'));
61+
}
62+
63+
public function testDateParsesWithoutFormat(): void
64+
{
65+
$instance = new TestInteractsWithDataClass(['date' => '2024-01-15 10:30:00']);
66+
67+
$result = $instance->date('date');
68+
69+
$this->assertInstanceOf(Carbon::class, $result);
70+
$this->assertEquals('2024-01-15 10:30:00', $result->format('Y-m-d H:i:s'));
71+
}
72+
73+
public function testDateParsesWithFormat(): void
74+
{
75+
$instance = new TestInteractsWithDataClass(['date' => '15/01/2024']);
76+
77+
$result = $instance->date('date', 'd/m/Y');
78+
79+
$this->assertInstanceOf(Carbon::class, $result);
80+
$this->assertEquals('2024-01-15', $result->format('Y-m-d'));
81+
}
82+
83+
public function testDateWithStringTimezone(): void
84+
{
85+
$instance = new TestInteractsWithDataClass(['date' => '2024-01-15 10:30:00']);
86+
87+
$result = $instance->date('date', null, 'America/New_York');
88+
89+
$this->assertInstanceOf(Carbon::class, $result);
90+
$this->assertEquals('America/New_York', $result->timezone->getName());
91+
}
92+
93+
public function testDateWithStringBackedEnumTimezone(): void
94+
{
95+
$instance = new TestInteractsWithDataClass(['date' => '2024-01-15 10:30:00']);
96+
97+
$result = $instance->date('date', null, InteractsWithDataTestStringEnum::NewYork);
98+
99+
$this->assertInstanceOf(Carbon::class, $result);
100+
$this->assertEquals('America/New_York', $result->timezone->getName());
101+
}
102+
103+
public function testDateWithUnitEnumTimezone(): void
104+
{
105+
$instance = new TestInteractsWithDataClass(['date' => '2024-01-15 10:30:00']);
106+
107+
// UnitEnum uses ->name, so 'UTC' will be the timezone
108+
$result = $instance->date('date', null, InteractsWithDataTestUnitEnum::UTC);
109+
110+
$this->assertInstanceOf(Carbon::class, $result);
111+
$this->assertEquals('UTC', $result->timezone->getName());
112+
}
113+
114+
public function testDateWithIntBackedEnumTimezoneUsesEnumValue(): void
115+
{
116+
$instance = new TestInteractsWithDataClass(['date' => '2024-01-15 10:30:00']);
117+
118+
// Int-backed enum will return int (1), which Carbon interprets as a UTC offset
119+
// This tests that enum_value() is called and passes the value to Carbon
120+
$result = $instance->date('date', null, InteractsWithDataTestIntEnum::One);
121+
122+
$this->assertInstanceOf(Carbon::class, $result);
123+
// Carbon interprets int as UTC offset, so timezone offset will be +01:00
124+
$this->assertEquals('+01:00', $result->timezone->getName());
125+
}
126+
127+
public function testDateWithNullTimezone(): void
128+
{
129+
$instance = new TestInteractsWithDataClass(['date' => '2024-01-15 10:30:00']);
130+
131+
$result = $instance->date('date', null, null);
132+
133+
$this->assertInstanceOf(Carbon::class, $result);
134+
}
135+
}
136+
137+
class TestInteractsWithDataClass
138+
{
139+
use InteractsWithData;
140+
141+
public function __construct(
142+
protected array $data = []
143+
) {
144+
}
145+
146+
public function all(mixed $keys = null): array
147+
{
148+
return $this->data;
149+
}
150+
151+
protected function data(?string $key = null, mixed $default = null): mixed
152+
{
153+
if (is_null($key)) {
154+
return $this->data;
155+
}
156+
157+
return $this->data[$key] ?? $default;
158+
}
159+
160+
public function collect(array|string|null $key = null): Collection
161+
{
162+
return new Collection(is_array($key) ? $this->only($key) : $this->data($key));
163+
}
164+
}

0 commit comments

Comments
 (0)