getConnection(); CustomerQuery::$joinWithProfile = false; } /** * {@inheritdoc} */ public function getCustomerClass() { return Customer::class; } /** * {@inheritdoc} */ public function getItemClass() { return Item::class; } /** * {@inheritdoc} */ public function getOrderClass() { return Order::class; } /** * {@inheritdoc} */ public function getOrderItemClass() { return OrderItem::class; } /** * @return string */ public function getCategoryClass() { return Category::class; } /** * {@inheritdoc} */ public function getOrderWithNullFKClass() { return OrderWithNullFK::class; } /** * {@inheritdoc} */ public function getOrderItemWithNullFKmClass() { return OrderItemWithNullFK::class; } public function testCustomColumns(): void { // find custom column if ($this->driverName === 'oci') { $customer = Customer::find()->select(['{{customer}}.*', '([[status]]*2) AS [[status2]]']) ->where(['name' => 'user3'])->one(); } else { $customer = Customer::find()->select(['*', '([[status]]*2) AS [[status2]]']) ->where(['name' => 'user3'])->one(); } $this->assertEquals(3, $customer->id); $this->assertEquals(4, $customer->status2); } public function testStatisticalFind(): void { // find count, sum, average, min, max, scalar $this->assertEquals(3, Customer::find()->count()); $this->assertEquals(2, Customer::find()->where('[[id]]=1 OR [[id]]=2')->count()); $this->assertEquals(6, Customer::find()->sum('[[id]]')); $this->assertEquals(2, Customer::find()->average('[[id]]')); $this->assertEquals(1, Customer::find()->min('[[id]]')); $this->assertEquals(3, Customer::find()->max('[[id]]')); $this->assertEquals(3, Customer::find()->select('COUNT(*)')->scalar()); } public function testFindScalar() { // query scalar $customerName = Customer::find()->where(['[[id]]' => 2])->select('[[name]]')->scalar(); $this->assertEquals('user2', $customerName); } public function testFindExists(): void { $this->assertTrue(Customer::find()->where(['[[id]]' => 2])->exists()); $this->assertFalse(Customer::find()->where(['[[id]]' => 42])->exists()); $this->assertTrue(Customer::find()->where(['[[id]]' => 2])->select('[[name]]')->exists()); $this->assertFalse(Customer::find()->where(['[[id]]' => 42])->select('[[name]]')->exists()); } public function testFindColumn() { /** @var TestCase|ActiveRecordTestTrait $this */ $this->assertEquals(['user1', 'user2', 'user3'], Customer::find()->select('[[name]]')->column()); $this->assertEquals(['user3', 'user2', 'user1'], Customer::find()->orderBy(['[[name]]' => SORT_DESC])->select('[[name]]')->column()); } public function testFindBySql(): void { // find one $customer = Customer::findBySql('SELECT * FROM {{customer}} ORDER BY [[id]] DESC')->one(); $this->assertInstanceOf(Customer::class, $customer); $this->assertEquals('user3', $customer->name); // find all $customers = Customer::findBySql('SELECT * FROM {{customer}}')->all(); $this->assertCount(3, $customers); // find with parameter binding $customer = Customer::findBySql('SELECT * FROM {{customer}} WHERE [[id]]=:id', [':id' => 2])->one(); $this->assertInstanceOf(Customer::class, $customer); $this->assertEquals('user2', $customer->name); } /** * @depends testFindBySql * * @see https://github.com/yiisoft/yii2/issues/8593 */ public function testCountWithFindBySql(): void { $query = Customer::findBySql('SELECT * FROM {{customer}}'); $this->assertEquals(3, $query->count()); $query = Customer::findBySql('SELECT * FROM {{customer}} WHERE [[id]]=:id', [':id' => 2]); $this->assertEquals(1, $query->count()); } public function testFindLazyViaTable(): void { /** @var Order $order */ $order = Order::findOne(1); $this->assertEquals(1, $order->id); $this->assertCount(2, $order->books); $this->assertEquals(1, $order->items[0]->id); $this->assertEquals(2, $order->items[1]->id); $order = Order::findOne(2); $this->assertEquals(2, $order->id); $this->assertCount(0, $order->books); $order = Order::find()->where(['id' => 1])->asArray()->one(); $this->assertIsArray($order); } public function testFindEagerViaTable(): void { $orders = Order::find()->with('books')->orderBy('id')->all(); $this->assertCount(3, $orders); $order = $orders[0]; $this->assertEquals(1, $order->id); $this->assertCount(2, $order->books); $this->assertEquals(1, $order->books[0]->id); $this->assertEquals(2, $order->books[1]->id); $order = $orders[1]; $this->assertEquals(2, $order->id); $this->assertCount(0, $order->books); $order = $orders[2]; $this->assertEquals(3, $order->id); $this->assertCount(1, $order->books); $this->assertEquals(2, $order->books[0]->id); // https://github.com/yiisoft/yii2/issues/1402 $orders = Order::find()->with('books')->orderBy('id')->asArray()->all(); $this->assertCount(3, $orders); $this->assertIsArray($orders[0]['orderItems'][0]); $order = $orders[0]; $this->assertIsArray($order); $this->assertEquals(1, $order['id']); $this->assertCount(2, $order['books']); $this->assertEquals(1, $order['books'][0]['id']); $this->assertEquals(2, $order['books'][1]['id']); } // deeply nested table relation public function testDeeplyNestedTableRelation(): void { /** @var Customer $customer */ $customer = Customer::findOne(1); $this->assertNotNull($customer); $items = $customer->orderItems; $this->assertCount(2, $items); $this->assertInstanceOf(Item::class, $items[0]); $this->assertInstanceOf(Item::class, $items[1]); $this->assertEquals(1, $items[0]->id); $this->assertEquals(2, $items[1]->id); } /** * @see https://github.com/yiisoft/yii2/issues/5341 * * Issue: Plan 1 -- * Account * -- * User * Our Tests: Category 1 -- * Item * -- * Order */ public function testDeeplyNestedTableRelation2(): void { /** @var Category $category */ $category = Category::findOne(1); $this->assertNotNull($category); $orders = $category->orders; $this->assertCount(2, $orders); $this->assertInstanceOf(Order::class, $orders[0]); $this->assertInstanceOf(Order::class, $orders[1]); $ids = [$orders[0]->id, $orders[1]->id]; sort($ids); $this->assertEquals([1, 3], $ids); $category = Category::findOne(2); $this->assertNotNull($category); $orders = $category->orders; $this->assertCount(1, $orders); $this->assertInstanceOf(Order::class, $orders[0]); $this->assertEquals(2, $orders[0]->id); } public function testStoreNull(): void { $record = new NullValues(); $this->assertNull($record->var1); $this->assertNull($record->var2); $this->assertNull($record->var3); $this->assertNull($record->stringcol); $record->var1 = 123; $record->var2 = 456; $record->var3 = 789; $record->stringcol = 'hello!'; $record->save(false); $this->assertTrue($record->refresh()); $this->assertEquals(123, $record->var1); $this->assertEquals(456, $record->var2); $this->assertEquals(789, $record->var3); $this->assertEquals('hello!', $record->stringcol); $record->var1 = null; $record->var2 = null; $record->var3 = null; $record->stringcol = null; $record->save(false); $this->assertTrue($record->refresh()); $this->assertNull($record->var1); $this->assertNull($record->var2); $this->assertNull($record->var3); $this->assertNull($record->stringcol); $record->var1 = 0; $record->var2 = 0; $record->var3 = 0; $record->stringcol = ''; $record->save(false); $this->assertTrue($record->refresh()); $this->assertEquals(0, $record->var1); $this->assertEquals(0, $record->var2); $this->assertEquals(0, $record->var3); $this->assertEquals('', $record->stringcol); } public function testStoreEmpty(): void { $record = new NullValues(); // this is to simulate empty html form submission $record->var1 = ''; $record->var2 = ''; $record->var3 = ''; $record->stringcol = ''; $record->save(false); $this->assertTrue($record->refresh()); // https://github.com/yiisoft/yii2/commit/34945b0b69011bc7cab684c7f7095d837892a0d4#commitcomment-4458225 $this->assertSame($record->var1, $record->var2); $this->assertSame($record->var2, $record->var3); } public function testIsPrimaryKey(): void { $this->assertFalse(Customer::isPrimaryKey([])); $this->assertTrue(Customer::isPrimaryKey(['id'])); $this->assertFalse(Customer::isPrimaryKey(['id', 'name'])); $this->assertFalse(Customer::isPrimaryKey(['name'])); $this->assertFalse(Customer::isPrimaryKey(['name', 'email'])); $this->assertFalse(OrderItem::isPrimaryKey([])); $this->assertFalse(OrderItem::isPrimaryKey(['order_id'])); $this->assertFalse(OrderItem::isPrimaryKey(['item_id'])); $this->assertFalse(OrderItem::isPrimaryKey(['quantity'])); $this->assertFalse(OrderItem::isPrimaryKey(['quantity', 'subtotal'])); $this->assertTrue(OrderItem::isPrimaryKey(['order_id', 'item_id'])); $this->assertFalse(OrderItem::isPrimaryKey(['order_id', 'item_id', 'quantity'])); } public function testJoinWith(): void { // left join and eager loading $orders = Order::find()->joinWith('customer')->orderBy('customer.id DESC, order.id')->all(); $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); $this->assertTrue($orders[2]->isRelationPopulated('customer')); // inner join filtering and eager loading $orders = Order::find()->innerJoinWith([ 'customer' => function ($query): void { $query->where('{{customer}}.[[id]]=2'); }, ])->orderBy('order.id')->all(); $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); // inner join filtering, eager loading, conditions on both primary and relation $orders = Order::find()->innerJoinWith([ 'customer' => function ($query): void { $query->where(['customer.id' => 2]); }, ])->where(['order.id' => [1, 2]])->orderBy('order.id')->all(); $this->assertCount(1, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); // inner join filtering without eager loading $orders = Order::find()->innerJoinWith([ 'customer' => function ($query): void { $query->where('{{customer}}.[[id]]=2'); }, ], false)->orderBy('order.id')->all(); $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertFalse($orders[0]->isRelationPopulated('customer')); $this->assertFalse($orders[1]->isRelationPopulated('customer')); // inner join filtering without eager loading, conditions on both primary and relation $orders = Order::find()->innerJoinWith([ 'customer' => function ($query): void { $query->where(['customer.id' => 2]); }, ], false)->where(['order.id' => [1, 2]])->orderBy('order.id')->all(); $this->assertCount(1, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertFalse($orders[0]->isRelationPopulated('customer')); // join with via-relation $orders = Order::find()->innerJoinWith('books')->orderBy('order.id')->all(); $this->assertCount(2, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('books')); $this->assertTrue($orders[1]->isRelationPopulated('books')); $this->assertCount(2, $orders[0]->books); $this->assertCount(1, $orders[1]->books); // join with sub-relation $orders = Order::find()->innerJoinWith([ 'items' => function ($q): void { $q->orderBy('item.id'); }, 'items.category' => function ($q): void { $q->where('{{category}}.[[id]] = 2'); }, ])->orderBy('order.id')->all(); $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); // join with table alias $orders = Order::find()->joinWith([ 'customer' => function ($q): void { $q->from('customer c'); }, ])->orderBy('c.id DESC, order.id')->all(); $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); $this->assertTrue($orders[2]->isRelationPopulated('customer')); // join with table alias $orders = Order::find()->joinWith('customer as c')->orderBy('c.id DESC, order.id')->all(); $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); $this->assertTrue($orders[2]->isRelationPopulated('customer')); // join with table alias sub-relation $orders = Order::find()->innerJoinWith([ 'items as t' => function ($q): void { $q->orderBy('t.id'); }, 'items.category as c' => function ($q): void { $q->where('{{c}}.[[id]] = 2'); }, ])->orderBy('order.id')->all(); $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); // join with ON condition $orders = Order::find()->joinWith('books2')->orderBy('order.id')->all(); $this->assertCount(3, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(2, $orders[1]->id); $this->assertEquals(3, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('books2')); $this->assertTrue($orders[1]->isRelationPopulated('books2')); $this->assertTrue($orders[2]->isRelationPopulated('books2')); $this->assertCount(2, $orders[0]->books2); $this->assertCount(0, $orders[1]->books2); $this->assertCount(1, $orders[2]->books2); // lazy loading with ON condition $order = Order::findOne(1); $this->assertCount(2, $order->books2); $order = Order::findOne(2); $this->assertCount(0, $order->books2); $order = Order::findOne(3); $this->assertCount(1, $order->books2); // eager loading with ON condition $orders = Order::find()->with('books2')->all(); $this->assertCount(3, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(2, $orders[1]->id); $this->assertEquals(3, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('books2')); $this->assertTrue($orders[1]->isRelationPopulated('books2')); $this->assertTrue($orders[2]->isRelationPopulated('books2')); $this->assertCount(2, $orders[0]->books2); $this->assertCount(0, $orders[1]->books2); $this->assertCount(1, $orders[2]->books2); // join with count and query $query = Order::find()->joinWith('customer'); $count = $query->count(); $this->assertEquals(3, $count); $orders = $query->all(); $this->assertCount(3, $orders); // https://github.com/yiisoft/yii2/issues/2880 $query = Order::findOne(1); $customer = $query->getCustomer() ->joinWith([ 'orders' => function ($q): void { $q->orderBy([]); }, ]) ->one(); $this->assertEquals(1, $customer->id); $order = Order::find()->joinWith([ 'items' => function ($q): void { $q->from(['items' => 'item']) ->orderBy('items.id'); }, ])->orderBy('order.id')->one(); // join with sub-relation called inside Closure $orders = Order::find() ->joinWith([ 'items' => function ($q): void { $q->orderBy('item.id'); $q->joinWith([ 'category' => function ($q): void { $q->where('{{category}}.[[id]] = 2'); }, ]); }, ]) ->orderBy('order.id') ->all(); $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); } /** * @depends testJoinWith */ public function testJoinWithDuplicateSimple(): void { // left join and eager loading $orders = Order::find() ->innerJoinWith('customer') ->joinWith('customer') ->orderBy('customer.id DESC, order.id') ->all(); $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); $this->assertTrue($orders[2]->isRelationPopulated('customer')); } /** * @depends testJoinWith */ public function testJoinWithDuplicateCallbackFiltering(): void { // inner join filtering and eager loading $orders = Order::find() ->innerJoinWith('customer') ->joinWith([ 'customer' => function ($query): void { $query->where('{{customer}}.[[id]]=2'); }, ])->orderBy('order.id')->all(); $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); } /** * @depends testJoinWith */ public function testJoinWithDuplicateCallbackFilteringConditionsOnPrimary(): void { // inner join filtering, eager loading, conditions on both primary and relation $orders = Order::find() ->innerJoinWith('customer') ->joinWith([ 'customer' => function ($query): void { $query->where(['{{customer}}.[[id]]' => 2]); }, ])->where(['order.id' => [1, 2]])->orderBy('order.id')->all(); $this->assertCount(1, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); } /** * @depends testJoinWith */ public function testJoinWithDuplicateWithSubRelation(): void { // join with sub-relation $orders = Order::find() ->innerJoinWith('items') ->joinWith([ 'items.category' => function ($q): void { $q->where('{{category}}.[[id]] = 2'); }, ])->orderBy('order.id')->all(); $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); } /** * @depends testJoinWith */ public function testJoinWithDuplicateTableAlias1(): void { // join with table alias $orders = Order::find() ->innerJoinWith('customer') ->joinWith([ 'customer' => function ($q): void { $q->from('customer c'); }, ])->orderBy('c.id DESC, order.id')->all(); $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); $this->assertTrue($orders[2]->isRelationPopulated('customer')); } /** * @depends testJoinWith */ public function testJoinWithDuplicateTableAlias2(): void { // join with table alias $orders = Order::find() ->innerJoinWith('customer') ->joinWith('customer as c') ->orderBy('c.id DESC, order.id') ->all(); $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); $this->assertTrue($orders[2]->isRelationPopulated('customer')); } /** * @depends testJoinWith */ public function testJoinWithDuplicateTableAliasSubRelation(): void { // join with table alias sub-relation $orders = Order::find() ->innerJoinWith([ 'items as t' => function ($q): void { $q->orderBy('t.id'); }, ]) ->joinWith([ 'items.category as c' => function ($q): void { $q->where('{{c}}.[[id]] = 2'); }, ])->orderBy('order.id')->all(); $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); } /** * @depends testJoinWith */ public function testJoinWithDuplicateSubRelationCalledInsideClosure(): void { // join with sub-relation called inside Closure $orders = Order::find() ->innerJoinWith('items') ->joinWith([ 'items' => function ($q): void { $q->orderBy('item.id'); $q->joinWith([ 'category' => function ($q): void { $q->where('{{category}}.[[id]] = 2'); }, ]); }, ]) ->orderBy('order.id') ->all(); $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); } /** * @depends testJoinWith */ public function testJoinWithAndScope(): void { // hasOne inner join $customers = Customer::find()->active()->innerJoinWith('profile')->orderBy('customer.id')->all(); $this->assertCount(1, $customers); $this->assertEquals(1, $customers[0]->id); $this->assertTrue($customers[0]->isRelationPopulated('profile')); // hasOne outer join $customers = Customer::find()->active()->joinWith('profile')->orderBy('customer.id')->all(); $this->assertCount(2, $customers); $this->assertEquals(1, $customers[0]->id); $this->assertEquals(2, $customers[1]->id); $this->assertTrue($customers[0]->isRelationPopulated('profile')); $this->assertTrue($customers[1]->isRelationPopulated('profile')); $this->assertInstanceOf(Profile::class, $customers[0]->profile); $this->assertNull($customers[1]->profile); // hasMany $customers = Customer::find()->active()->joinWith([ 'orders' => function ($q): void { $q->orderBy('order.id'); }, ])->orderBy('customer.id DESC, order.id')->all(); $this->assertCount(2, $customers); $this->assertEquals(2, $customers[0]->id); $this->assertEquals(1, $customers[1]->id); $this->assertTrue($customers[0]->isRelationPopulated('orders')); $this->assertTrue($customers[1]->isRelationPopulated('orders')); } /** * This query will do the same join twice, ensure duplicated JOIN gets removed. * * @see https://github.com/yiisoft/yii2/pull/2650 */ public function testJoinWithVia(): void { Order::getDb()->getQueryBuilder()->separator = "\n"; $rows = Order::find()->joinWith('itemsInOrder1')->joinWith([ 'items' => function ($q): void { $q->orderBy('item.id'); }, ])->all(); $this->assertNotEmpty($rows); } /** * Test joinWith eager loads via relation * * @see https://github.com/yiisoft/yii2/issues/19507 */ public function testJoinWithEager(): void { $eagerCustomers = Customer::find()->joinWith(['items'])->all(); $eagerItemsCount = 0; foreach ($eagerCustomers as $customer) { $eagerItemsCount += count($customer->items); } $lazyCustomers = Customer::find()->all(); $lazyItemsCount = 0; foreach ($lazyCustomers as $customer) { $lazyItemsCount += count($customer->items); } $this->assertEquals($eagerItemsCount, $lazyItemsCount); } public static function aliasMethodProvider(): array { return [ ['explicit'], // c //['querysyntax'], // {{@customer}} //['applyAlias'], // $query->applyAlias('customer', 'id') // _aliases are currently not being populated //later getRelationAlias() could be added ]; } /** * Tests the alias syntax for joinWith: 'alias' => 'relation'. * * @dataProvider aliasMethodProvider * * @param string $aliasMethod Whether alias is specified explicitly or using the query syntax {{@tablename}}. */ public function testJoinWithAlias(string $aliasMethod): void { // left join and eager loading /** @var ActiveQuery $query */ $query = Order::find()->joinWith(['customer c']); if ($aliasMethod === 'explicit') { $orders = $query->orderBy('c.id DESC, order.id')->all(); } elseif ($aliasMethod === 'querysyntax') { $orders = $query->orderBy('{{@customer}}.id DESC, {{@order}}.id')->all(); } elseif ($aliasMethod === 'applyAlias') { $orders = $query->orderBy($query->applyAlias('customer', 'id') . ' DESC,' . $query->applyAlias('order', 'id'))->all(); } $this->assertCount(3, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertEquals(1, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); $this->assertTrue($orders[2]->isRelationPopulated('customer')); // inner join filtering and eager loading $query = Order::find()->innerJoinWith(['customer c']); if ($aliasMethod === 'explicit') { $orders = $query->where('{{c}}.[[id]]=2')->orderBy('order.id')->all(); } elseif ($aliasMethod === 'querysyntax') { $orders = $query->where('{{@customer}}.[[id]]=2')->orderBy('{{@order}}.id')->all(); } elseif ($aliasMethod === 'applyAlias') { $orders = $query->where([$query->applyAlias('customer', 'id') => 2])->orderBy($query->applyAlias('order', 'id'))->all(); } $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('customer')); $this->assertTrue($orders[1]->isRelationPopulated('customer')); // inner join filtering without eager loading $query = Order::find()->innerJoinWith(['customer c'], false); if ($aliasMethod === 'explicit') { $orders = $query->where('{{c}}.[[id]]=2')->orderBy('order.id')->all(); } elseif ($aliasMethod === 'querysyntax') { $orders = $query->where('{{@customer}}.[[id]]=2')->orderBy('{{@order}}.id')->all(); } elseif ($aliasMethod === 'applyAlias') { $orders = $query->where([$query->applyAlias('customer', 'id') => 2])->orderBy($query->applyAlias('order', 'id'))->all(); } $this->assertCount(2, $orders); $this->assertEquals(2, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertFalse($orders[0]->isRelationPopulated('customer')); $this->assertFalse($orders[1]->isRelationPopulated('customer')); // join with via-relation $query = Order::find()->innerJoinWith(['books b']); if ($aliasMethod === 'explicit') { $orders = $query->where(['b.name' => 'Yii 1.1 Application Development Cookbook'])->orderBy('order.id')->all(); } elseif ($aliasMethod === 'querysyntax') { $orders = $query->where(['{{@item}}.name' => 'Yii 1.1 Application Development Cookbook'])->orderBy('{{@order}}.id')->all(); } elseif ($aliasMethod === 'applyAlias') { $orders = $query->where([$query->applyAlias('book', 'name') => 'Yii 1.1 Application Development Cookbook'])->orderBy($query->applyAlias('order', 'id'))->all(); } $this->assertCount(2, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(3, $orders[1]->id); $this->assertTrue($orders[0]->isRelationPopulated('books')); $this->assertTrue($orders[1]->isRelationPopulated('books')); $this->assertCount(2, $orders[0]->books); $this->assertCount(1, $orders[1]->books); // joining sub relations $query = Order::find()->innerJoinWith([ 'items i' => function (ActiveQuery $q) use ($aliasMethod) { if ($aliasMethod === 'explicit') { $q->orderBy('{{i}}.id'); } elseif ($aliasMethod === 'querysyntax') { $q->orderBy('{{@item}}.id'); } elseif ($aliasMethod === 'applyAlias') { $q->orderBy($q->applyAlias('item', 'id')); } }, 'items.category c' => function (ActiveQuery $q) use ($aliasMethod) { if ($aliasMethod === 'explicit') { $q->where('{{c}}.[[id]] = 2'); } elseif ($aliasMethod === 'querysyntax') { $q->where('{{@category}}.[[id]] = 2'); } elseif ($aliasMethod === 'applyAlias') { $q->where([$q->applyAlias('category', 'id') => 2]); } }, ]); if ($aliasMethod === 'explicit') { $orders = $query->orderBy('{{i}}.id')->all(); } elseif ($aliasMethod === 'querysyntax') { $orders = $query->orderBy('{{@item}}.id')->all(); } elseif ($aliasMethod === 'applyAlias') { $orders = $query->orderBy($query->applyAlias('item', 'id'))->all(); } $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); // join with ON condition if ($aliasMethod === 'explicit' || $aliasMethod === 'querysyntax') { $relationName = 'books' . ucfirst($aliasMethod); $orders = Order::find()->joinWith(["$relationName b"])->orderBy('order.id')->all(); $this->assertCount(3, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(2, $orders[1]->id); $this->assertEquals(3, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated($relationName)); $this->assertTrue($orders[1]->isRelationPopulated($relationName)); $this->assertTrue($orders[2]->isRelationPopulated($relationName)); $this->assertCount(2, $orders[0]->$relationName); $this->assertCount(0, $orders[1]->$relationName); $this->assertCount(1, $orders[2]->$relationName); } // join with ON condition and alias in relation definition if ($aliasMethod === 'explicit' || $aliasMethod === 'querysyntax') { $relationName = 'books' . ucfirst($aliasMethod) . 'A'; $orders = Order::find()->joinWith([(string)$relationName])->orderBy('order.id')->all(); $this->assertCount(3, $orders); $this->assertEquals(1, $orders[0]->id); $this->assertEquals(2, $orders[1]->id); $this->assertEquals(3, $orders[2]->id); $this->assertTrue($orders[0]->isRelationPopulated($relationName)); $this->assertTrue($orders[1]->isRelationPopulated($relationName)); $this->assertTrue($orders[2]->isRelationPopulated($relationName)); $this->assertCount(2, $orders[0]->$relationName); $this->assertCount(0, $orders[1]->$relationName); $this->assertCount(1, $orders[2]->$relationName); } // join with count and query /** @var ActiveQuery $query */ $query = Order::find()->joinWith(['customer c']); if ($aliasMethod === 'explicit') { $count = $query->count('[[c.id]]'); } elseif ($aliasMethod === 'querysyntax') { $count = $query->count('{{@customer}}.id'); } elseif ($aliasMethod === 'applyAlias') { $count = $query->count($query->applyAlias('customer', 'id')); } $this->assertEquals(3, $count); $orders = $query->all(); $this->assertCount(3, $orders); // relational query /** @var Order $order */ $order = Order::findOne(1); $customerQuery = $order->getCustomer()->innerJoinWith(['orders o'], false); if ($aliasMethod === 'explicit') { $customer = $customerQuery->where(['o.id' => 1])->one(); } elseif ($aliasMethod === 'querysyntax') { $customer = $customerQuery->where(['{{@order}}.id' => 1])->one(); } elseif ($aliasMethod === 'applyAlias') { $customer = $customerQuery->where([$query->applyAlias('order', 'id') => 1])->one(); } $this->assertNotNull($customer); $this->assertEquals(1, $customer->id); // join with sub-relation called inside Closure $orders = Order::find()->joinWith([ 'items' => function (ActiveQuery $q) use ($aliasMethod) { $q->orderBy('item.id'); $q->joinWith(['category c']); if ($aliasMethod === 'explicit') { $q->where('{{c}}.[[id]] = 2'); } elseif ($aliasMethod === 'querysyntax') { $q->where('{{@category}}.[[id]] = 2'); } elseif ($aliasMethod === 'applyAlias') { $q->where([$q->applyAlias('category', 'id') => 2]); } }, ])->orderBy('order.id')->all(); $this->assertCount(1, $orders); $this->assertTrue($orders[0]->isRelationPopulated('items')); $this->assertEquals(2, $orders[0]->id); $this->assertCount(3, $orders[0]->items); $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category')); $this->assertEquals(2, $orders[0]->items[0]->category->id); } public function testJoinWithSameTable(): void { // join with the same table but different aliases // alias is defined in the relation definition // without eager loading $query = Order::find() ->joinWith('bookItems', false) ->joinWith('movieItems', false) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertFalse($orders[0]->isRelationPopulated('bookItems')); $this->assertFalse($orders[0]->isRelationPopulated('movieItems')); // with eager loading $query = Order::find() ->joinWith('bookItems', true) ->joinWith('movieItems', true) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('bookItems')); $this->assertTrue($orders[0]->isRelationPopulated('movieItems')); $this->assertCount(0, $orders[0]->bookItems); $this->assertCount(3, $orders[0]->movieItems); // join with the same table but different aliases // alias is defined in the call to joinWith() // without eager loading $query = Order::find() ->joinWith([ 'itemsIndexed books' => function ($q): void { $q->onCondition('[[books.category_id]] = 1'); }, ], false) ->joinWith([ 'itemsIndexed movies' => function ($q): void { $q->onCondition('[[movies.category_id]] = 2'); }, ], false) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertFalse($orders[0]->isRelationPopulated('itemsIndexed')); // with eager loading, only for one relation as it would be overwritten otherwise. $query = Order::find() ->joinWith([ 'itemsIndexed books' => function ($q): void { $q->onCondition('[[books.category_id]] = 1'); }, ], false) ->joinWith([ 'itemsIndexed movies' => function ($q): void { $q->onCondition('[[movies.category_id]] = 2'); }, ], true) ->where(['movies.name' => 'Toy Story']); $orders = $query->all(); $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('itemsIndexed')); $this->assertCount(3, $orders[0]->itemsIndexed); // with eager loading, and the other relation $query = Order::find() ->joinWith([ 'itemsIndexed books' => function ($q): void { $q->onCondition('[[books.category_id]] = 1'); }, ], true) ->joinWith([ 'itemsIndexed movies' => function ($q): void { $q->onCondition('[[movies.category_id]] = 2'); }, ], false) ->where(['[[movies.name]]' => 'Toy Story']); $orders = $query->all(); $this->assertCount(1, $orders, $query->createCommand()->rawSql . print_r($orders, true)); $this->assertEquals(2, $orders[0]->id); $this->assertTrue($orders[0]->isRelationPopulated('itemsIndexed')); $this->assertCount(0, $orders[0]->itemsIndexed); } /** * @see https://github.com/yiisoft/yii2/issues/10201 * @see https://github.com/yiisoft/yii2/issues/9047 */ public function testFindCompositeRelationWithJoin(): void { /** @var OrderItem $orderItem */ $orderItem = OrderItem::findOne([1, 1]); $orderItemNoJoin = $orderItem->orderItemCompositeNoJoin; $this->assertInstanceOf('yiiunit\data\ar\OrderItem', $orderItemNoJoin); $orderItemWithJoin = $orderItem->orderItemCompositeWithJoin; $this->assertInstanceOf('yiiunit\data\ar\OrderItem', $orderItemWithJoin); } public function testFindSimpleRelationWithJoin(): void { /** @var Order $order */ $order = Order::findOne(1); $customerNoJoin = $order->customer; $this->assertInstanceOf('yiiunit\data\ar\Customer', $customerNoJoin); $customerWithJoin = $order->customerJoinedWithProfile; $this->assertInstanceOf('yiiunit\data\ar\Customer', $customerWithJoin); $customerWithJoinIndexOrdered = $order->customerJoinedWithProfileIndexOrdered; $this->assertIsArray($customerWithJoinIndexOrdered); $this->assertArrayHasKey('user1', $customerWithJoinIndexOrdered); $this->assertInstanceOf('yiiunit\data\ar\Customer', $customerWithJoinIndexOrdered['user1']); } public static function tableNameProvider(): array { return [ ['order', 'order_item'], ['order', '{{%order_item}}'], ['{{%order}}', 'order_item'], ['{{%order}}', '{{%order_item}}'], ]; } /** * Test whether conditions are quoted correctly in conditions where joinWith is used. * * @see https://github.com/yiisoft/yii2/issues/11088 * * @dataProvider tableNameProvider * * @param string $orderTableName The order table name. * @param string $orderItemTableName The order item table name. */ public function testRelationWhereParams(string $orderTableName, string $orderItemTableName): void { Order::$tableName = $orderTableName; OrderItem::$tableName = $orderItemTableName; /** @var Order $order */ $order = Order::findOne(1); $itemsSQL = $order->getOrderitems()->createCommand()->rawSql; $expectedSQL = self::replaceQuotes('SELECT * FROM [[order_item]] WHERE [[order_id]]=1'); $this->assertEquals($expectedSQL, $itemsSQL); $order = Order::findOne(1); $itemsSQL = $order->getOrderItems()->joinWith('item')->createCommand()->rawSql; $expectedSQL = self::replaceQuotes('SELECT [[order_item]].* FROM [[order_item]] LEFT JOIN [[item]] ON [[order_item]].[[item_id]] = [[item]].[[id]] WHERE [[order_item]].[[order_id]]=1'); $this->assertEquals($expectedSQL, $itemsSQL); Order::$tableName = null; OrderItem::$tableName = null; } public function testOutdatedRelationsAreResetForNewRecords(): void { $orderItem = new OrderItem(); $orderItem->order_id = 1; $orderItem->item_id = 3; $this->assertEquals(1, $orderItem->order->id); $this->assertEquals(3, $orderItem->item->id); // Test `__set()`. $orderItem->order_id = 2; $orderItem->item_id = 1; $this->assertEquals(2, $orderItem->order->id); $this->assertEquals(1, $orderItem->item->id); // Test `setAttribute()`. $orderItem->setAttribute('order_id', 2); $orderItem->setAttribute('item_id', 2); $this->assertEquals(2, $orderItem->order->id); $this->assertEquals(2, $orderItem->item->id); } public function testOutdatedRelationsAreResetForExistingRecords(): void { $orderItem = OrderItem::findOne(1); $this->assertEquals(1, $orderItem->order->id); $this->assertEquals(1, $orderItem->item->id); // Test `__set()`. $orderItem->order_id = 2; $orderItem->item_id = 1; $this->assertEquals(2, $orderItem->order->id); $this->assertEquals(1, $orderItem->item->id); // Test `setAttribute()`. $orderItem->setAttribute('order_id', 3); $orderItem->setAttribute('item_id', 1); $this->assertEquals(3, $orderItem->order->id); $this->assertEquals(1, $orderItem->item->id); } public function testOutdatedCompositeKeyRelationsAreReset(): void { $dossier = Dossier::findOne([ 'department_id' => 1, 'employee_id' => 1, ]); $this->assertEquals('John Doe', $dossier->employee->fullName); $dossier->department_id = 2; $this->assertEquals('Ann Smith', $dossier->employee->fullName); $dossier->employee_id = 2; $this->assertEquals('Will Smith', $dossier->employee->fullName); unset($dossier->employee_id); $this->assertNull($dossier->employee); $dossier = new Dossier(); $this->assertNull($dossier->employee); $dossier->employee_id = 1; $dossier->department_id = 2; $this->assertEquals('Ann Smith', $dossier->employee->fullName); $dossier->employee_id = 2; $this->assertEquals('Will Smith', $dossier->employee->fullName); } public function testOutdatedViaTableRelationsAreReset(): void { $order = Order::findOne(1); $orderItemIds = ArrayHelper::getColumn($order->items, 'id'); sort($orderItemIds); $this->assertSame([1, 2], $orderItemIds); $order->id = 2; sort($orderItemIds); $orderItemIds = ArrayHelper::getColumn($order->items, 'id'); $this->assertSame([3, 4, 5], $orderItemIds); unset($order->id); $this->assertSame([], $order->items); $order = new Order(); $this->assertSame([], $order->items); $order->id = 3; $orderItemIds = ArrayHelper::getColumn($order->items, 'id'); $this->assertSame([2], $orderItemIds); } public function testAlias(): void { $query = Order::find(); $this->assertNull($query->from); $query = Order::find()->alias('o'); $this->assertEquals(['o' => Order::tableName()], $query->from); $query = Order::find()->alias('o')->alias('ord'); $this->assertEquals(['ord' => Order::tableName()], $query->from); $query = Order::find()->from([ 'users', 'o' => Order::tableName(), ])->alias('ord'); $this->assertEquals([ 'users', 'ord' => Order::tableName(), ], $query->from); } public function testInverseOf(): void { // eager loading: find one and all $customer = Customer::find()->with('orders2')->where(['id' => 1])->one(); $this->assertSame($customer->orders2[0]->customer2, $customer); $customers = Customer::find()->with('orders2')->where(['id' => [1, 3]])->all(); $this->assertSame($customers[0]->orders2[0]->customer2, $customers[0]); $this->assertEmpty($customers[1]->orders2); // lazy loading $customer = Customer::findOne(2); $orders = $customer->orders2; $this->assertCount(2, $orders); $this->assertSame($customer->orders2[0]->customer2, $customer); $this->assertSame($customer->orders2[1]->customer2, $customer); // ad-hoc lazy loading $customer = Customer::findOne(2); $orders = $customer->getOrders2()->all(); $this->assertCount(2, $orders); $this->assertTrue($orders[0]->isRelationPopulated('customer2'), 'inverse relation did not populate the relation'); $this->assertTrue($orders[1]->isRelationPopulated('customer2'), 'inverse relation did not populate the relation'); $this->assertSame($orders[0]->customer2, $customer); $this->assertSame($orders[1]->customer2, $customer); // the other way around $customer = Customer::find()->with('orders2')->where(['id' => 1])->asArray()->one(); $this->assertSame($customer['orders2'][0]['customer2']['id'], $customer['id']); $customers = Customer::find()->with('orders2')->where(['id' => [1, 3]])->asArray()->all(); $this->assertSame($customer['orders2'][0]['customer2']['id'], $customers[0]['id']); $this->assertEmpty($customers[1]['orders2']); $orders = Order::find()->with('customer2')->where(['id' => 1])->all(); $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]); $order = Order::find()->with('customer2')->where(['id' => 1])->one(); $this->assertSame($order->customer2->orders2, [$order]); $orders = Order::find()->with('customer2')->where(['id' => 1])->asArray()->all(); $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']); $order = Order::find()->with('customer2')->where(['id' => 1])->asArray()->one(); $this->assertSame($order['customer2']['orders2'][0]['id'], $orders[0]['id']); $orders = Order::find()->with('customer2')->where(['id' => [1, 3]])->all(); $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]); $this->assertSame($orders[1]->customer2->orders2, [$orders[1]]); $orders = Order::find()->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->all(); $this->assertSame($orders[0]->customer2->orders2, $orders); $this->assertSame($orders[1]->customer2->orders2, $orders); $orders = Order::find()->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->asArray()->all(); $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']); $this->assertSame($orders[0]['customer2']['orders2'][1]['id'], $orders[1]['id']); $this->assertSame($orders[1]['customer2']['orders2'][0]['id'], $orders[0]['id']); $this->assertSame($orders[1]['customer2']['orders2'][1]['id'], $orders[1]['id']); } public function testInverseOfDynamic(): void { $customer = Customer::findOne(1); // request the inverseOf relation without explicitly (eagerly) loading it $orders2 = $customer->getOrders2()->all(); $this->assertSame($customer, $orders2[0]->customer2); $orders2 = $customer->getOrders2()->one(); $this->assertSame($customer, $orders2->customer2); // request the inverseOf relation while also explicitly eager loading it (while possible, this is of course redundant) $orders2 = $customer->getOrders2()->with('customer2')->all(); $this->assertSame($customer, $orders2[0]->customer2); $orders2 = $customer->getOrders2()->with('customer2')->one(); $this->assertSame($customer, $orders2->customer2); // request the inverseOf relation as array $orders2 = $customer->getOrders2()->asArray()->all(); $this->assertEquals($customer['id'], $orders2[0]['customer2']['id']); $orders2 = $customer->getOrders2()->asArray()->one(); $this->assertEquals($customer['id'], $orders2['customer2']['id']); } public function testDefaultValues(): void { $model = new Type(); $model->loadDefaultValues(); $this->assertEquals(1, $model->int_col2); $this->assertEquals('something', $model->char_col2); $this->assertEquals(1.23, $model->float_col2); $this->assertEquals(33.22, $model->numeric_col); $this->assertEquals(true, $model->bool_col2); $this->assertEquals('2002-01-01 00:00:00', $model->time); $model = new Type(); $model->char_col2 = 'not something'; $model->loadDefaultValues(); $this->assertEquals('not something', $model->char_col2); $model = new Type(); $model->char_col2 = 'not something'; $model->loadDefaultValues(false); $this->assertEquals('something', $model->char_col2); // Cropped model with 2 attributes/columns $model = new CroppedType(); $model->loadDefaultValues(); $this->assertEquals(['int_col2' => 1], $model->toArray()); } public function testUnlinkAllViaTable(): void { /** @var ActiveRecordInterface $orderClass */ $orderClass = $this->getOrderClass(); /** @var ActiveRecordInterface $orderItemClass */ $orderItemClass = $this->getOrderItemClass(); /** @var ActiveRecordInterface $itemClass */ $itemClass = $this->getItemClass(); /** @var ActiveRecordInterface $orderItemsWithNullFKClass */ $orderItemsWithNullFKClass = $this->getOrderItemWithNullFKmClass(); // via table with delete /** @var Order $order */ $order = $orderClass::findOne(1); $this->assertCount(2, $order->booksViaTable); $orderItemCount = $orderItemClass::find()->count(); $this->assertEquals(5, $itemClass::find()->count()); $order->unlinkAll('booksViaTable', true); $this->afterSave(); $this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals($orderItemCount - 2, $orderItemClass::find()->count()); $this->assertCount(0, $order->booksViaTable); // via table without delete $this->assertCount(2, $order->booksWithNullFKViaTable); $orderItemCount = $orderItemsWithNullFKClass::find()->count(); $this->assertEquals(5, $itemClass::find()->count()); $order->unlinkAll('booksWithNullFKViaTable', false); $this->assertCount(0, $order->booksWithNullFKViaTable); $this->assertEquals(2, $orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count()); $this->assertEquals($orderItemCount, $orderItemsWithNullFKClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count()); } public function testCastValues() { $model = new Type(); $model->int_col = 123; $model->int_col2 = 456; $model->smallint_col = 42; $model->char_col = '1337'; $model->char_col2 = 'test'; $model->char_col3 = 'test123'; $model->float_col = 3.742; $model->float_col2 = 42.1337; $model->bool_col = true; $model->bool_col2 = false; $model->save(false); /** @var Type $model */ $model = Type::find()->one(); $this->assertSame(123, $model->int_col); $this->assertSame(456, $model->int_col2); $this->assertSame(42, $model->smallint_col); $this->assertSame('1337', trim((string) $model->char_col)); $this->assertSame('test', $model->char_col2); $this->assertSame('test123', $model->char_col3); $this->assertSame(3.742, $model->float_col); $this->assertSame(42.1337, $model->float_col2); $this->assertSame(true, $model->bool_col); $this->assertSame(false, $model->bool_col2); } public function testIssues(): void { // https://github.com/yiisoft/yii2/issues/4938 $category = Category::findOne(2); $this->assertInstanceOf(Category::class, $category); $this->assertEquals(3, $category->getItems()->count()); $this->assertEquals(1, $category->getLimitedItems()->count()); $this->assertEquals(1, $category->getLimitedItems()->distinct(true)->count()); // https://github.com/yiisoft/yii2/issues/3197 $orders = Order::find()->with('orderItems')->orderBy('id')->all(); $this->assertCount(3, $orders); $this->assertCount(2, $orders[0]->orderItems); $this->assertCount(3, $orders[1]->orderItems); $this->assertCount(1, $orders[2]->orderItems); $orders = Order::find() ->with([ 'orderItems' => function ($q): void { $q->indexBy('item_id'); }, ]) ->orderBy('id') ->all(); $this->assertCount(3, $orders); $this->assertCount(2, $orders[0]->orderItems); $this->assertCount(3, $orders[1]->orderItems); $this->assertCount(1, $orders[2]->orderItems); // https://github.com/yiisoft/yii2/issues/8149 $model = new Customer(); $model->name = 'test'; $model->email = 'test'; $model->save(false); $model->updateCounters(['status' => 1]); $this->assertEquals(1, $model->status); } public function testPopulateRecordCallWhenQueryingOnParentClass(): void { (new Cat())->save(false); (new Dog())->save(false); $animal = Animal::find()->where(['type' => Dog::class])->one(); $this->assertEquals('bark', $animal->getDoes()); $animal = Animal::find()->where(['type' => Cat::class])->one(); $this->assertEquals('meow', $animal->getDoes()); } public function testSaveEmpty(): void { $record = new NullValues(); $this->assertTrue($record->save(false)); $this->assertEquals(1, $record->id); } public function testOptimisticLock(): void { /** @var Document $record */ $record = Document::findOne(1); $record->content = 'New Content'; $record->save(false); $this->assertEquals(1, $record->version); $record = Document::findOne(1); $record->content = 'Rewrite attempt content'; $record->version = 0; $this->expectException('yii\db\StaleObjectException'); $record->save(false); } public function testPopulateWithoutPk(): void { // tests with single pk asArray $aggregation = Customer::find() ->select(['{{customer}}.[[status]]', 'SUM({{order}}.[[total]]) AS [[sumtotal]]']) ->joinWith('ordersPlain', false) ->groupBy('{{customer}}.[[status]]') ->orderBy('status') ->asArray() ->all(); $expected = [ [ 'status' => 1, 'sumtotal' => 183, ], [ 'status' => 2, 'sumtotal' => 0, ], ]; $this->assertEquals($expected, $aggregation); // tests with single pk asArray with eager loading $aggregation = Customer::find() ->select(['{{customer}}.[[status]]', 'SUM({{order}}.[[total]]) AS [[sumtotal]]']) ->joinWith('ordersPlain') ->groupBy('{{customer}}.[[status]]') ->orderBy('status') ->asArray() ->all(); $expected = [ [ 'status' => 1, 'sumtotal' => 183, 'ordersPlain' => [], ], [ 'status' => 2, 'sumtotal' => 0, 'ordersPlain' => [], ], ]; $this->assertEquals($expected, $aggregation); // tests with single pk with Models $aggregation = Customer::find() ->select(['{{customer}}.[[status]]', 'SUM({{order}}.[[total]]) AS [[sumTotal]]']) ->joinWith('ordersPlain', false) ->groupBy('{{customer}}.[[status]]') ->orderBy('status') ->all(); $this->assertCount(2, $aggregation); $this->assertContainsOnlyInstancesOf(Customer::class, $aggregation); foreach ($aggregation as $item) { if ($item->status == 1) { $this->assertEquals(183, $item->sumTotal); } elseif ($item->status == 2) { $this->assertEquals(0, $item->sumTotal); } } // tests with composite pk asArray $aggregation = OrderItem::find() ->select(['[[order_id]]', 'SUM([[subtotal]]) AS [[subtotal]]']) ->joinWith('order', false) ->groupBy('[[order_id]]') ->orderBy('[[order_id]]') ->asArray() ->all(); $expected = [ [ 'order_id' => 1, 'subtotal' => 70, ], [ 'order_id' => 2, 'subtotal' => 33, ], [ 'order_id' => 3, 'subtotal' => 40, ], ]; $this->assertEquals($expected, $aggregation); // tests with composite pk with Models $aggregation = OrderItem::find() ->select(['[[order_id]]', 'SUM([[subtotal]]) AS [[subtotal]]']) ->joinWith('order', false) ->groupBy('[[order_id]]') ->orderBy('[[order_id]]') ->all(); $this->assertCount(3, $aggregation); $this->assertContainsOnlyInstancesOf(OrderItem::class, $aggregation); foreach ($aggregation as $item) { if ($item->order_id == 1) { $this->assertEquals(70, $item->subtotal); } elseif ($item->order_id == 2) { $this->assertEquals(33, $item->subtotal); } elseif ($item->order_id == 3) { $this->assertEquals(40, $item->subtotal); } } } /** * @see https://github.com/yiisoft/yii2/issues/9006 */ public function testBit(): void { $falseBit = BitValues::findOne(1); $this->assertEquals(false, $falseBit->val); $trueBit = BitValues::findOne(2); $this->assertEquals(true, $trueBit->val); } public function testLinkWhenRelationIsIndexed2(): void { $order = Order::find() ->with('orderItems2') ->where(['id' => 1]) ->one(); $orderItem = new OrderItem([ 'order_id' => $order->id, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 10.0, ]); $order->link('orderItems2', $orderItem); $this->assertTrue(isset($order->orderItems2['3'])); } public function testLinkWhenRelationIsIndexed3(): void { $order = Order::find() ->with('orderItems3') ->where(['id' => 1]) ->one(); $orderItem = new OrderItem([ 'order_id' => $order->id, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 10.0, ]); $order->link('orderItems3', $orderItem); $this->assertTrue(isset($order->orderItems3['1_3'])); } public function testUpdateAttributes() { $order = Order::findOne(1); $newTotal = 978; $this->assertSame(1, $order->updateAttributes(['total' => $newTotal])); $this->assertEquals($newTotal, $order->total); $order = Order::findOne(1); $this->assertEquals($newTotal, $order->total); // @see https://github.com/yiisoft/yii2/issues/12143 $newOrder = new Order(); $this->assertTrue($newOrder->getIsNewRecord()); $newTotal = 200; $this->assertSame(0, $newOrder->updateAttributes(['total' => $newTotal])); $this->assertTrue($newOrder->getIsNewRecord()); $this->assertEquals($newTotal, $newOrder->total); } public function testEmulateExecution(): void { $this->assertGreaterThan(0, Customer::find()->count()); $rows = Customer::find() ->emulateExecution() ->all(); $this->assertSame([], $rows); $row = Customer::find() ->emulateExecution() ->one(); $this->assertNull($row); $exists = Customer::find() ->emulateExecution() ->exists(); $this->assertFalse($exists); $count = Customer::find() ->emulateExecution() ->count(); $this->assertSame(0, $count); $sum = Customer::find() ->emulateExecution() ->sum('id'); $this->assertSame(0, $sum); $sum = Customer::find() ->emulateExecution() ->average('id'); $this->assertSame(0, $sum); $max = Customer::find() ->emulateExecution() ->max('id'); $this->assertNull($max); $min = Customer::find() ->emulateExecution() ->min('id'); $this->assertNull($min); $scalar = Customer::find() ->select(['id']) ->emulateExecution() ->scalar(); $this->assertNull($scalar); $column = Customer::find() ->select(['id']) ->emulateExecution() ->column(); $this->assertSame([], $column); } /** * @see https://github.com/yiisoft/yii2/issues/12213 */ public function testUnlinkAllOnCondition(): void { /** @var Category $categoryClass */ $categoryClass = $this->getCategoryClass(); /** @var Item $itemClass */ $itemClass = $this->getItemClass(); // Ensure there are three items with category_id = 2 in the Items table $itemsCount = $itemClass::find()->where(['category_id' => 2])->count(); $this->assertEquals(3, $itemsCount); $categoryQuery = $categoryClass::find()->with('limitedItems')->where(['id' => 2]); // Ensure that limitedItems relation returns only one item // (category_id = 2 and id in (1,2,3)) $category = $categoryQuery->one(); $this->assertCount(1, $category->limitedItems); // Unlink all items in the limitedItems relation $category->unlinkAll('limitedItems', true); // Make sure that only one item was unlinked $itemsCount = $itemClass::find()->where(['category_id' => 2])->count(); $this->assertEquals(2, $itemsCount); // Call $categoryQuery again to ensure no items were found $this->assertCount(0, $categoryQuery->one()->limitedItems); } /** * @see https://github.com/yiisoft/yii2/issues/12213 */ public function testUnlinkAllOnConditionViaTable(): void { /** @var Order $orderClass */ $orderClass = $this->getOrderClass(); /** @var Item $itemClass */ $itemClass = $this->getItemClass(); // Ensure there are three items with category_id = 2 in the Items table $itemsCount = $itemClass::find()->where(['category_id' => 2])->count(); $this->assertEquals(3, $itemsCount); $orderQuery = $orderClass::find()->with('limitedItems')->where(['id' => 2]); // Ensure that limitedItems relation returns only one item // (category_id = 2 and id in (4, 5)) $category = $orderQuery->one(); $this->assertCount(2, $category->limitedItems); // Unlink all items in the limitedItems relation $category->unlinkAll('limitedItems', true); // Call $orderQuery again to ensure that links are removed $this->assertCount(0, $orderQuery->one()->limitedItems); // Make sure that only links were removed, the items were not removed $this->assertEquals(3, $itemClass::find()->where(['category_id' => 2])->count()); } /** * https://github.com/yiisoft/yii2/pull/13891 */ public function testIndexByAfterLoadingRelations(): void { $orderClass = $this->getOrderClass(); $orderClass::find()->with('customer')->indexBy(function (Order $order) { $this->assertTrue($order->isRelationPopulated('customer')); $this->assertNotEmpty($order->customer->id); return $order->customer->id; })->all(); $orders = $orderClass::find()->with('customer')->indexBy('customer.id')->all(); foreach ($orders as $customer_id => $order) { $this->assertEquals($customer_id, $order->customer_id); } } /** * Verify that {{}} are not going to be replaced in parameters. */ public function testNoTablenameReplacement(): void { /** @var Customer $customer */ $class = $this->getCustomerClass(); $customer = new $class(); $customer->name = 'Some {{weird}} name'; $customer->email = 'test@example.com'; $customer->address = 'Some {{%weird}} address'; $customer->insert(false); $customer->refresh(); $this->assertEquals('Some {{weird}} name', $customer->name); $this->assertEquals('Some {{%weird}} address', $customer->address); $customer->name = 'Some {{updated}} name'; $customer->address = 'Some {{%updated}} address'; $customer->update(false); $this->assertEquals('Some {{updated}} name', $customer->name); $this->assertEquals('Some {{%updated}} address', $customer->address); } /** * Ensure no ambiguous column error occurs if ActiveQuery adds a JOIN. * * @see https://github.com/yiisoft/yii2/issues/13757 */ public function testAmbiguousColumnFindOne(): void { CustomerQuery::$joinWithProfile = true; $model = Customer::findOne(1); $this->assertTrue($model->refresh()); CustomerQuery::$joinWithProfile = false; } public function testFindOneByColumnName(): void { $model = Customer::findOne(['id' => 1]); $this->assertEquals(1, $model->id); CustomerQuery::$joinWithProfile = true; $model = Customer::findOne(['customer.id' => 1]); $this->assertEquals(1, $model->id); CustomerQuery::$joinWithProfile = false; } /** * @dataProvider filterTableNamesFromAliasesProvider * * @param array|string $fromParams The table name or the alias. * @param array $expectedAliases The expected aliases. * * @throws \yii\base\InvalidConfigException */ public function testFilterTableNamesFromAliases(array|string $fromParams, array $expectedAliases): void { $query = Customer::find()->from($fromParams); $aliases = $this->invokeMethod(\Yii::createObject(Customer::class), 'filterValidAliases', [$query]); $this->assertEquals($expectedAliases, $aliases); } public static function filterTableNamesFromAliasesProvider(): array { return [ 'table name as string' => ['customer', []], 'table name as array' => [['customer'], []], 'table names' => [['customer', 'order'], []], 'table name and a table alias' => [['customer', 'ord' => 'order'], ['ord']], 'table alias' => [['csr' => 'customer'], ['csr']], 'table aliases' => [['csr' => 'customer', 'ord' => 'order'], ['csr', 'ord']], ]; } public static function legalValuesForFindByCondition(): array { return [ [Customer::class, ['id' => 1]], [Customer::class, ['customer.id' => 1]], [Customer::class, ['[[id]]' => 1]], [Customer::class, ['{{customer}}.[[id]]' => 1]], [Customer::class, ['{{%customer}}.[[id]]' => 1]], [CustomerWithAlias::class, ['id' => 1]], [CustomerWithAlias::class, ['customer.id' => 1]], [CustomerWithAlias::class, ['[[id]]' => 1]], [CustomerWithAlias::class, ['{{customer}}.[[id]]' => 1]], [CustomerWithAlias::class, ['{{%customer}}.[[id]]' => 1]], [CustomerWithAlias::class, ['csr.id' => 1]], [CustomerWithAlias::class, ['{{csr}}.[[id]]' => 1]], ]; } /** * @dataProvider legalValuesForFindByCondition * * @param string $modelClassName The model class name. * @param array $validFilter The valid filter. */ public function testLegalValuesForFindByCondition(string $modelClassName, array $validFilter): void { /** @var Query $query */ $query = $this->invokeMethod(\Yii::createObject($modelClassName), 'findByCondition', [$validFilter]); Customer::getDb()->queryBuilder->build($query); $this->assertTrue(true); } public static function illegalValuesForFindByCondition(): array { return [ [Customer::class, [['`id`=`id` and 1' => 1]]], [Customer::class, [[ 'legal' => 1, '`id`=`id` and 1' => 1, ]]], [Customer::class, [[ 'nested_illegal' => [ 'false or 1=' => 1 ] ]]], [Customer::class, [['true--' => 1]]], [CustomerWithAlias::class, [['`csr`.`id`=`csr`.`id` and 1' => 1]]], [CustomerWithAlias::class, [[ 'legal' => 1, '`csr`.`id`=`csr`.`id` and 1' => 1, ]]], [CustomerWithAlias::class, [[ 'nested_illegal' => [ 'false or 1=' => 1 ] ]]], [CustomerWithAlias::class, [['true--' => 1]]], ]; } /** * @dataProvider illegalValuesForFindByCondition * * @param string $modelClassName The model class name. * @param array $filterWithInjection The filter with injection. */ public function testValueEscapingInFindByCondition(string $modelClassName, array $filterWithInjection): void { $this->expectException(\yii\base\InvalidArgumentException::class); $this->expectExceptionMessageMatches('/^Key "(.+)?" is not a column name and can not be used as a filter$/'); /** @var Query $query */ $query = $this->invokeMethod(\Yii::createObject($modelClassName), 'findByCondition', $filterWithInjection); Customer::getDb()->queryBuilder->build($query); } /** * Ensure no ambiguous column error occurs on indexBy with JOIN. * * @see https://github.com/yiisoft/yii2/issues/13859 */ public function testAmbiguousColumnIndexBy(): void { switch ($this->driverName) { case 'pgsql': case 'sqlite': $selectExpression = "(customer.name || ' in ' || p.description) AS name"; break; case 'cubird': case 'mysql': $selectExpression = "concat(customer.name,' in ', p.description) name"; break; default: $this->markTestIncomplete('CONCAT syntax for this DBMS is not added to the test yet.'); } $result = Customer::find()->select([$selectExpression]) ->innerJoinWith('profile p') ->indexBy('id')->column(); $this->assertEquals([ 1 => 'user1 in profile customer 1', 3 => 'user3 in profile customer 3', ], $result); } /** * @see https://github.com/yiisoft/yii2/issues/5786 * * @depends testJoinWith */ public function testFindWithConstructors(): void { /** @var OrderWithConstructor[] $orders */ $orders = OrderWithConstructor::find() ->with(['customer.profile', 'orderItems']) ->orderBy('id') ->all(); $this->assertCount(3, $orders); $order = $orders[0]; $this->assertEquals(1, $order->id); $this->assertNotNull($order->customer); $this->assertInstanceOf(CustomerWithConstructor::class, $order->customer); $this->assertEquals(1, $order->customer->id); $this->assertNotNull($order->customer->profile); $this->assertInstanceOf(ProfileWithConstructor::class, $order->customer->profile); $this->assertEquals(1, $order->customer->profile->id); $this->assertNotNull($order->customerJoinedWithProfile); $customerWithProfile = $order->customerJoinedWithProfile; $this->assertInstanceOf(CustomerWithConstructor::class, $customerWithProfile); $this->assertEquals(1, $customerWithProfile->id); $this->assertNotNull($customerProfile = $customerWithProfile->profile); $this->assertInstanceOf(ProfileWithConstructor::class, $customerProfile); $this->assertEquals(1, $customerProfile->id); $this->assertCount(2, $order->orderItems); $item = $order->orderItems[0]; $this->assertInstanceOf(OrderItemWithConstructor::class, $item); $this->assertEquals(1, $item->item_id); // @see https://github.com/yiisoft/yii2/issues/15540 $orders = OrderWithConstructor::find() ->with(['customer.profile', 'orderItems']) ->orderBy('id') ->asArray(true) ->all(); $this->assertCount(3, $orders); } public function testCustomARRelation(): void { $orderItem = OrderItem::findOne(1); $this->assertInstanceOf(Order::class, $orderItem->custom); } public function testRefresh_querySetAlias_findRecord(): void { $customer = new \yiiunit\data\ar\CustomerWithAlias(); $customer->id = 1; $customer->refresh(); $this->assertEquals(1, $customer->id); } public function testResetNotSavedRelation(): void { $order = new Order(); $order->customer_id = 1; $order->total = 0; $orderItem = new OrderItem(); $order->orderItems; $order->populateRelation('orderItems', [$orderItem]); $order->save(); $this->assertEquals(1, sizeof($order->orderItems)); } public function testIssetThrowable() { $cat = new Cat(); $this->assertFalse(isset($cat->throwable)); } /** * @see https://github.com/yiisoft/yii2/issues/15482 */ public function testEagerLoadingUsingStringIdentifiers(): void { if (!in_array($this->driverName, ['mysql', 'pgsql', 'sqlite'])) { $this->markTestSkipped('This test has fixtures only for databases MySQL, PostgreSQL and SQLite.'); } $betas = Beta::find()->with('alpha')->all(); $this->assertNotEmpty($betas); $alphaIdentifiers = []; /** @var Beta[] $betas */ foreach ($betas as $beta) { $this->assertNotNull($beta->alpha); $this->assertEquals($beta->alpha_string_identifier, $beta->alpha->string_identifier); $alphaIdentifiers[] = $beta->alpha->string_identifier; } $this->assertEquals(['1', '01', '001', '001', '2', '2b', '2b', '02'], $alphaIdentifiers); } /** * @see https://github.com/yiisoft/yii2/issues/16492 */ public function testEagerLoadingWithTypeCastedCompositeIdentifier(): void { $aggregation = Order::find()->joinWith('quantityOrderItems', true)->all(); foreach ($aggregation as $item) { if ($item->id == 1) { $this->assertEquals(1, $item->quantityOrderItems[0]->order_id); } elseif ($item->id != 1) { $this->assertCount(0, $item->quantityOrderItems); } } } public static function providerForUnlinkDelete(): array { return [ 'with delete' => [true, 0], 'without delete' => [false, 1], ]; } /** * @dataProvider providerForUnlinkDelete * * @param bool $delete Whether to delete the record. * @param int $count The expected number of records. * * @see https://github.com/yiisoft/yii2/issues/17174 */ public function testUnlinkWithViaOnCondition(bool $delete, int $count): void { /** @var ActiveRecordInterface $orderClass */ $orderClass = $this->getOrderClass(); $order = $orderClass::findOne(2); $this->assertCount(1, $order->itemsFor8); $order->unlink('itemsFor8', $order->itemsFor8[0], $delete); $order = $orderClass::findOne(2); $this->assertCount(0, $order->itemsFor8); $this->assertCount(2, $order->orderItemsWithNullFK); /** @var ActiveRecordInterface $orderItemClass */ $orderItemClass = $this->getOrderItemWithNullFKmClass(); $this->assertCount(1, $orderItemClass::findAll([ 'order_id' => 2, 'item_id' => 5, ])); $this->assertCount($count, $orderItemClass::findAll([ 'order_id' => null, 'item_id' => null, ])); } public function testVirtualRelation(): void { /** @var ActiveRecordInterface $orderClass */ $orderClass = $this->getOrderClass(); $order = $orderClass::findOne(2); $order->virtualCustomerId = $order->customer_id; $this->assertNotNull($order->virtualCustomer); } public function labelTestModelProvider() { $data = []; // Model 2 and 3 are represented by objects. $model1 = new LabelTestModel1(); $model2 = new LabelTestModel2(); $model3 = new LabelTestModel3(); $model2->populateRelation('model3', $model3); $model1->populateRelation('model2', $model2); $data[] = [$model1]; // Model 2 and 3 are represented by arrays instead of objects. $model1 = new LabelTestModel1(); $model2 = ['model3' => []]; $model1->populateRelation('model2', $model2); $data[] = [$model1]; return $data; } /** * @dataProvider labelTestModelProvider * @param \yii\db\ActiveRecord $model */ public function testGetAttributeLabel($model): void { $this->assertEquals('model3.attr1 from model2', $model->getAttributeLabel('model2.model3.attr1')); $this->assertEquals('attr2 from model3', $model->getAttributeLabel('model2.model3.attr2')); $this->assertEquals('model3.attr3 from model2', $model->getAttributeLabel('model2.model3.attr3')); $attr = 'model2.doesNotExist.attr1'; $this->assertEquals($model->generateAttributeLabel($attr), $model->getAttributeLabel($attr)); } public function testLoadRelations(): void { // Test eager loading relations for multiple primary models using loadRelationsFor(). /** @var Customer[] $customers */ $customers = Customer::find()->all(); Customer::loadRelationsFor($customers, ['orders.items']); foreach ($customers as $customer) { $this->assertTrue($customer->isRelationPopulated('orders')); foreach ($customer->orders as $order) { $this->assertTrue($order->isRelationPopulated('items')); } } // Test eager loading relations as arrays. /** @var array $customers */ $customers = Customer::find()->asArray(true)->all(); Customer::loadRelationsFor($customers, ['orders.items' => function ($query): void { $query->asArray(false); }], true); foreach ($customers as $customer) { $this->assertTrue(isset($customer['orders'])); $this->assertTrue(is_array($customer['orders'])); foreach ($customer['orders'] as $order) { $this->assertTrue(is_array($order)); $this->assertTrue(isset($order['items'])); $this->assertTrue(is_array($order['items'])); foreach ($order['items'] as $item) { $this->assertFalse(is_array($item)); } } } // Test eager loading relations for a single primary model using loadRelations(). /** @var Customer $customer */ $customer = Customer::find()->where(['id' => 1])->one(); $customer->loadRelations('orders.items'); $this->assertTrue($customer->isRelationPopulated('orders')); foreach ($customer->orders as $order) { $this->assertTrue($order->isRelationPopulated('items')); } // Test eager loading previously loaded relation (relation value should be replaced with a new value loaded from database). /** @var Customer $customer */ $customer = Customer::find()->where(['id' => 2])->with(['orders' => function ($query): void { $query->orderBy(['id' => SORT_ASC]); }])->one(); $this->assertTrue($customer->orders[0]->id < $customer->orders[1]->id, 'Related models should be sorted by ID in ascending order.'); $customer->loadRelations(['orders' => function ($query): void { $query->orderBy(['id' => SORT_DESC]); }]); $this->assertTrue($customer->orders[0]->id > $customer->orders[1]->id, 'Related models should be sorted by ID in descending order.'); } } class LabelTestModel1 extends \yii\db\ActiveRecord { public function attributes() { return []; } public function getModel2() { return $this->hasOne(LabelTestModel2::class, []); } } class LabelTestModel2 extends \yii\db\ActiveRecord { public function attributes() { return []; } public function getModel3() { return $this->hasOne(LabelTestModel3::class, []); } public function attributeLabels() { return [ 'model3.attr1' => 'model3.attr1 from model2', // Override label defined in model3. 'model3.attr3' => 'model3.attr3 from model2', // Define label not defined in model3. ]; } } class LabelTestModel3 extends \yii\db\ActiveRecord { public function attributes() { return ['attr1', 'attr2', 'attr3']; } public function attributeLabels() { return [ 'attr1' => 'attr1 from model3', 'attr2' => 'attr2 from model3', ]; } }