From 5ca44bdd09ad2cbef3e1159b0605c2e34ecf6042 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 25 Feb 2026 23:05:49 +0700 Subject: [PATCH 01/13] Instantiate AR model without constructor --- src/ActiveQuery.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ActiveQuery.php b/src/ActiveQuery.php index b18b94507..6f875c0cb 100644 --- a/src/ActiveQuery.php +++ b/src/ActiveQuery.php @@ -6,6 +6,7 @@ use Closure; use InvalidArgumentException; +use ReflectionClass; use ReflectionException; use Throwable; use Yiisoft\ActiveRecord\Internal\ArArrayHelper; @@ -162,7 +163,7 @@ final public function __construct( ) { $this->model = $modelClass instanceof ActiveRecordInterface ? $modelClass - : new $modelClass(); + : (new ReflectionClass($modelClass))->newInstanceWithoutConstructor(); parent::__construct($this->model->db()); } From 65e03ca17a47ff6aca918ed7b25b6c4b40c13cde Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 25 Feb 2026 23:25:31 +0700 Subject: [PATCH 02/13] Update tests --- CHANGELOG.md | 1 + docs/create-model.md | 33 +++----------------------- tests/Stubs/ActiveRecord/Order.php | 25 +++++++++---------- tests/Stubs/ActiveRecord/OrderItem.php | 10 ++++---- 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4c86d353..48bcdf69e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Bug #527: Fix PHPDoc tags `@see` (@mspirkov) - Enh #532, #533: Remove unnecessary files from Composer package (@mspirkov) +- Enh #538: Instantiate AR model without constructor (@Tigrov, @olegbaturin) ## 1.0.0 December 09, 2025 diff --git a/docs/create-model.md b/docs/create-model.md index c062f2a5c..3fa652497 100644 --- a/docs/create-model.md +++ b/docs/create-model.md @@ -203,11 +203,6 @@ use Yiisoft\ActiveRecord\Trait\MagicPropertiesTrait; final class User extends ActiveRecord { use MagicPropertiesTrait; - - public function tableName(): string - { - return '{{%user}}'; - } } ``` @@ -234,36 +229,14 @@ use Yiisoft\ActiveRecord\ActiveRecord; final class User extends ActiveRecord { public function __construct( - public ?int $id = null, - public ?string $username = null, - public ?string $email = null, + public int $id, + public string $username, + public string $email, public string $status = 'active', ) {} } ``` -### Limitations - -When using the constructor, you should either specify default values or `null` for the arguments, or avoid using the static -`ActiveRecord::query()` method. It will not work correctly. Instead, create a new model instance and create a new query -object by calling the `createQuery()` method on the model instance. - -```php -// If the constructor arguments do not have default values -$user = new User(1, 'admin', 'admin@example.net', 'active'); -/** @var Yiisoft\ActiveRecord\ActiveQueryInterface $query */ -$query = $user->createQuery(); -``` - -Then you can use the active query object as usual, for example: - -```php -$users = $query->where(['status' => 'active'])->all(); -``` - -Also, if the constructor arguments do not have default values, you cannot use `RepositoryTrait`, because it uses static -`ActiveRecord::query()` method. - ## Relations To define relations, use the `ActiveRecordInterface::relationQuery()` method. This method should return an instance of diff --git a/tests/Stubs/ActiveRecord/Order.php b/tests/Stubs/ActiveRecord/Order.php index a60163435..0600e7770 100644 --- a/tests/Stubs/ActiveRecord/Order.php +++ b/tests/Stubs/ActiveRecord/Order.php @@ -27,18 +27,19 @@ class Order extends ActiveRecord public const TABLE_NAME = 'order'; - protected ?int $id; - protected int $customer_id; - #[DefaultDateTimeOnInsert] - protected int|DateTimeInterface $created_at; - #[DefaultDateTimeOnInsert] - #[SetDateTimeOnUpdate] - protected int|DateTimeInterface $updated_at; - #[SoftDelete] - protected int|DateTimeInterface|null $deleted_at; - protected float $total; - - protected string|int|null $virtualCustomerId = null; + public function __construct( + protected ?int $id, + protected int $customer_id, + #[DefaultDateTimeOnInsert] + protected int|DateTimeInterface $created_at, + #[DefaultDateTimeOnInsert] + #[SetDateTimeOnUpdate] + protected int|DateTimeInterface $updated_at, + #[SoftDelete] + protected int|DateTimeInterface|null $deleted_at, + protected float $total, + protected string|int|null $virtualCustomerId = null, + ) {} public function tableName(): string { diff --git a/tests/Stubs/ActiveRecord/OrderItem.php b/tests/Stubs/ActiveRecord/OrderItem.php index e4841ea5f..c24af3746 100644 --- a/tests/Stubs/ActiveRecord/OrderItem.php +++ b/tests/Stubs/ActiveRecord/OrderItem.php @@ -16,10 +16,12 @@ final class OrderItem extends ActiveRecord { use CustomTableNameTrait; - protected int $order_id; - protected int $item_id; - protected int $quantity; - protected float $subtotal; + public function __construct( + protected int $order_id, + protected int $item_id, + protected int $quantity, + protected float $subtotal, + ) {} public function tableName(): string { From 01d004c87d2b4dc67bd957cb2beefd133ee8ae8d Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 26 Feb 2026 00:56:52 +0700 Subject: [PATCH 03/13] Add tests --- src/Trait/EventsTrait.php | 10 +- tests/ActiveRecordTest.php | 45 ++++++- tests/EventsTraitTest.php | 7 - tests/Stubs/ActiveRecord/Order.php | 25 ++-- tests/Stubs/ActiveRecord/OrderItem.php | 10 +- .../ActiveRecord/OrderItemWithConstructor.php | 96 +++++++++++++ .../ActiveRecord/OrderWithConstructor.php | 127 ++++++++++++++++++ 7 files changed, 283 insertions(+), 37 deletions(-) create mode 100644 tests/Stubs/ActiveRecord/OrderItemWithConstructor.php create mode 100644 tests/Stubs/ActiveRecord/OrderWithConstructor.php diff --git a/src/Trait/EventsTrait.php b/src/Trait/EventsTrait.php index 3a9a99447..4e065286c 100644 --- a/src/Trait/EventsTrait.php +++ b/src/Trait/EventsTrait.php @@ -4,7 +4,7 @@ namespace Yiisoft\ActiveRecord\Trait; -use Closure; +use ReflectionClass; use Yiisoft\ActiveRecord\ActiveQueryInterface; use Yiisoft\ActiveRecord\ActiveRecordInterface; use Yiisoft\ActiveRecord\Event\AfterCreateQuery; @@ -23,8 +23,6 @@ use Yiisoft\ActiveRecord\Event\BeforeUpsert; use Yiisoft\ActiveRecord\Event\EventDispatcherProvider; -use function is_string; - /** * Trait to implement event dispatching for ActiveRecord. * @@ -84,13 +82,11 @@ public function populateRecord(array|object $data): static return $this; } - public static function query(ActiveRecordInterface|Closure|string|null $modelClass = null): ActiveQueryInterface + public static function query(ActiveRecordInterface|string|null $modelClass = null): ActiveQueryInterface { $model = match (true) { - $modelClass === null => new static(), - is_string($modelClass) => new $modelClass(), $modelClass instanceof ActiveRecordInterface => $modelClass, - default => ($modelClass)(), + default => (new ReflectionClass($modelClass ?? static::class))->newInstanceWithoutConstructor(), }; $eventDispatcher = EventDispatcherProvider::get($model::class); diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php index 9aee4bb86..b093be227 100644 --- a/tests/ActiveRecordTest.php +++ b/tests/ActiveRecordTest.php @@ -32,6 +32,7 @@ use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Order; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItem; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItemWithNullFK; +use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderWithConstructor; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderWithFactory; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Profile; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Promotion; @@ -1060,7 +1061,7 @@ public function testWithFactoryWithConstructor(): void $this->assertInstanceOf(OrderWithFactory::class, $customer->getOrdersWithFactory()[0]); } - public function testWithFactoryNonInitiated(): void + public function testWithFactoryInitiated(): void { $orderQuery = OrderWithFactory::query(); $order = $orderQuery->findByPk(2); @@ -1069,10 +1070,9 @@ public function testWithFactoryNonInitiated(): void $this->assertInstanceOf(Customer::class, $customer); - $this->expectException(ArgumentCountError::class); - $this->expectExceptionMessage('Too few arguments to function'); - $customer = $order->getCustomerWithFactory(); + + $this->assertInstanceOf(Customer::class, $customer); } public function testSerialization(): void @@ -1939,5 +1939,42 @@ public function testGetAllWithHasOneAndArrayValue(): void $this->assertNull($promotions[1]->relation('singleItem')); } + public function testWithConstructorQuery(): void + { + /** @var OrderWithConstructor[] $orders */ + $orders = OrderWithConstructor::query()->all(); + + $this->assertCount(3, $orders); + } + + public function testWithConstructorRelations(): void + { + $orderItems = OrderWithConstructor::query()->findByPk(1)->getOrderItems(); + $this->assertCount(2, $orderItems); + } + + public function testWithConstructorRepositoryTrait(): void + { + $this->assertCount(3, OrderWithConstructor::findAll()); + $this->assertInstanceOf(OrderWithConstructor::class, OrderWithConstructor::findByPk(1)); + } + + public function testWithConstructorNewInstance(): void + { + $this->reloadFixtureAfterTest(); + + $newOrder = new OrderWithConstructor(1); + + $this->assertTrue($newOrder->isNew()); + $newOrder->save(); + $this->assertFalse($newOrder->isNew()); + $this->assertSame(4, $newOrder->getId()); + $this->assertNotNull($newOrder->getCreatedAt()); + $this->assertNotNull($newOrder->getUpdatedAt()); + $this->assertNull($newOrder->getDeletedAt()); + $this->assertSame(1, $newOrder->delete()); + $this->assertNotNull($newOrder->getDeletedAt()); + } + abstract protected function createFactory(): Factory; } diff --git a/tests/EventsTraitTest.php b/tests/EventsTraitTest.php index 8d1dea73a..568de4b28 100644 --- a/tests/EventsTraitTest.php +++ b/tests/EventsTraitTest.php @@ -110,13 +110,6 @@ static function (object $event) use ($customQuery): void { $this->assertSame($customQuery, $query); } - public function testQueryWithClosureModelClass(): void - { - $query = CategoryEventsModel::query(fn() => new Category()); - - $this->assertInstanceOf(Category::class, $query->getModel()); - } - public function testSaveWithEventPrevention(): void { EventDispatcherProvider::set( diff --git a/tests/Stubs/ActiveRecord/Order.php b/tests/Stubs/ActiveRecord/Order.php index 0600e7770..a60163435 100644 --- a/tests/Stubs/ActiveRecord/Order.php +++ b/tests/Stubs/ActiveRecord/Order.php @@ -27,19 +27,18 @@ class Order extends ActiveRecord public const TABLE_NAME = 'order'; - public function __construct( - protected ?int $id, - protected int $customer_id, - #[DefaultDateTimeOnInsert] - protected int|DateTimeInterface $created_at, - #[DefaultDateTimeOnInsert] - #[SetDateTimeOnUpdate] - protected int|DateTimeInterface $updated_at, - #[SoftDelete] - protected int|DateTimeInterface|null $deleted_at, - protected float $total, - protected string|int|null $virtualCustomerId = null, - ) {} + protected ?int $id; + protected int $customer_id; + #[DefaultDateTimeOnInsert] + protected int|DateTimeInterface $created_at; + #[DefaultDateTimeOnInsert] + #[SetDateTimeOnUpdate] + protected int|DateTimeInterface $updated_at; + #[SoftDelete] + protected int|DateTimeInterface|null $deleted_at; + protected float $total; + + protected string|int|null $virtualCustomerId = null; public function tableName(): string { diff --git a/tests/Stubs/ActiveRecord/OrderItem.php b/tests/Stubs/ActiveRecord/OrderItem.php index c24af3746..e4841ea5f 100644 --- a/tests/Stubs/ActiveRecord/OrderItem.php +++ b/tests/Stubs/ActiveRecord/OrderItem.php @@ -16,12 +16,10 @@ final class OrderItem extends ActiveRecord { use CustomTableNameTrait; - public function __construct( - protected int $order_id, - protected int $item_id, - protected int $quantity, - protected float $subtotal, - ) {} + protected int $order_id; + protected int $item_id; + protected int $quantity; + protected float $subtotal; public function tableName(): string { diff --git a/tests/Stubs/ActiveRecord/OrderItemWithConstructor.php b/tests/Stubs/ActiveRecord/OrderItemWithConstructor.php new file mode 100644 index 000000000..e0592db22 --- /dev/null +++ b/tests/Stubs/ActiveRecord/OrderItemWithConstructor.php @@ -0,0 +1,96 @@ +order_id; + } + + public function getItemId(): int + { + return $this->item_id; + } + + public function getQuantity(): int + { + return $this->quantity; + } + + public function getSubtotal(): float + { + return $this->subtotal; + } + + public function setOrderId(int $orderId): void + { + $this->set('order_id', $orderId); + } + + public function setItemId(int $itemId): void + { + $this->set('item_id', $itemId); + } + + public function setQuantity(int $quantity): void + { + $this->quantity = $quantity; + } + + public function setSubtotal(float $subtotal): void + { + $this->subtotal = $subtotal; + } + + public function relationQuery(string $name): ActiveQueryInterface + { + return match ($name) { + 'order' => $this->getOrderQuery(), + 'item' => $this->getItemQuery(), + default => parent::relationQuery($name), + }; + } + + public function getOrder(): ?OrderWithConstructor + { + return $this->relation('order'); + } + + public function getOrderQuery(): ActiveQuery + { + return $this->hasOne(OrderWithConstructor::class, ['id' => 'order_id']); + } + + public function getItem(): ?Item + { + return $this->relation('item'); + } + + public function getItemQuery(): ActiveQuery + { + return $this->hasOne(Item::class, ['id' => 'item_id']); + } +} diff --git a/tests/Stubs/ActiveRecord/OrderWithConstructor.php b/tests/Stubs/ActiveRecord/OrderWithConstructor.php new file mode 100644 index 000000000..8a037abac --- /dev/null +++ b/tests/Stubs/ActiveRecord/OrderWithConstructor.php @@ -0,0 +1,127 @@ +id; + } + + public function getCustomerId(): int + { + return $this->customer_id; + } + + public function getCreatedAt(): int|DateTimeInterface + { + return $this->created_at; + } + + public function getTotal(): float + { + return $this->total; + } + + public function getUpdatedAt(): int|DateTimeInterface + { + return $this->updated_at; + } + + public function getDeletedAt(): int|DateTimeInterface|null + { + return $this->deleted_at; + } + + public function setId(?int $id): void + { + $this->set('id', $id); + } + + public function setCustomerId(int $customerId): void + { + $this->set('customer_id', $customerId); + } + + public function setCreatedAt(int|DateTimeInterface $createdAt): void + { + $this->created_at = $createdAt; + } + + public function setUpdatedAt(int|DateTimeInterface $updatedAt): void + { + $this->updated_at = $updatedAt; + } + + public function setTotal(float $total): void + { + $this->total = $total; + } + + public function relationQuery(string $name): ActiveQueryInterface + { + return match ($name) { + 'customer' => $this->getCustomerQuery(), + 'orderItems' => $this->getOrderItemsQuery(), + default => parent::relationQuery($name), + }; + } + + public function getCustomer(): ?Customer + { + return $this->relation('customer'); + } + + public function getCustomerQuery(): ActiveQuery + { + return $this->hasOne(Customer::class, ['id' => 'customer_id']); + } + + public function getOrderItems(): array + { + return $this->relation('orderItems'); + } + + public function getOrderItemsQuery(): ActiveQuery + { + return $this->hasMany(OrderItemWithConstructor::class, ['order_id' => 'id']); + } +} From 2f18f3c7b69087bcbea0b36638f2fd8fd19aa120 Mon Sep 17 00:00:00 2001 From: Tigrov <8563175+Tigrov@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:59:35 +0000 Subject: [PATCH 04/13] Apply PHP CS Fixer and Rector changes (CI) --- tests/ActiveRecordTest.php | 1 - tests/EventsTraitTest.php | 1 - tests/Stubs/ActiveRecord/OrderWithConstructor.php | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php index b093be227..1aebb20cc 100644 --- a/tests/ActiveRecordTest.php +++ b/tests/ActiveRecordTest.php @@ -4,7 +4,6 @@ namespace Yiisoft\ActiveRecord\Tests; -use ArgumentCountError; use DivisionByZeroError; use InvalidArgumentException; use LogicException; diff --git a/tests/EventsTraitTest.php b/tests/EventsTraitTest.php index 568de4b28..f3fbf309b 100644 --- a/tests/EventsTraitTest.php +++ b/tests/EventsTraitTest.php @@ -11,7 +11,6 @@ use Yiisoft\ActiveRecord\Event\BeforeUpdate; use Yiisoft\ActiveRecord\Event\BeforeUpsert; use Yiisoft\ActiveRecord\Event\EventDispatcherProvider; -use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Category; use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CategoryEventsModel; use Yiisoft\Test\Support\EventDispatcher\SimpleEventDispatcher; diff --git a/tests/Stubs/ActiveRecord/OrderWithConstructor.php b/tests/Stubs/ActiveRecord/OrderWithConstructor.php index 8a037abac..9454d93ba 100644 --- a/tests/Stubs/ActiveRecord/OrderWithConstructor.php +++ b/tests/Stubs/ActiveRecord/OrderWithConstructor.php @@ -22,7 +22,7 @@ class OrderWithConstructor extends ActiveRecord use EventsTrait; use RepositoryTrait; - protected ?int $id; + protected ?int $id = null; public function __construct( protected int $customer_id, From 97c52024392948559ea1877a5c467d8899ea481b Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 26 Feb 2026 01:13:48 +0700 Subject: [PATCH 05/13] Fix --- rector.php | 1 + tests/Stubs/ActiveRecord/OrderWithConstructor.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rector.php b/rector.php index 21c42b7d6..3f8e69617 100644 --- a/rector.php +++ b/rector.php @@ -24,5 +24,6 @@ 'tests/Stubs/ActiveRecord/Category.php', 'tests/Stubs/ActiveRecord/Customer.php', 'tests/Stubs/ActiveRecord/Order.php', + 'tests/Stubs/ActiveRecord/OrderWithConstructor.php', ], ]); diff --git a/tests/Stubs/ActiveRecord/OrderWithConstructor.php b/tests/Stubs/ActiveRecord/OrderWithConstructor.php index 9454d93ba..8a037abac 100644 --- a/tests/Stubs/ActiveRecord/OrderWithConstructor.php +++ b/tests/Stubs/ActiveRecord/OrderWithConstructor.php @@ -22,7 +22,7 @@ class OrderWithConstructor extends ActiveRecord use EventsTrait; use RepositoryTrait; - protected ?int $id = null; + protected ?int $id; public function __construct( protected int $customer_id, From dcacc63addb03a03b9a673ae98f33dd2be16a8c7 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 26 Feb 2026 01:21:09 +0700 Subject: [PATCH 06/13] Update CHANGELOG.md [skip ci] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48bcdf69e..5366bb27c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Bug #527: Fix PHPDoc tags `@see` (@mspirkov) - Enh #532, #533: Remove unnecessary files from Composer package (@mspirkov) -- Enh #538: Instantiate AR model without constructor (@Tigrov, @olegbaturin) +- Enh #538: Instantiate AR model with constructor (@Tigrov, @olegbaturin) ## 1.0.0 December 09, 2025 From 719def0a78e30d76b1daa47dee79fae77470cc13 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 26 Feb 2026 01:24:16 +0700 Subject: [PATCH 07/13] Update CHANGELOG.md [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5366bb27c..0d1456e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Bug #527: Fix PHPDoc tags `@see` (@mspirkov) - Enh #532, #533: Remove unnecessary files from Composer package (@mspirkov) - Enh #538: Instantiate AR model with constructor (@Tigrov, @olegbaturin) +- Bug #538: Remove `Closure` type from parameter `$modelClass` of `EventsTrait::query()` method (@Tigrov) ## 1.0.0 December 09, 2025 From 11e57f45d334014cb5744e65bc65fca7d7598a50 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 26 Feb 2026 01:25:24 +0700 Subject: [PATCH 08/13] Update doc [skip ci] --- docs/create-model.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/create-model.md b/docs/create-model.md index 3fa652497..87fe449d3 100644 --- a/docs/create-model.md +++ b/docs/create-model.md @@ -228,8 +228,9 @@ use Yiisoft\ActiveRecord\ActiveRecord; **/ final class User extends ActiveRecord { + public ?int $id; + public function __construct( - public int $id, public string $username, public string $email, public string $status = 'active', From 58445c7671ccb98f1fe76232c531a0294b039ad0 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 26 Feb 2026 01:31:15 +0700 Subject: [PATCH 09/13] Update --- .../ActiveRecord/OrderWithConstructor.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Stubs/ActiveRecord/OrderWithConstructor.php b/tests/Stubs/ActiveRecord/OrderWithConstructor.php index 8a037abac..4851e7907 100644 --- a/tests/Stubs/ActiveRecord/OrderWithConstructor.php +++ b/tests/Stubs/ActiveRecord/OrderWithConstructor.php @@ -23,17 +23,17 @@ class OrderWithConstructor extends ActiveRecord use RepositoryTrait; protected ?int $id; + #[DefaultDateTimeOnInsert] + protected int|DateTimeInterface $created_at; + #[DefaultDateTimeOnInsert] + #[SetDateTimeOnUpdate] + protected int|DateTimeInterface $updated_at; + #[SoftDelete] + protected int|DateTimeInterface|null $deleted_at; + protected float $total = 0.0; public function __construct( protected int $customer_id, - #[DefaultDateTimeOnInsert] - protected int|DateTimeInterface|null $created_at = null, - #[DefaultDateTimeOnInsert] - #[SetDateTimeOnUpdate] - protected int|DateTimeInterface|null $updated_at = null, - #[SoftDelete] - protected int|DateTimeInterface|null $deleted_at = null, - protected float $total = 0, ) {} public function tableName(): string @@ -68,7 +68,7 @@ public function getUpdatedAt(): int|DateTimeInterface public function getDeletedAt(): int|DateTimeInterface|null { - return $this->deleted_at; + return $this->deleted_at ?? null; } public function setId(?int $id): void From e7d43441670d5b58ba7711a25cdb5868bc1b6472 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 26 Feb 2026 01:33:01 +0700 Subject: [PATCH 10/13] Update --- tests/Stubs/ActiveRecord/OrderWithConstructor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Stubs/ActiveRecord/OrderWithConstructor.php b/tests/Stubs/ActiveRecord/OrderWithConstructor.php index 4851e7907..4c01c462c 100644 --- a/tests/Stubs/ActiveRecord/OrderWithConstructor.php +++ b/tests/Stubs/ActiveRecord/OrderWithConstructor.php @@ -30,10 +30,10 @@ class OrderWithConstructor extends ActiveRecord protected int|DateTimeInterface $updated_at; #[SoftDelete] protected int|DateTimeInterface|null $deleted_at; - protected float $total = 0.0; public function __construct( protected int $customer_id, + protected float $total = 0.0, ) {} public function tableName(): string From 33f3890e049c0b2e830098f2e8625e6e156d9ef9 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Fri, 27 Feb 2026 15:48:18 +0700 Subject: [PATCH 11/13] Update CHANGELOG.md Co-authored-by: Alexander Makarov --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d1456e5d..b673aecb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Bug #527: Fix PHPDoc tags `@see` (@mspirkov) - Enh #532, #533: Remove unnecessary files from Composer package (@mspirkov) -- Enh #538: Instantiate AR model with constructor (@Tigrov, @olegbaturin) +- Enh #538: It is now possible to instantiate AR model with constructor (@Tigrov, @olegbaturin) - Bug #538: Remove `Closure` type from parameter `$modelClass` of `EventsTrait::query()` method (@Tigrov) ## 1.0.0 December 09, 2025 From 6bdf2ea152f9169b328b7ca8c3e70c2ba253dcdf Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 27 Feb 2026 23:18:29 +0700 Subject: [PATCH 12/13] Improve --- src/Trait/EventsTrait.php | 7 +++---- tests/ActiveRecordTest.php | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Trait/EventsTrait.php b/src/Trait/EventsTrait.php index 4e065286c..a3cf2ff62 100644 --- a/src/Trait/EventsTrait.php +++ b/src/Trait/EventsTrait.php @@ -84,10 +84,9 @@ public function populateRecord(array|object $data): static public static function query(ActiveRecordInterface|string|null $modelClass = null): ActiveQueryInterface { - $model = match (true) { - $modelClass instanceof ActiveRecordInterface => $modelClass, - default => (new ReflectionClass($modelClass ?? static::class))->newInstanceWithoutConstructor(), - }; + $model = !$modelClass instanceof ActiveRecordInterface + ? (new ReflectionClass($modelClass ?? static::class))->newInstanceWithoutConstructor() + : $modelClass; $eventDispatcher = EventDispatcherProvider::get($model::class); $eventDispatcher->dispatch($event = new BeforeCreateQuery($model)); diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php index 1aebb20cc..22cca4a86 100644 --- a/tests/ActiveRecordTest.php +++ b/tests/ActiveRecordTest.php @@ -1060,7 +1060,7 @@ public function testWithFactoryWithConstructor(): void $this->assertInstanceOf(OrderWithFactory::class, $customer->getOrdersWithFactory()[0]); } - public function testWithFactoryInitiated(): void + public function testWithFactoryNonInitiated(): void { $orderQuery = OrderWithFactory::query(); $order = $orderQuery->findByPk(2); From 4631f1004ec5c4282c2d8abee9518eb5a33a6ddb Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 27 Feb 2026 23:24:37 +0700 Subject: [PATCH 13/13] Improve --- src/Trait/EventsTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Trait/EventsTrait.php b/src/Trait/EventsTrait.php index a3cf2ff62..d46490289 100644 --- a/src/Trait/EventsTrait.php +++ b/src/Trait/EventsTrait.php @@ -84,9 +84,9 @@ public function populateRecord(array|object $data): static public static function query(ActiveRecordInterface|string|null $modelClass = null): ActiveQueryInterface { - $model = !$modelClass instanceof ActiveRecordInterface - ? (new ReflectionClass($modelClass ?? static::class))->newInstanceWithoutConstructor() - : $modelClass; + $model = $modelClass instanceof ActiveRecordInterface + ? $modelClass + : (new ReflectionClass($modelClass ?? static::class))->newInstanceWithoutConstructor(); $eventDispatcher = EventDispatcherProvider::get($model::class); $eventDispatcher->dispatch($event = new BeforeCreateQuery($model));