Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/ActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down
65 changes: 65 additions & 0 deletions tests/TypedPropertyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace flight\tests;

use flight\tests\classes\TypedUser;
use PDO;

/**
* Tests that insert() correctly assigns a typed int primary key
* after insert when the subclass declares public int $id.
*/
class TypedPropertyTest extends \PHPUnit\Framework\TestCase
{
protected PDO $pdo;

public static function setUpBeforeClass(): void
{
require_once __DIR__ . '/classes/TypedUser.php';
@unlink('test_typed.db');
}

public static function tearDownAfterClass(): void
{
@unlink('test_typed.db');
}

public function setUp(): void
{
$this->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']);
}
}
23 changes: 23 additions & 0 deletions tests/classes/TypedUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace flight\tests\classes;

use flight\ActiveRecord;

/**
* Test subclass with typed public properties.
* Used to verify ActiveRecord works correctly when subclasses
* declare typed properties instead of using dynamic properties.
*/
class TypedUser extends ActiveRecord
{
public int $id;
public string $name;
public string $password;
public ?string $created_dt = null;

public function __construct($databaseConnection = null, array $config = [])
{
parent::__construct($databaseConnection, 'user', $config);
}
}