@@ -403,6 +403,16 @@ protected static function getFieldnames(): array
403403 return self ::$ _tableColumns [$ class ];
404404 }
405405
406+ /**
407+ * Refresh cached table metadata for the current model class.
408+ *
409+ * @return void
410+ */
411+ public static function refreshTableMetadata (): void
412+ {
413+ unset(self ::$ _tableColumns [static ::class]);
414+ }
415+
406416 /**
407417 * Split a table name into schema and table, defaulting schema to public.
408418 *
@@ -985,7 +995,7 @@ protected static function fetchWhereWithSuffix(string $SQLfragment = '', array $
985995 * returns an array of objects of the sub-class which match the conditions
986996 *
987997 * @param string $SQLfragment conditions, sorting, grouping and limit to apply (to right of WHERE keywords)
988- * @param array<int, mixed> $params optional params to be escaped and injected into the SQL query (standrd PDO syntax)
998+ * @param array<int, mixed> $params optional params to be escaped and injected into the SQL query (standard PDO syntax)
989999 * @param bool $limitOne if true the first match will be returned
9901000 *
9911001 * @return array|static|null
@@ -999,7 +1009,7 @@ public static function fetchWhere(string $SQLfragment = '', array $params = [],
9991009 * returns an array of objects of the sub-class which match the conditions
10001010 *
10011011 * @param string $SQLfragment conditions, sorting, grouping and limit to apply (to right of WHERE keywords)
1002- * @param array $params optional params to be escaped and injected into the SQL query (standrd PDO syntax)
1012+ * @param array $params optional params to be escaped and injected into the SQL query (standard PDO syntax)
10031013 *
10041014 * @return array object[] of objects of calling class
10051015 */
@@ -1014,7 +1024,7 @@ public static function fetchAllWhere(string $SQLfragment = '', array $params = [
10141024 * returns an object of the sub-class which matches the conditions
10151025 *
10161026 * @param string $SQLfragment conditions, sorting, grouping and limit to apply (to right of WHERE keywords)
1017- * @param array $params optional params to be escaped and injected into the SQL query (standrd PDO syntax)
1027+ * @param array $params optional params to be escaped and injected into the SQL query (standard PDO syntax)
10181028 *
10191029 * @return static|null object of calling class
10201030 */
@@ -1137,7 +1147,7 @@ public function delete()
11371147 * Delete records based on an SQL conditions
11381148 *
11391149 * @param string $where SQL fragment of conditions
1140- * @param array $params optional params to be escaped and injected into the SQL query (standrd PDO syntax)
1150+ * @param array $params optional params to be escaped and injected into the SQL query (standard PDO syntax)
11411151 *
11421152 * @return \PDOStatement
11431153 */
@@ -1206,6 +1216,16 @@ protected function runUpdateValidation(): void
12061216 $ this ->validateForUpdate ();
12071217 }
12081218
1219+ /**
1220+ * Return a UTC timestamp string suitable for the built-in timestamp columns.
1221+ *
1222+ * @return string
1223+ */
1224+ protected static function currentTimestamp (): string
1225+ {
1226+ return gmdate ('Y-m-d H:i:s ' );
1227+ }
1228+
12091229 /**
12101230 * insert a row into the database table, and update the primary key field with the one generated on insert
12111231 *
@@ -1219,7 +1239,7 @@ protected function runUpdateValidation(): void
12191239 public function insert (bool $ autoTimestamp = true , bool $ allowSetPrimaryKey = false ): bool
12201240 {
12211241 $ pk = static ::$ _primary_column_name ;
1222- $ timeStr = gmdate ( ' Y-m-d H:i:s ' );
1242+ $ timeStr = static :: currentTimestamp ( );
12231243 if ($ autoTimestamp && in_array ('created_at ' , static ::getFieldnames ())) {
12241244 $ this ->created_at = $ timeStr ;
12251245 }
@@ -1291,7 +1311,7 @@ public function insert(bool $autoTimestamp = true, bool $allowSetPrimaryKey = fa
12911311 public function update (bool $ autoTimestamp = true ): bool
12921312 {
12931313 if ($ autoTimestamp && in_array ('updated_at ' , static ::getFieldnames ())) {
1294- $ this ->updated_at = gmdate ( ' Y-m-d H:i:s ' );
1314+ $ this ->updated_at = static :: currentTimestamp ( );
12951315 }
12961316 $ this ->runUpdateValidation ();
12971317 $ set = $ this ->setString ();
@@ -1305,10 +1325,11 @@ public function update(bool $autoTimestamp = true): bool
13051325 $ query ,
13061326 $ set ['params ' ]
13071327 );
1308- if ($ st -> rowCount () == 1 ) {
1328+ if ($ this -> updateSucceeded ( $ st ) ) {
13091329 $ this ->clearDirtyFields ();
1330+ return true ;
13101331 }
1311- return ( $ st -> rowCount () == 1 ) ;
1332+ return false ;
13121333 }
13131334
13141335 /**
@@ -1417,6 +1438,37 @@ protected function hasPrimaryKeyValue(): bool
14171438 return $ this ->$ primaryKey !== null ;
14181439 }
14191440
1441+ /**
1442+ * Determine whether an update succeeded even when the driver reports zero changed rows.
1443+ *
1444+ * @param \PDOStatement $statement
1445+ *
1446+ * @return bool
1447+ */
1448+ protected function updateSucceeded (\PDOStatement $ statement ): bool
1449+ {
1450+ $ count = $ statement ->rowCount ();
1451+
1452+ if ($ count === 1 ) {
1453+ return true ;
1454+ }
1455+
1456+ if ($ count === 0 ) {
1457+ return static ::existsWhere (
1458+ static ::_quote_identifier (static ::$ _primary_column_name ) . ' = ? ' ,
1459+ [$ this ->{static ::$ _primary_column_name }]
1460+ );
1461+ }
1462+
1463+ throw new ModelException (
1464+ sprintf (
1465+ 'Update affected %d rows for %s; expected at most one row. ' ,
1466+ $ count ,
1467+ static ::class
1468+ )
1469+ );
1470+ }
1471+
14201472 /**
14211473 * @param mixed $match
14221474 *
0 commit comments