diff --git a/psalm.xml b/psalm.xml
index e66ef57a1..19d34d11d 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -14,4 +14,7 @@
+
+
+
diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php
index 7b408a43d..90318b562 100644
--- a/src/ActiveRecord.php
+++ b/src/ActiveRecord.php
@@ -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);
@@ -366,10 +366,6 @@ protected function deleteInternal(): false|int
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
- if (is_array($condition) === false) {
- return false;
- }
-
if ($lock !== null) {
$condition[$lock] = $this->$lock;
}
diff --git a/src/ActiveRecordInterface.php b/src/ActiveRecordInterface.php
index f8a9952b8..6af7b3cd7 100644
--- a/src/ActiveRecordInterface.php
+++ b/src/ActiveRecordInterface.php
@@ -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
+ * : mixed|null
+ * )
*/
public function getOldPrimaryKey(bool $asArray = false): mixed;
@@ -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
+ * : mixed|null
+ * )
*/
public function getPrimaryKey(bool $asArray = false): mixed;
@@ -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;
@@ -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
@@ -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.
@@ -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();
* }
* ```
*
diff --git a/src/BaseActiveRecord.php b/src/BaseActiveRecord.php
index 9e34d15f5..e40125933 100644
--- a/src/BaseActiveRecord.php
+++ b/src/BaseActiveRecord.php
@@ -69,10 +69,6 @@ public function delete(): false|int
*/
$condition = $this->getOldPrimaryKey(true);
- if (is_array($condition) === false) {
- return false;
- }
-
$lock = $this->optimisticLock();
if ($lock !== null) {
@@ -231,7 +227,7 @@ public function getOldAttributes(): array
* @throws InvalidConfigException
* @throws Exception
*/
- public function getOldPrimaryKey(bool $asArray = false): array|string|null
+ public function getOldPrimaryKey(bool $asArray = false): mixed
{
$keys = $this->primaryKey();
@@ -242,16 +238,13 @@ public function getOldPrimaryKey(bool $asArray = false): array|string|null
);
}
- 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 $keys */
foreach ($keys as $name) {
- /** @psalm-var string|null */
$values[$name] = $this->oldAttributes[$name] ?? null;
}
@@ -262,16 +255,14 @@ public function getPrimaryKey(bool $asArray = false): mixed
{
$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 $keys */
foreach ($keys as $name) {
- /** @psalm-var string|null */
- $values[$name] = $this->attributes[$name] ?? null;
+ $values[$name] = $this->$name;
}
return $values;
@@ -660,7 +651,9 @@ public function save(array $attributeNames = null): bool
return $this->insert($attributeNames);
}
- return $this->update($attributeNames) !== false;
+ $this->update($attributeNames);
+
+ return true;
}
public function setAttribute(string $name, mixed $value): void
@@ -739,7 +732,7 @@ public function setOldAttributes(array $values = null): void
$this->oldAttributes = $values;
}
- public function update(array $attributeNames = null): false|int
+ public function update(array $attributeNames = null): int
{
return $this->updateInternal($attributeNames);
}
@@ -757,16 +750,8 @@ public function updateAttributes(array $attributes): int
{
$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;
@@ -780,11 +765,10 @@ public function updateAttributes(array $attributes): int
return 0;
}
- $rows = $this->updateAll($values, $this->getOldPrimaryKey(true) ?? []);
+ $rows = $this->updateAll($values, $this->getOldPrimaryKey(true));
- /** @psalm-var array $value */
foreach ($values as $name => $value) {
- $this->oldAttributes[$name] = $this->attributes[$name];
+ $this->oldAttributes[$name] = $value;
}
return $rows;
@@ -848,6 +832,8 @@ public function updateAllCounters(array $counters, array|string $condition = '',
* @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 $counters
+ *
* @throws Exception
* @throws NotSupportedException
*
@@ -857,18 +843,17 @@ public function updateAllCounters(array $counters, array|string $condition = '',
*/
public function updateCounters(array $counters): bool
{
- if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true) ?? '')) {
- /** @psalm-var array $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;
+ }
- 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
@@ -1180,41 +1165,27 @@ protected function updateInternal(array $attributes = null): int
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|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|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;
}
diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php
index 903ee53ce..9ec06c79c 100644
--- a/tests/ActiveRecordTest.php
+++ b/tests/ActiveRecordTest.php
@@ -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());
+ $this->assertSame(['id' => 1], $customer->getOldPrimaryKey(true));
+ }
}