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..0be43e215 100644 --- a/src/ActiveRecordInterface.php +++ b/src/ActiveRecordInterface.php @@ -502,6 +502,16 @@ 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. + * + * @param array $fields the fields that the output array should contain. Fields not specified + * in {@see fields()} will be ignored. If this parameter is empty, all fields as specified + * in {@see fields()} will be returned. + * @param array $expand the additional fields that the output array should contain. + * Fields not specified in {@see extraFields()} will be ignored. If this parameter is empty, no extra fields + * will be returned. + * @param bool $recursive Whether to recursively return array representation of embedded objects. + * + * @return array The array representation of the object. */ - public function toArray(): array; + public function toArray(array $fields = [], array $expand = [], bool $recursive = true): array; } diff --git a/src/BaseActiveRecord.php b/src/BaseActiveRecord.php index bdbdd4d1d..fc40a9c76 100644 --- a/src/BaseActiveRecord.php +++ b/src/BaseActiveRecord.php @@ -9,6 +9,9 @@ use IteratorAggregate; use ReflectionException; use Throwable; +use Yiisoft\Arrays\ArrayableInterface; +use Yiisoft\Arrays\ArrayableTrait; +use Yiisoft\Arrays\ArrayHelper; use Yiisoft\Db\Connection\ConnectionInterface; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidArgumentException; @@ -40,8 +43,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 = []; @@ -1266,19 +1270,28 @@ public function getTableName(): string return $this->tableName; } - public function toArray(): array + /** + * @inheritDoc + */ + public function toArray(array $fields = [], array $expand = [], bool $recursive = true): 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]; + foreach ($this->resolveFields($fields, $expand) as $field => $definition) { + $attribute = $definition instanceof Closure ? $definition($this, $field) : $definition; + + if ($recursive) { + $nestedFields = $this->extractFieldsFor($fields, $field); + $nestedExpand = $this->extractFieldsFor($expand, $field); + if ($attribute instanceof ArrayableInterface) { + $attribute = $attribute->toArray($nestedFields, $nestedExpand); + } elseif (is_array($attribute) && ($nestedExpand || $nestedFields)) { + $attribute = $this->filterAndExpand($attribute, $nestedFields, $nestedExpand); + } } + $data[$field] = $attribute; } - return $data; + + return $recursive ? ArrayHelper::toArray($data) : $data; } } diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php index 4d054d29e..6fb1bfc7a 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; @@ -20,6 +21,7 @@ use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItemWithNullFK; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Type; use Yiisoft\ActiveRecord\Tests\Support\Assert; +use Yiisoft\Arrays\ArrayHelper; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidArgumentException; use Yiisoft\Db\Exception\InvalidCallException; @@ -763,4 +765,24 @@ public function testToArrayWithClosure(): void $customer->toArray(), ); } + + public function testToArrayForArrayable(): void + { + $this->checkFixture($this->db, 'customer', true); + + $customerQuery = new ActiveQuery(CustomerForArrayable::class, $this->db); + $customer = $customerQuery->findOne(1); + + $this->assertSame( + [ + 'id' => 1, + 'email' => 'user1@example.com', + 'name' => 'user1', + 'address' => 'address1', + 'status' => 'active', + 'profile_id' => 1, + ], + ArrayHelper::toArray($customer), + ); + } } diff --git a/tests/Driver/Oracle/ActiveRecordTest.php b/tests/Driver/Oracle/ActiveRecordTest.php index dbad51704..14f6f24d4 100644 --- a/tests/Driver/Oracle/ActiveRecordTest.php +++ b/tests/Driver/Oracle/ActiveRecordTest.php @@ -7,8 +7,10 @@ use Yiisoft\ActiveRecord\ActiveQuery; use Yiisoft\ActiveRecord\Tests\Driver\Oracle\Stubs\Customer; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerClosureField; +use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerForArrayable; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Type; use Yiisoft\ActiveRecord\Tests\Support\OracleHelper; +use Yiisoft\Arrays\ArrayHelper; final class ActiveRecordTest extends \Yiisoft\ActiveRecord\Tests\ActiveRecordTest { @@ -166,4 +168,24 @@ public function testToArrayWithClosure(): void $customer->toArray(), ); } + + public function testToArrayForArrayable(): void + { + $this->checkFixture($this->db, 'customer', true); + + $customerQuery = new ActiveQuery(CustomerForArrayable::class, $this->db); + $customer = $customerQuery->findOne(1); + + $this->assertSame( + [ + 'id' => 1, + 'email' => 'user1@example.com', + 'name' => 'user1', + 'address' => 'address1', + 'status' => 'active', + 'profile_id' => 1, + ], + ArrayHelper::toArray($customer), + ); + } } diff --git a/tests/Driver/Pgsql/ActiveRecordTest.php b/tests/Driver/Pgsql/ActiveRecordTest.php index 3a91cbd76..f6357ad84 100644 --- a/tests/Driver/Pgsql/ActiveRecordTest.php +++ b/tests/Driver/Pgsql/ActiveRecordTest.php @@ -12,9 +12,11 @@ use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\BoolAR; 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\DefaultPk; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\UserAR; use Yiisoft\ActiveRecord\Tests\Support\PgsqlHelper; +use Yiisoft\Arrays\ArrayHelper; use Yiisoft\Db\Expression\ArrayExpression; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\JsonExpression; @@ -381,4 +383,24 @@ public function testToArrayWithClosure(): void $customer->toArray(), ); } + + public function testToArrayForArrayable(): void + { + $this->checkFixture($this->db, 'customer', true); + + $customerQuery = new ActiveQuery(CustomerForArrayable::class, $this->db); + $customer = $customerQuery->findOne(1); + + $this->assertSame( + [ + 'id' => 1, + 'email' => 'user1@example.com', + 'name' => 'user1', + 'address' => 'address1', + 'status' => 'active', + 'profile_id' => 1, + ], + ArrayHelper::toArray($customer), + ); + } } diff --git a/tests/Stubs/ActiveRecord/CustomerForArrayable.php b/tests/Stubs/ActiveRecord/CustomerForArrayable.php new file mode 100644 index 000000000..70bd1662b --- /dev/null +++ b/tests/Stubs/ActiveRecord/CustomerForArrayable.php @@ -0,0 +1,31 @@ +status == 1 ? 'active' : 'inactive'; + + return $data; + } +} \ No newline at end of file