diff --git a/composer.json b/composer.json index 1d26f0fdf..9f43232ff 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "require": { "php": "^8.0", "ext-json": "*", + "yiisoft/arrays": "^3.0", "yiisoft/db": "^1.1", "yiisoft/factory": "^1.0" }, diff --git a/src/ActiveRecordInterface.php b/src/ActiveRecordInterface.php index 2177dd362..f8a9952b8 100644 --- a/src/ActiveRecordInterface.php +++ b/src/ActiveRecordInterface.php @@ -4,7 +4,6 @@ namespace Yiisoft\ActiveRecord; -use Closure; use Throwable; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidArgumentException; @@ -86,14 +85,6 @@ public function deleteAll(array $condition = []): int; */ public function equals(self $record): bool; - /** - * @return array The default implementation returns the names of the columns whose values have been populated into - * this record. - * - * @psalm-return array - */ - public function fields(): array; - /** * Filters array condition before it's assigned to a Query filter. * @@ -499,9 +490,4 @@ public function getOldAttributes(): array; * @throws InvalidConfigException */ public function populateRecord(array|object $row): void; - - /** - * Serializes the active record into its array implementation with attribute name as a key, and it values as value. - */ - public function toArray(): array; } diff --git a/src/BaseActiveRecord.php b/src/BaseActiveRecord.php index bdbdd4d1d..d6cb497e3 100644 --- a/src/BaseActiveRecord.php +++ b/src/BaseActiveRecord.php @@ -9,6 +9,8 @@ use IteratorAggregate; use ReflectionException; use Throwable; +use Yiisoft\Arrays\ArrayableInterface; +use Yiisoft\Arrays\ArrayableTrait; use Yiisoft\Db\Connection\ConnectionInterface; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidArgumentException; @@ -40,8 +42,9 @@ * @template-implements ArrayAccess * @template-implements IteratorAggregate */ -abstract class BaseActiveRecord implements ActiveRecordInterface, IteratorAggregate, ArrayAccess +abstract class BaseActiveRecord implements ActiveRecordInterface, IteratorAggregate, ArrayAccess, ArrayableInterface { + use ArrayableTrait; use BaseActiveRecordTrait; private array $attributes = []; @@ -115,7 +118,7 @@ public function extraFields(): array } /** - * @psalm-suppress MixedReturnTypeCoercion + * @psalm-return array */ public function fields(): array { @@ -1265,20 +1268,4 @@ public function getTableName(): string return $this->tableName; } - - public function toArray(): array - { - $data = []; - - foreach ($this->fields() as $key => $value) { - if ($value instanceof Closure) { - /** @var mixed */ - $data[$key] = $value($this); - } else { - /** @var mixed */ - $data[$value] = $this[$value]; - } - } - return $data; - } } diff --git a/src/BaseActiveRecordTrait.php b/src/BaseActiveRecordTrait.php index c5061b1ac..50c5ac2d4 100644 --- a/src/BaseActiveRecordTrait.php +++ b/src/BaseActiveRecordTrait.php @@ -250,11 +250,11 @@ public function offsetGet(mixed $offset): mixed * It is implicitly called when you use something like `$model[$offset] = $item;`. * * @param mixed $offset the offset to set element. - * @param mixed $item the element value. + * @param mixed $value the element value. */ - public function offsetSet(mixed $offset, mixed $item): void + public function offsetSet(mixed $offset, mixed $value): void { - $this->$offset = $item; + $this->$offset = $value; } /** diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php index 4d054d29e..7d232d6cf 100644 --- a/tests/ActiveRecordTest.php +++ b/tests/ActiveRecordTest.php @@ -10,6 +10,7 @@ use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Cat; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerClosureField; +use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerForArrayable; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerWithAlias; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Dog; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Item; @@ -763,4 +764,58 @@ public function testToArrayWithClosure(): void $customer->toArray(), ); } + + public function testToArrayForArrayable(): void + { + $this->checkFixture($this->db, 'customer', true); + + $customerQuery = new ActiveQuery(CustomerForArrayable::class, $this->db); + + /** @var CustomerForArrayable $customer */ + $customer = $customerQuery->findOne(1); + /** @var CustomerForArrayable $customer2 */ + $customer2 = $customerQuery->findOne(2); + /** @var CustomerForArrayable $customer3 */ + $customer3 = $customerQuery->findOne(3); + + $customer->setItem($customer2); + $customer->setItems($customer3); + + $this->assertSame( + [ + 'id' => 1, + 'email' => 'user1@example.com', + 'name' => 'user1', + 'address' => 'address1', + 'status' => 'active', + 'item' => [ + 'id' => 2, + 'email' => 'user2@example.com', + 'name' => 'user2', + 'status' => 'active', + ], + 'items' => [ + [ + 'id' => 3, + 'email' => 'user3@example.com', + 'name' => 'user3', + 'status' => 'inactive', + ], + ], + ], + $customer->toArray([ + 'id', + 'name', + 'email', + 'address', + 'status', + 'item.id', + 'item.name', + 'item.email', + 'items.0.id', + 'items.0.name', + 'items.0.email', + ]), + ); + } } diff --git a/tests/Stubs/ActiveRecord/CustomerForArrayable.php b/tests/Stubs/ActiveRecord/CustomerForArrayable.php new file mode 100644 index 000000000..591cd79cf --- /dev/null +++ b/tests/Stubs/ActiveRecord/CustomerForArrayable.php @@ -0,0 +1,57 @@ +item = $item; + } + + public function setItems(self ...$items) + { + $this->items = $items; + } + + public function toArray(array $fields = [], array $expand = [], bool $recursive = true): array + { + $data = parent::toArray($fields, $expand, $recursive); + + $data['status'] = $this->status == 1 ? 'active' : 'inactive'; + + return $data; + } +}