Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor update...() methods #282

Merged
merged 11 commits into from
Jan 14, 2024
3 changes: 3 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<MixedAssignment errorLevel="suppress" />
</issueHandlers>
</psalm>
6 changes: 1 addition & 5 deletions src/ActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ public function transactions(): array
return [];
}

public function update(array $attributeNames = null): false|int
public function update(array $attributeNames = null): int
{
if (!$this->isTransactional(self::OP_UPDATE)) {
return $this->updateInternal($attributeNames);
Expand Down Expand Up @@ -366,10 +366,6 @@ protected function deleteInternal(): false|int
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();

if (is_array($condition) === false) {
darkdef marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

if ($lock !== null) {
$condition[$lock] = $this->$lock;
}
Expand Down
29 changes: 20 additions & 9 deletions src/ActiveRecordInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ public function getIsNewRecord(): bool;
* @return mixed The old primary key value. An array (column name => column value) is returned if the primary key
* is composite or `$asArray` is true. A string is returned otherwise (`null` will be returned if the key value is
* `null`).
*
* @psalm-return (
* $asArray is true
* ? array<string, mixed>
* : mixed|null
* )
*/
public function getOldPrimaryKey(bool $asArray = false): mixed;

Expand All @@ -172,6 +178,12 @@ public function getOldPrimaryKey(bool $asArray = false): mixed;
* @return mixed The primary key value. An array (attribute name => attribute value) is returned if the primary key
* is composite or `$asArray` is true. A string is returned otherwise (`null` will be returned if the key value is
* `null`).
*
* @psalm-return (
* $asArray is true
* ? array<string, mixed>
* : mixed|null
* )
*/
public function getPrimaryKey(bool $asArray = false): mixed;

Expand Down Expand Up @@ -322,7 +334,7 @@ public function populateRelation(string $name, array|self|null $records): void;
* @throws Exception
* @throws InvalidConfigException
*
* @psalm-return string[] The primary keys of the associated database table.
* @return string[] The primary keys of the associated database table.
*/
public function primaryKey(): array;

Expand Down Expand Up @@ -377,7 +389,7 @@ public function setAttribute(string $name, mixed $value): void;
* For this reason, you should use the following code to check if update() is successful or not:
*
* ```php
* if ($customer->update() !== false) {
* if ($customer->update() !== 0) {
* // update successful
* } else {
* // update failed
Expand All @@ -391,10 +403,9 @@ public function setAttribute(string $name, mixed $value): void;
* outdated.
* @throws Throwable In case update failed.
*
* @return false|int The number of rows affected, or false if validation fails or {@seebeforeSave()} stops the
* updating process.
* @return int The number of rows affected.
*/
public function update(array $attributeNames = null): false|int;
public function update(array $attributeNames = null): int;

/**
* Updates the whole table using the provided attribute values and conditions.
Expand All @@ -410,10 +421,10 @@ public function update(array $attributeNames = null): false|int;
*
* ```php
* $customerQuery = new ActiveQuery(Customer::class, $db);
* $aqClasses = $customerQuery->where('status = 2')->all();
* foreach ($aqClasses as $aqClass) {
* $aqClass->status = 1;
* $aqClass->update();
* $customers = $customerQuery->where('status = 2')->all();
* foreach ($customers as $customer) {
* $customer->status = 1;
* $customer->update();
* }
* ```
*
Expand Down
95 changes: 33 additions & 62 deletions src/BaseActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@
*/
$condition = $this->getOldPrimaryKey(true);

if (is_array($condition) === false) {
return false;
}

$lock = $this->optimisticLock();

if ($lock !== null) {
Expand Down Expand Up @@ -231,7 +227,7 @@
* @throws InvalidConfigException
* @throws Exception
*/
public function getOldPrimaryKey(bool $asArray = false): array|string|null
public function getOldPrimaryKey(bool $asArray = false): mixed
{
$keys = $this->primaryKey();

Expand All @@ -242,16 +238,13 @@
);
}

if (!$asArray && count($keys) === 1) {
/** @psalm-var string|null */
if ($asArray === false && count($keys) === 1) {
return $this->oldAttributes[$keys[0]] ?? null;
}

$values = [];

/** @psalm-var list<string> $keys */
foreach ($keys as $name) {
/** @psalm-var string|null */
$values[$name] = $this->oldAttributes[$name] ?? null;
}

Expand All @@ -262,16 +255,14 @@
{
$keys = $this->primaryKey();

if (!$asArray && count($keys) === 1) {
return $this->attributes[$keys[0]] ?? null;
if ($asArray === false && count($keys) === 1) {
return $this->{$keys[0]};
}

$values = [];

/** @psalm-var list<string> $keys */
foreach ($keys as $name) {
/** @psalm-var string|null */
$values[$name] = $this->attributes[$name] ?? null;
$values[$name] = $this->$name;
}

return $values;
Expand Down Expand Up @@ -660,7 +651,9 @@
return $this->insert($attributeNames);
}

return $this->update($attributeNames) !== false;
$this->update($attributeNames);

return true;
darkdef marked this conversation as resolved.
Show resolved Hide resolved
}

public function setAttribute(string $name, mixed $value): void
Expand Down Expand Up @@ -739,7 +732,7 @@
$this->oldAttributes = $values;
}

public function update(array $attributeNames = null): false|int
public function update(array $attributeNames = null): int

Check warning on line 735 in src/BaseActiveRecord.php

View check run for this annotation

Codecov / codecov/patch

src/BaseActiveRecord.php#L735

Added line #L735 was not covered by tests
{
return $this->updateInternal($attributeNames);
}
Expand All @@ -757,16 +750,8 @@
{
$attrs = [];

$condition = $this->getOldPrimaryKey(true);

if ($condition === null || $condition === []) {
return 0;
}

/** @psalm-var mixed $value */
foreach ($attributes as $name => $value) {
if (is_int($name)) {
/** @psalm-var mixed */
$attrs[] = $value;
} else {
$this->$name = $value;
Expand All @@ -780,11 +765,10 @@
return 0;
}

$rows = $this->updateAll($values, $this->getOldPrimaryKey(true) ?? []);
$rows = $this->updateAll($values, $this->getOldPrimaryKey(true));

/** @psalm-var array<string, mixed> $value */
foreach ($values as $name => $value) {
$this->oldAttributes[$name] = $this->attributes[$name];
$this->oldAttributes[$name] = $value;
}

return $rows;
Expand Down Expand Up @@ -848,6 +832,8 @@
* @param array $counters The counters to be updated (attribute name => increment value), use negative values if you
* want to decrement the counters.
*
* @psalm-param array<string, int> $counters
*
* @throws Exception
* @throws NotSupportedException
*
Expand All @@ -857,18 +843,17 @@
*/
public function updateCounters(array $counters): bool
{
if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true) ?? '')) {
/** @psalm-var array<string, int> $counters */
foreach ($counters as $name => $value) {
$this->attributes[$name] = isset($this->attributes[$name])
? (int) $this->attributes[$name] + $value : $value;
$this->oldAttributes[$name] = $this->attributes[$name];
}
if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) === 0) {
return false;

Check warning on line 847 in src/BaseActiveRecord.php

View check run for this annotation

Codecov / codecov/patch

src/BaseActiveRecord.php#L847

Added line #L847 was not covered by tests
}

return true;
foreach ($counters as $name => $value) {
$value += $this->$name ?? 0;
$this->$name = $value;
$this->oldAttributes[$name] = $value;
}

return false;
return true;
}

public function unlink(string $name, ActiveRecordInterface $arClass, bool $delete = false): void
Expand Down Expand Up @@ -1180,41 +1165,27 @@
return 0;
}

/** @psalm-var mixed $condition */
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();

if ($lock !== null && is_array($condition)) {
$values[$lock] = (int) $this->$lock + 1;
/** @psalm-var array<array-key, mixed>|string */
$condition[$lock] = $this->$lock;
}
if ($lock !== null) {
$lockValue = $this->$lock;

/**
* We do not check the return value of updateAll() because it's possible that the UPDATE statement doesn't
* change anything and thus returns 0.
*
* @psalm-var array<array-key, mixed>|string $condition
*/
$rows = $this->updateAll($values, $condition);
$condition[$lock] = $lockValue;
$values[$lock] = ++$lockValue;

if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.');
}
$rows = $this->updateAll($values, $condition);

if (isset($values[$lock])) {
$this->$lock = $values[$lock];
}
if ($rows === 0) {
throw new StaleObjectException('The object being updated is outdated.');
}

$changedAttributes = [];
$this->$lock = $lockValue;
} else {
$rows = $this->updateAll($values, $condition);
}

/**
* @psalm-var string $name
* @psalm-var mixed $value
*/
foreach ($values as $name => $value) {
/** @psalm-var mixed */
$changedAttributes[$name] = $this->oldAttributes[$name] ?? null;
$this->oldAttributes[$name] = $value;
}

Expand Down
36 changes: 36 additions & 0 deletions tests/ActiveRecordTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -836,4 +836,40 @@ public function testToArrayForArrayable(): void
]),
);
}

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

$customerQuery = new ActiveQuery(Customer::class, $this->db);

$customer = $customerQuery->findOne(1);

$this->assertTrue($customer->save());
}

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

$customerQuery = new ActiveQuery(Customer::class, $this->db);

$customer = $customerQuery->findOne(1);

$this->assertSame(1, $customer->getPrimaryKey());
$this->assertSame(['id' => 1], $customer->getPrimaryKey(true));
}

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

$customerQuery = new ActiveQuery(Customer::class, $this->db);

$customer = $customerQuery->findOne(1);
$customer->id = 2;

$this->assertSame(1, $customer->getOldPrimaryKey());
Tigrov marked this conversation as resolved.
Show resolved Hide resolved
$this->assertSame(['id' => 1], $customer->getOldPrimaryKey(true));
}
}