Skip to content

Commit 821175f

Browse files
authored
Added caster Hyperf\Database\Model\Casts\AsArrayObject for ArrayObject. (#7487)
1 parent d5e90e8 commit 821175f

File tree

3 files changed

+245
-0
lines changed

3 files changed

+245
-0
lines changed

src/Model/Casts/ArrayObject.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Database\Model\Casts;
14+
15+
use ArrayObject as BaseArrayObject;
16+
use Hyperf\Collection\Collection;
17+
use Hyperf\Contract\Arrayable;
18+
use JsonSerializable;
19+
20+
/**
21+
* @template TKey of array-key
22+
* @template TItem
23+
* @extends BaseArrayObject<TKey, TItem>
24+
*/
25+
class ArrayObject extends BaseArrayObject implements Arrayable, JsonSerializable
26+
{
27+
/**
28+
* Get a collection containing the underlying array.
29+
*/
30+
public function collect(): Collection
31+
{
32+
return new Collection($this->getArrayCopy());
33+
}
34+
35+
/**
36+
* Get the instance as an array.
37+
*/
38+
public function toArray(): array
39+
{
40+
return $this->getArrayCopy();
41+
}
42+
43+
/**
44+
* Get the array that should be JSON serialized.
45+
*/
46+
public function jsonSerialize(): array
47+
{
48+
return $this->getArrayCopy();
49+
}
50+
}

src/Model/Casts/AsArrayObject.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Database\Model\Casts;
14+
15+
use Hyperf\Codec\Json;
16+
use Hyperf\Contract\Castable;
17+
use Hyperf\Contract\CastsAttributes;
18+
19+
class AsArrayObject implements Castable
20+
{
21+
/**
22+
* Get the caster class to use when casting from / to this cast target.
23+
* @return CastsAttributes<ArrayObject<array-key, mixed>, iterable>
24+
*/
25+
public static function castUsing(): CastsAttributes
26+
{
27+
return new class implements CastsAttributes {
28+
public function get($model, $key, $value, $attributes)
29+
{
30+
if (! isset($attributes[$key])) {
31+
return;
32+
}
33+
34+
$data = Json::decode($attributes[$key]);
35+
36+
return is_array($data) ? new ArrayObject($data, ArrayObject::ARRAY_AS_PROPS) : null;
37+
}
38+
39+
public function set($model, $key, $value, $attributes): array
40+
{
41+
return [$key => Json::encode($value)];
42+
}
43+
44+
public function serialize($model, string $key, $value, array $attributes)
45+
{
46+
return $value->getArrayCopy();
47+
}
48+
};
49+
}
50+
}

tests/DatabaseModelCustomCastingTest.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@
1313
namespace HyperfTest\Database;
1414

1515
use Hyperf\Collection\Arr;
16+
use Hyperf\Collection\Collection;
17+
use Hyperf\Contract\Arrayable;
1618
use Hyperf\Contract\Castable;
1719
use Hyperf\Contract\CastsAttributes;
1820
use Hyperf\Contract\CastsInboundAttributes;
1921
use Hyperf\Database\Exception\InvalidCastException;
22+
use Hyperf\Database\Model\Casts\ArrayObject;
23+
use Hyperf\Database\Model\Casts\AsArrayObject;
2024
use Hyperf\Database\Model\CastsValue;
2125
use Hyperf\Database\Model\Model;
2226
use HyperfTest\Database\Stubs\ContainerStub;
27+
use JsonSerializable;
2328
use Mockery;
2429
use PHPUnit\Framework\Attributes\CoversNothing;
2530
use PHPUnit\Framework\TestCase;
@@ -263,6 +268,129 @@ public function testThrowExceptionWhenCastClassNotExist()
263268
$model = new TestModelWithCustomCast();
264269
$model->invalid_caster = 'foo';
265270
}
271+
272+
public function testAsArrayObjectCasting()
273+
{
274+
$model = new TestModelWithArrayObjectCast();
275+
276+
// Test setting array data
277+
$data = ['name' => 'Hyperf', 'version' => '3.1', 'features' => ['fast', 'modern']];
278+
$model->config = $data;
279+
280+
$this->assertInstanceOf(ArrayObject::class, $model->config);
281+
$this->assertEquals($data, $model->config->toArray());
282+
$this->assertEquals(json_encode($data), $model->getAttributes()['config']);
283+
284+
// Test ArrayObject methods
285+
$this->assertEquals('Hyperf', $model->config['name']);
286+
$this->assertEquals('3.1', $model->config['version']);
287+
$this->assertCount(3, $model->config);
288+
289+
// Test collect method
290+
$collection = $model->config->collect();
291+
$this->assertInstanceOf(Collection::class, $collection);
292+
$this->assertEquals($data, $collection->toArray());
293+
294+
// Test modification
295+
$model->config['type'] = 'framework';
296+
$this->assertEquals('framework', $model->config['type']);
297+
$this->assertCount(4, $model->config);
298+
299+
// Test JSON serialization
300+
$this->assertEquals($model->config->toArray(), $model->config->jsonSerialize());
301+
302+
// Test with null value
303+
$model->config = null;
304+
$this->assertNull($model->config);
305+
306+
// Test with empty array
307+
$model->config = [];
308+
$this->assertInstanceOf(ArrayObject::class, $model->config);
309+
$this->assertCount(0, $model->config);
310+
$this->assertEquals([], $model->config->toArray());
311+
}
312+
313+
public function testAsArrayObjectCastingFromDatabase()
314+
{
315+
$model = new TestModelWithArrayObjectCast();
316+
317+
// Simulate loading from database
318+
$jsonData = json_encode(['database' => 'mysql', 'driver' => 'swoole']);
319+
$model->setRawAttributes(['config' => $jsonData]);
320+
321+
$this->assertInstanceOf(ArrayObject::class, $model->config);
322+
$this->assertEquals('mysql', $model->config['database']);
323+
$this->assertEquals('swoole', $model->config['driver']);
324+
$this->assertEquals(['database' => 'mysql', 'driver' => 'swoole'], $model->config->toArray());
325+
}
326+
327+
public function testAsArrayObjectCastingInvalidJson()
328+
{
329+
$model = new TestModelWithArrayObjectCast();
330+
$this->expectExceptionMessage('Syntax error');
331+
332+
// Test with invalid JSON - should return null
333+
$model->setRawAttributes(['config' => 'invalid json']);
334+
$this->assertNull($model->config);
335+
336+
// Test with non-array JSON - should return null
337+
$model->setRawAttributes(['config' => json_encode('string value')]);
338+
$this->assertNull($model->config);
339+
340+
$model->setRawAttributes(['config' => json_encode(123)]);
341+
$this->assertNull($model->config);
342+
}
343+
344+
public function testArrayObjectCollectMethod()
345+
{
346+
$data = ['a' => 1, 'b' => 2, 'c' => 3];
347+
$arrayObject = new ArrayObject($data);
348+
349+
$collection = $arrayObject->collect();
350+
$this->assertInstanceOf(Collection::class, $collection);
351+
$this->assertEquals($data, $collection->toArray());
352+
353+
// Test collection methods work
354+
$filtered = $arrayObject->collect()->filter(fn ($value) => $value > 1);
355+
$this->assertEquals(['b' => 2, 'c' => 3], $filtered->toArray());
356+
}
357+
358+
public function testArrayObjectInterfaces()
359+
{
360+
$data = ['key' => 'value', 'number' => 42];
361+
$arrayObject = new ArrayObject($data);
362+
363+
// Test Arrayable interface
364+
$this->assertInstanceOf(Arrayable::class, $arrayObject);
365+
$this->assertEquals($data, $arrayObject->toArray());
366+
367+
// Test JsonSerializable interface
368+
$this->assertInstanceOf(JsonSerializable::class, $arrayObject);
369+
$this->assertEquals($data, $arrayObject->jsonSerialize());
370+
371+
// Test array access
372+
$this->assertEquals('value', $arrayObject['key']);
373+
$this->assertEquals(42, $arrayObject['number']);
374+
375+
// Test modification
376+
$arrayObject['new_key'] = 'new_value';
377+
$this->assertEquals('new_value', $arrayObject['new_key']);
378+
}
379+
380+
public function testArrayObjectSerialization()
381+
{
382+
$model = new TestModelWithArrayObjectCast();
383+
$data = ['serialized' => true, 'data' => [1, 2, 3]];
384+
$model->config = $data;
385+
386+
// Test model serialization/unserialization
387+
$serialized = serialize($model);
388+
$unserialized = unserialize($serialized);
389+
390+
$this->assertInstanceOf(ArrayObject::class, $unserialized->config);
391+
$this->assertEquals($data, $unserialized->config->toArray());
392+
$this->assertEquals(json_encode($data), $unserialized->getAttributes()['config']);
393+
}
266394
}
267395

268396
class TestModelWithCustomCast extends Model
@@ -290,6 +418,23 @@ class TestModelWithCustomCast extends Model
290418
];
291419
}
292420

421+
class TestModelWithArrayObjectCast extends Model
422+
{
423+
/**
424+
* The attributes that aren't mass assignable.
425+
*/
426+
protected array $guarded = [];
427+
428+
/**
429+
* The attributes that should be cast to native types.
430+
*/
431+
protected array $casts = [
432+
'config' => AsArrayObject::class,
433+
'settings' => AsArrayObject::class,
434+
'metadata' => AsArrayObject::class,
435+
];
436+
}
437+
293438
class CastUsing implements Castable
294439
{
295440
/**

0 commit comments

Comments
 (0)