From ed7d9a7a90bf7997290ff6398ed5d2b575b5433e Mon Sep 17 00:00:00 2001 From: enlivenapp Date: Thu, 23 Apr 2026 20:25:08 -0400 Subject: [PATCH] Fix insert() TypeError when primary key is typed as int lastInsertId() returns a string, which throws a TypeError when assigned to a typed public int property under strict_types. Cast the value to int when the primary key property has an int type declaration. enlivenapp --- src/ActiveRecord.php | 14 +++++++- tests/TypedPropertyTest.php | 65 +++++++++++++++++++++++++++++++++++++ tests/classes/TypedUser.php | 23 +++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 tests/TypedPropertyTest.php create mode 100644 tests/classes/TypedUser.php diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 98646c7..5b3bd43 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -529,7 +529,19 @@ public function insert(): ActiveRecord $this->execute($this->buildSql(['insert', 'values']), $this->params); - $this->{$this->primaryKey} = $intentionallyAssignedPrimaryKey ?: $this->databaseConnection->lastInsertId(); + $lastInsertId = $intentionallyAssignedPrimaryKey ?: $this->databaseConnection->lastInsertId(); + + // Cast to int if the primary key property is typed as int, + // since lastInsertId() returns a string which fails under strict_types. + $classRef = new \ReflectionClass($this); + if ($classRef->hasProperty($this->primaryKey)) { + $type = $classRef->getProperty($this->primaryKey)->getType(); + if ($type instanceof \ReflectionNamedType && $type->getName() === 'int') { + $lastInsertId = (int) $lastInsertId; + } + } + + $this->{$this->primaryKey} = $lastInsertId; $this->processEvent(['afterInsert', 'afterSave'], [$this]); diff --git a/tests/TypedPropertyTest.php b/tests/TypedPropertyTest.php new file mode 100644 index 0000000..2ace001 --- /dev/null +++ b/tests/TypedPropertyTest.php @@ -0,0 +1,65 @@ +pdo = new PDO('sqlite:test_typed.db'); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->pdo->exec("CREATE TABLE IF NOT EXISTS user ( + id INTEGER PRIMARY KEY, + name TEXT, + password TEXT, + created_dt TEXT + )"); + } + + public function tearDown(): void + { + $this->pdo->exec("DROP TABLE IF EXISTS user"); + } + + public function testInsertSetsTypedIntId(): void + { + $user = new TypedUser($this->pdo); + // Use dirty() to set values since the dirty-sync fix is separate + $user->dirty(['name' => 'charlie', 'password' => 'hash3']); + $user->insert(); + + $this->assertIsInt($user->id, 'id should be int after insert, not string'); + $this->assertGreaterThan(0, $user->id); + } + + public function testInsertPersistsWithTypedId(): void + { + $user = new TypedUser($this->pdo); + $user->dirty(['name' => 'dave', 'password' => 'hash4']); + $user->insert(); + + // Verify persisted via raw query (avoids isHydrated dependency) + $row = $this->pdo->query("SELECT * FROM user WHERE id = {$user->id}")->fetch(PDO::FETCH_ASSOC); + $this->assertSame('dave', $row['name']); + } +} diff --git a/tests/classes/TypedUser.php b/tests/classes/TypedUser.php new file mode 100644 index 0000000..996c002 --- /dev/null +++ b/tests/classes/TypedUser.php @@ -0,0 +1,23 @@ +