Skip to content

Commit

Permalink
Fix AR with properties
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov committed Dec 29, 2023
1 parent 5964f2a commit 78023fe
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 27 deletions.
33 changes: 16 additions & 17 deletions src/BaseActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
use function array_intersect_key;
use function array_key_exists;
use function array_keys;
use function array_merge;
use function array_search;
use function array_values;
use function count;
use function get_object_vars;
use function in_array;
use function is_array;
use function is_int;
use function property_exists;
use function reset;

/**
Expand Down Expand Up @@ -187,11 +190,8 @@ public function getOldAttribute(string $name): mixed
*/
public function getDirtyAttributes(array $names = null): array
{
if ($names === null) {
$attributes = $this->attributes;
} else {
$attributes = array_intersect_key($this->attributes, array_flip($names));
}
$attributes = array_merge($this->attributes, get_object_vars($this));
$attributes = array_intersect_key($attributes, array_flip($names ?? $this->attributes()));

if ($this->oldAttributes === null) {
return $attributes;
Expand Down Expand Up @@ -570,21 +570,11 @@ public function optimisticLock(): string|null
*/
public function populateRecord(array|object $row): void
{
$columns = array_flip($this->attributes());

/**
* @psalm-var string $name
* @psalm-var mixed $value
*/
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$this->attributes[$name] = $value;
} elseif ($this->canSetProperty($name)) {
$this->$name = $value;
}
$this->populateAttribute($name, $value);
$this->oldAttributes[$name] = $value;
}

$this->oldAttributes = $this->attributes;
$this->related = [];
$this->relationsDependencies = [];
}
Expand Down Expand Up @@ -1252,4 +1242,13 @@ public function getTableName(): string

return $this->tableName;
}

private function populateAttribute(string $name, mixed $value): void
{
if (property_exists($this, $name)) {
$this->$name = $value;
} else {
$this->attributes[$name] = $value;
}
}
}
46 changes: 36 additions & 10 deletions tests/ActiveRecordTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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\CustomerWithProperties;
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Dog;
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Item;
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\NoExist;
Expand Down Expand Up @@ -597,14 +598,9 @@ public function testAttributeAccess(): void
$this->assertTrue($customer->canGetProperty('orderItems'));
$this->assertFalse($customer->canSetProperty('orderItems'));

try {
/** @var $itemClass ActiveRecordInterface */
$customer->orderItems = [new Item($this->db)];
$this->fail('setter call above MUST throw Exception');
} catch (Exception $e) {
/** catch exception "Setting read-only property" */
$this->assertInstanceOf(InvalidCallException::class, $e);
}
$this->expectException(UnknownPropertyException::class);
$this->expectExceptionMessage('Setting unknown property: ' . Customer::class . '::orderItems');
$customer->orderItems = [new Item($this->db)];

/** related attribute $customer->orderItems didn't change cause it's read-only */
$this->assertSame([], $customer->orderItems);
Expand Down Expand Up @@ -855,7 +851,7 @@ public function testGetDirtyAttributesOnNewRecord(): void
);
$this->assertEquals(
['email' => '[email protected]', 'address' => null],
$customer->getDirtyAttributes(['id', 'email', 'address', 'status']),
$customer->getDirtyAttributes(['id', 'email', 'address', 'status', 'unknown']),
);

$this->assertTrue($customer->save());
Expand Down Expand Up @@ -885,7 +881,37 @@ public function testGetDirtyAttributesAfterFind(): void
);
$this->assertEquals(
['email' => '[email protected]', 'address' => null],
$customer->getDirtyAttributes(['id', 'email', 'address', 'status']),
$customer->getDirtyAttributes(['id', 'email', 'address', 'status', 'unknown']),
);
}

public function testGetDirtyAttributesWithProperties(): void
{
$this->checkFixture($this->db, 'customer');

$customer = new CustomerWithProperties($this->db);
$this->assertSame([
'name' => null,
'address' => null,
], $customer->getDirtyAttributes());

$customerQuery = new ActiveQuery(CustomerWithProperties::class, $this->db);
$customer = $customerQuery->findOne(1);

$this->assertSame([], $customer->getDirtyAttributes());

$customer->setEmail('[email protected]');
$customer->setName('Adam');
$customer->setAddress(null);
$customer->setStatus(null);

$this->assertEquals(
['email' => '[email protected]', 'name' => 'Adam', 'address' => null, 'status' => null],
$customer->getDirtyAttributes(),
);
$this->assertEquals(
['email' => '[email protected]', 'address' => null],
$customer->getDirtyAttributes(['id', 'email', 'address', 'unknown']),
);
}
}
79 changes: 79 additions & 0 deletions tests/Stubs/ActiveRecord/CustomerWithProperties.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord;

use Yiisoft\ActiveRecord\ActiveQuery;
use Yiisoft\ActiveRecord\ActiveRecord;

/**
* Class Customer with defined properties.
*/
class CustomerWithProperties extends ActiveRecord
{
protected int $id;
protected string $email;
protected string|null $name = null;
public string|null $address = null;

public function getTableName(): string
{
return 'customer';
}

public function getId(): int
{
return $this->id;
}

public function getEmail(): string
{
return $this->email;
}

public function getName(): string|null
{
return $this->name;
}

public function getAddress(): string|null
{
return $this->address;
}

public function getStatus(): int|null
{
return $this->getAttribute('status');
}

public function getProfile(): ActiveQuery
{
return $this->hasOne(Profile::class, ['id' => 'profile_id']);
}

public function getOrders(): ActiveQuery
{
return $this->hasMany(Order::class, ['customer_id' => 'id'])->orderBy('[[id]]');
}

public function setEmail(string $email): void
{
$this->email = $email;
}

public function setName(string|null $name): void
{
$this->name = $name;
}

public function setAddress(string|null $address): void
{
$this->address = $address;
}

public function setStatus(int|null $status): void
{
$this->setAttribute('status', $status);
}
}

0 comments on commit 78023fe

Please sign in to comment.