Passed
Push — master ( c85fa2...56cc67 )
by Alexander
06:57
created

ActiveRecordTest::testInverseOf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 65
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 45
c 1
b 0
f 1
dl 0
loc 65
rs 9.2
cc 1
nc 1
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ActiveRecord\Tests;
6
7
use Yiisoft\Arrays\ArrayHelper;
8
use Yiisoft\ActiveRecord\ActiveQuery;
9
use Yiisoft\ActiveRecord\ActiveRecordInterface;
10
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Animal;
11
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Beta;
12
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\BitValues;
13
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Cat;
14
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Category;
15
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer;
16
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerQuery;
17
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerWithAlias;
18
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerWithConstructor;
19
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Document;
20
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Dog;
21
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Dossier;
22
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Item;
23
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\NullValues;
24
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Order;
25
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItem;
26
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Profile;
27
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\ProfileWithConstructor;
28
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItemWithConstructor;
29
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderWithConstructor;
30
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItemWithNullFK;
31
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Type;
32
use Yiisoft\Db\Exception\Exception;
33
use Yiisoft\Db\Exception\InvalidConfigException;
34
use Yiisoft\Db\Exception\NotSupportedException;
35
use Yiisoft\Db\Query\Query;
36
use Yiisoft\Db\Exception\InvalidArgumentException;
37
use Yiisoft\Db\Exception\StaleObjectException;
38
39
abstract class ActiveRecordTest extends TestCase
40
{
41
    use ActiveRecordTestTrait;
42
43
    public function testCustomColumns(): void
44
    {
45
        $this->loadFixture(Customer::getConnection());
46
47
        /** find custom column */
48
        if ($this->driverName === 'oci') {
49
            $customer = Customer::find()->select(['{{customer}}.*', '([[status]]*2) AS [[status2]]'])
50
                ->where(['name' => 'user3'])->one();
51
        } else {
52
            $customer = Customer::find()->select(['*', '([[status]]*2) AS [[status2]]'])
53
                ->where(['name' => 'user3'])->one();
54
        }
55
56
        $this->assertEquals(3, $customer->id);
57
        $this->assertEquals(4, $customer->status2);
58
    }
59
60
    public function testCallFind(): void
61
    {
62
        /** find count, sum, average, min, max, scalar */
63
        $this->assertEquals(3, Customer::find()->count());
64
        $this->assertEquals(2, Customer::find()->where('[[id]]=1 OR [[id]]=2')->count());
65
        $this->assertEquals(6, Customer::find()->sum('[[id]]'));
66
        $this->assertEquals(2, Customer::find()->average('[[id]]'));
67
        $this->assertEquals(1, Customer::find()->min('[[id]]'));
68
        $this->assertEquals(3, Customer::find()->max('[[id]]'));
69
        $this->assertEquals(3, Customer::find()->select('COUNT(*)')->scalar());
70
    }
71
72
    public function testFindScalar(): void
73
    {
74
        /** query scalar */
75
        $customerName = Customer::find()->where(['[[id]]' => 2])->select('[[name]]')->scalar();
76
77
        $this->assertEquals('user2', $customerName);
78
    }
79
80
    public function testFindExists(): void
81
    {
82
        $this->assertTrue(Customer::find()->where(['[[id]]' => 2])->exists());
83
        $this->assertTrue(Customer::find()->where(['[[id]]' => 2])->select('[[name]]')->exists());
84
85
        $this->assertFalse(Customer::find()->where(['[[id]]' => 42])->exists());
86
        $this->assertFalse(Customer::find()->where(['[[id]]' => 42])->select('[[name]]')->exists());
87
    }
88
89
    public function testFindColumn(): void
90
    {
91
        $this->assertEquals(
92
            ['user1', 'user2', 'user3'],
93
            Customer::find()->select('[[name]]')->column()
94
        );
95
96
        $this->assertEquals(
97
            ['user3', 'user2', 'user1'],
98
            Customer::find()->orderBy(['[[name]]' => SORT_DESC])->select('[[name]]')->column()
99
        );
100
    }
101
102
    public function testFindBySql(): void
103
    {
104
        /** find one */
105
        $customer = Customer::findBySql('SELECT * FROM {{customer}} ORDER BY [[id]] DESC')->one();
106
        $this->assertInstanceOf(Customer::class, $customer);
107
        $this->assertEquals('user3', $customer->name);
108
109
        /** find all */
110
        $customers = Customer::findBySql('SELECT * FROM {{customer}}')->all();
111
        $this->assertCount(3, $customers);
112
113
        /** find with parameter binding */
114
        $customer = Customer::findBySql('SELECT * FROM {{customer}} WHERE [[id]]=:id', [':id' => 2])->one();
115
        $this->assertInstanceOf(Customer::class, $customer);
116
        $this->assertEquals('user2', $customer->name);
117
    }
118
119
    /**
120
     * {@see https://github.com/yiisoft/yii2/issues/8593}
121
     */
122
    public function testCountWithFindBySql(): void
123
    {
124
        $query = Customer::findBySql('SELECT * FROM {{customer}}');
125
        $this->assertEquals(3, $query->count());
126
127
        $query = Customer::findBySql('SELECT * FROM {{customer}} WHERE  [[id]]=:id', [':id' => 2]);
128
        $this->assertEquals(1, $query->count());
129
    }
130
131
    public function testFindLazyViaTable(): void
132
    {
133
        $order = Order::findOne(2);
134
135
        $this->assertCount(0, $order->books);
136
        $this->assertEquals(2, $order->id);
137
138
        $order = Order::find()->where(['id' => 1])->asArray()->one();
139
        $this->assertIsArray($order);
140
    }
141
142
    public function testFindEagerViaTable(): void
143
    {
144
        $orders = Order::find()->with('books')->orderBy('id')->all();
145
        $this->assertCount(3, $orders);
146
147
        $order = $orders[0];
148
        $this->assertCount(2, $order->books);
149
        $this->assertEquals(1, $order->id);
150
        $this->assertEquals(1, $order->books[0]->id);
151
        $this->assertEquals(2, $order->books[1]->id);
152
153
        $order = $orders[1];
154
        $this->assertCount(0, $order->books);
155
        $this->assertEquals(2, $order->id);
156
157
        $order = $orders[2];
158
        $this->assertCount(1, $order->books);
159
        $this->assertEquals(3, $order->id);
160
        $this->assertEquals(2, $order->books[0]->id);
161
162
        /** https://github.com/yiisoft/yii2/issues/1402 */
163
        $orders = Order::find()->with('books')->orderBy('id')->asArray()->all();
164
        $this->assertCount(3, $orders);
165
        $this->assertIsArray($orders[0]['orderItems'][0]);
166
167
        $order = $orders[0];
168
        $this->assertCount(2, $order['books']);
169
        $this->assertEquals(1, $order['id']);
170
        $this->assertEquals(1, $order['books'][0]['id']);
171
        $this->assertEquals(2, $order['books'][1]['id']);
172
        $this->assertIsArray($order);
173
    }
174
175
    public function testDeeplyNestedTableRelation(): void
176
    {
177
        /** @var $customer Customer */
178
        $customer = Customer::findOne(1);
179
        $this->assertNotNull($customer);
180
181
        $items = $customer->orderItems;
182
183
        $this->assertCount(2, $items);
184
        $this->assertEquals(1, $items[0]->id);
185
        $this->assertEquals(2, $items[1]->id);
186
        $this->assertInstanceOf(Item::class, $items[0]);
187
        $this->assertInstanceOf(Item::class, $items[1]);
188
    }
189
190
    /**
191
     * {@see https://github.com/yiisoft/yii2/issues/5341}
192
     *
193
     * Issue: Plan 1 -- * Account * -- * User
194
     * Our Tests: Category 1 -- * Item * -- * Order
195
     */
196
    public function testDeeplyNestedTableRelation2(): void
197
    {
198
        /** @var $category Category */
199
        $category = Category::findOne(1);
200
        $this->assertNotNull($category);
201
202
        $orders = $category->orders;
203
        $this->assertCount(2, $orders);
204
        $this->assertInstanceOf(Order::class, $orders[0]);
205
        $this->assertInstanceOf(Order::class, $orders[1]);
206
207
        $ids = [$orders[0]->id, $orders[1]->id];
208
        sort($ids);
209
        $this->assertEquals([1, 3], $ids);
210
211
        $category = Category::findOne(2);
212
        $this->assertNotNull($category);
213
214
        $orders = $category->orders;
215
        $this->assertCount(1, $orders);
216
        $this->assertEquals(2, $orders[0]->id);
217
        $this->assertInstanceOf(Order::class, $orders[0]);
218
    }
219
220
    public function testStoreNull(): void
221
    {
222
        $record = new NullValues();
223
224
        $this->assertNull($record->var1);
225
        $this->assertNull($record->var2);
226
        $this->assertNull($record->var3);
227
        $this->assertNull($record->stringcol);
228
229
        $record->var1 = 123;
230
        $record->var2 = 456;
231
        $record->var3 = 789;
232
        $record->stringcol = 'hello!';
233
234
        $record->save();
235
        $this->assertTrue($record->refresh());
236
237
        $this->assertEquals(123, $record->var1);
238
        $this->assertEquals(456, $record->var2);
239
        $this->assertEquals(789, $record->var3);
240
        $this->assertEquals('hello!', $record->stringcol);
241
242
        $record->var1 = null;
243
        $record->var2 = null;
244
        $record->var3 = null;
245
        $record->stringcol = null;
246
247
        $record->save();
248
        $this->assertTrue($record->refresh());
249
250
        $this->assertNull($record->var1);
251
        $this->assertNull($record->var2);
252
        $this->assertNull($record->var3);
253
254
        $this->assertNull($record->stringcol);
255
256
        $record->var1 = 0;
257
        $record->var2 = 0;
258
        $record->var3 = 0;
259
        $record->stringcol = '';
260
261
        $record->save();
262
        $this->assertTrue($record->refresh());
263
264
        $this->assertEquals(0, $record->var1);
265
        $this->assertEquals(0, $record->var2);
266
        $this->assertEquals(0, $record->var3);
267
        $this->assertEquals('', $record->stringcol);
268
    }
269
270
    public function testStoreEmpty(): void
271
    {
272
        $record = new NullValues();
273
274
        /* this is to simulate empty html form submission */
275
        $record->var1 = '';
276
        $record->var2 = '';
277
        $record->var3 = '';
278
        $record->stringcol = '';
279
        $record->save();
280
281
        $this->assertTrue($record->refresh());
282
283
        /** {@see https://github.com/yiisoft/yii2/commit/34945b0b69011bc7cab684c7f7095d837892a0d4#commitcomment-4458225} */
284
        $this->assertSame($record->var1, $record->var2);
285
        $this->assertSame($record->var2, $record->var3);
286
    }
287
288
    public function testIsPrimaryKey(): void
289
    {
290
        $this->assertTrue(Customer::isPrimaryKey(['id']));
291
        $this->assertFalse(Customer::isPrimaryKey([]));
292
        $this->assertFalse(Customer::isPrimaryKey(['id', 'name']));
293
        $this->assertFalse(Customer::isPrimaryKey(['name']));
294
        $this->assertFalse(Customer::isPrimaryKey(['name', 'email']));
295
296
        $this->assertTrue(OrderItem::isPrimaryKey(['order_id', 'item_id']));
297
        $this->assertFalse(OrderItem::isPrimaryKey([]));
298
        $this->assertFalse(OrderItem::isPrimaryKey(['order_id']));
299
        $this->assertFalse(OrderItem::isPrimaryKey(['item_id']));
300
        $this->assertFalse(OrderItem::isPrimaryKey(['quantity']));
301
        $this->assertFalse(OrderItem::isPrimaryKey(['quantity', 'subtotal']));
302
        $this->assertFalse(OrderItem::isPrimaryKey(['order_id', 'item_id', 'quantity']));
303
    }
304
305
    public function testJoinWith(): void
306
    {
307
        /** left join and eager loading */
308
        $orders = Order::find()->joinWith('customer')->orderBy('customer.id DESC, order.id')->all();
309
        $this->assertCount(3, $orders);
310
        $this->assertEquals(2, $orders[0]->id);
311
        $this->assertEquals(3, $orders[1]->id);
312
        $this->assertEquals(1, $orders[2]->id);
313
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
314
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
315
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
316
317
        /** inner join filtering and eager loading */
318
        $orders = Order::find()->innerJoinWith([
319
            'customer' => function ($query) {
320
                $query->where('{{customer}}.[[id]]=2');
321
            },
322
        ])->orderBy('order.id')->all();
323
        $this->assertCount(2, $orders);
324
        $this->assertEquals(2, $orders[0]->id);
325
        $this->assertEquals(3, $orders[1]->id);
326
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
327
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
328
329
        /** inner join filtering, eager loading, conditions on both primary and relation */
330
        $orders = Order::find()->innerJoinWith([
331
            'customer' => function ($query) {
332
                $query->where(['customer.id' => 2]);
333
            },
334
        ])->where(['order.id' => [1, 2]])->orderBy('order.id')->all();
335
        $this->assertCount(1, $orders);
336
        $this->assertEquals(2, $orders[0]->id);
337
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
338
339
        /** inner join filtering without eager loading */
340
        $orders = Order::find()->innerJoinWith([
341
            'customer' => static function ($query) {
342
                $query->where('{{customer}}.[[id]]=2');
343
            },
344
        ], false)->orderBy('order.id')->all();
345
        $this->assertCount(2, $orders);
346
        $this->assertEquals(2, $orders[0]->id);
347
        $this->assertEquals(3, $orders[1]->id);
348
        $this->assertFalse($orders[0]->isRelationPopulated('customer'));
349
        $this->assertFalse($orders[1]->isRelationPopulated('customer'));
350
351
        /** inner join filtering without eager loading, conditions on both primary and relation */
352
        $orders = Order::find()->innerJoinWith([
353
            'customer' => static function ($query) {
354
                $query->where(['customer.id' => 2]);
355
            },
356
        ], false)->where(['order.id' => [1, 2]])->orderBy('order.id')->all();
357
        $this->assertCount(1, $orders);
358
        $this->assertEquals(2, $orders[0]->id);
359
        $this->assertFalse($orders[0]->isRelationPopulated('customer'));
360
361
        /** join with via-relation */
362
        $orders = Order::find()->innerJoinWith('books')->orderBy('order.id')->all();
363
        $this->assertCount(2, $orders);
364
        $this->assertCount(2, $orders[0]->books);
365
        $this->assertCount(1, $orders[1]->books);
366
        $this->assertEquals(1, $orders[0]->id);
367
        $this->assertEquals(3, $orders[1]->id);
368
        $this->assertTrue($orders[0]->isRelationPopulated('books'));
369
        $this->assertTrue($orders[1]->isRelationPopulated('books'));
370
371
        /** join with sub-relation */
372
        $orders = Order::find()->innerJoinWith([
373
            'items' => function ($q) {
374
                $q->orderBy('item.id');
375
            },
376
            'items.category' => function ($q) {
377
                $q->where('{{category}}.[[id]] = 2');
378
            },
379
        ])->orderBy('order.id')->all();
380
        $this->assertCount(1, $orders);
381
        $this->assertCount(3, $orders[0]->items);
382
        $this->assertEquals(2, $orders[0]->id);
383
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
384
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
385
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
386
387
        /** join with table alias */
388
        $orders = Order::find()->joinWith([
389
            'customer' => function ($q) {
390
                $q->from('customer c');
391
            },
392
        ])->orderBy('c.id DESC, order.id')->all();
393
        $this->assertCount(3, $orders);
394
        $this->assertEquals(2, $orders[0]->id);
395
        $this->assertEquals(3, $orders[1]->id);
396
        $this->assertEquals(1, $orders[2]->id);
397
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
398
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
399
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
400
401
        /** join with table alias */
402
        $orders = Order::find()->joinWith('customer as c')->orderBy('c.id DESC, order.id')->all();
403
        $this->assertCount(3, $orders);
404
        $this->assertEquals(2, $orders[0]->id);
405
        $this->assertEquals(3, $orders[1]->id);
406
        $this->assertEquals(1, $orders[2]->id);
407
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
408
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
409
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
410
411
        /** join with table alias sub-relation */
412
        $orders = Order::find()->innerJoinWith([
413
            'items as t' => function ($q) {
414
                $q->orderBy('t.id');
415
            },
416
            'items.category as c' => function ($q) {
417
                $q->where('{{c}}.[[id]] = 2');
418
            },
419
        ])->orderBy('order.id')->all();
420
421
        $this->assertCount(1, $orders);
422
        $this->assertCount(3, $orders[0]->items);
423
        $this->assertEquals(2, $orders[0]->id);
424
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
425
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
426
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
427
428
429
        /** join with ON condition */
430
        $orders = Order::find()->joinWith('books2')->orderBy('order.id')->all();
431
        $this->assertCount(3, $orders);
432
        $this->assertCount(2, $orders[0]->books2);
433
        $this->assertCount(0, $orders[1]->books2);
434
        $this->assertCount(1, $orders[2]->books2);
435
        $this->assertEquals(1, $orders[0]->id);
436
        $this->assertEquals(2, $orders[1]->id);
437
        $this->assertEquals(3, $orders[2]->id);
438
        $this->assertTrue($orders[0]->isRelationPopulated('books2'));
439
        $this->assertTrue($orders[1]->isRelationPopulated('books2'));
440
        $this->assertTrue($orders[2]->isRelationPopulated('books2'));
441
442
        /** lazy loading with ON condition */
443
        $order = Order::findOne(1);
444
        $this->assertCount(2, $order->books2);
445
446
        $order = Order::findOne(2);
447
        $this->assertCount(0, $order->books2);
448
449
        $order = Order::findOne(3);
450
        $this->assertCount(1, $order->books2);
451
452
        /** eager loading with ON condition */
453
        $orders = Order::find()->with('books2')->all();
454
        $this->assertCount(3, $orders);
455
        $this->assertCount(2, $orders[0]->books2);
456
        $this->assertCount(0, $orders[1]->books2);
457
        $this->assertCount(1, $orders[2]->books2);
458
        $this->assertEquals(1, $orders[0]->id);
459
        $this->assertEquals(2, $orders[1]->id);
460
        $this->assertEquals(3, $orders[2]->id);
461
        $this->assertTrue($orders[0]->isRelationPopulated('books2'));
462
        $this->assertTrue($orders[1]->isRelationPopulated('books2'));
463
        $this->assertTrue($orders[2]->isRelationPopulated('books2'));
464
465
        /** join with count and query */
466
        $query = Order::find()->joinWith('customer');
467
468
        $count = $query->count();
469
        $this->assertEquals(3, $count);
470
471
        $orders = $query->all();
472
        $this->assertCount(3, $orders);
473
474
        /** {@see https://github.com/yiisoft/yii2/issues/2880} */
475
        $query = Order::findOne(1);
476
477
        $customer = $query->getCustomer()
478
            ->joinWith([
479
                'orders' => static function ($q) {
480
                    $q->orderBy([]);
481
                },
482
            ])
483
            ->one();
484
        $this->assertEquals(1, $customer->id);
485
486
        $order = Order::find()->joinWith([
487
            'items' => static function ($q) {
488
                $q->from(['items' => 'item'])
489
                    ->orderBy('items.id');
490
            },
491
        ])->orderBy('order.id')->one();
492
493
        /** join with sub-relation called inside Closure */
494
        $orders = Order::find()
495
            ->joinWith([
496
                'items' => static function ($q) {
497
                    $q->orderBy('item.id');
498
                    $q->joinWith([
499
                        'category' => static function ($q) {
500
                            $q->where('{{category}}.[[id]] = 2');
501
                        },
502
                    ]);
503
                },
504
            ])
505
            ->orderBy('order.id')
506
            ->all();
507
        $this->assertCount(1, $orders);
508
        $this->assertCount(3, $orders[0]->items);
509
        $this->assertEquals(2, $orders[0]->id);
510
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
511
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
512
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
513
    }
514
515
    public function testJoinWithAndScope(): void
516
    {
517
        /**  hasOne inner join */
518
        $customers = Customer::find()->active()->innerJoinWith('profile')->orderBy('customer.id')->all();
519
        $this->assertCount(1, $customers);
520
        $this->assertEquals(1, $customers[0]->id);
521
        $this->assertTrue($customers[0]->isRelationPopulated('profile'));
522
523
        /** hasOne outer join */
524
        $customers = Customer::find()->active()->joinWith('profile')->orderBy('customer.id')->all();
525
526
        $this->assertCount(2, $customers);
527
        $this->assertEquals(1, $customers[0]->id);
528
        $this->assertEquals(2, $customers[1]->id);
529
        $this->assertInstanceOf(Profile::class, $customers[0]->profile);
530
        $this->assertNull($customers[1]->profile);
531
        $this->assertTrue($customers[0]->isRelationPopulated('profile'));
532
        $this->assertTrue($customers[1]->isRelationPopulated('profile'));
533
534
        /** hasMany */
535
        $customers = Customer::find()->active()->joinWith([
536
            'orders' => static function ($q) {
537
                $q->orderBy('order.id');
538
            },
539
        ])->orderBy('customer.id DESC, order.id')->all();
540
        $this->assertCount(2, $customers);
541
        $this->assertEquals(2, $customers[0]->id);
542
        $this->assertEquals(1, $customers[1]->id);
543
        $this->assertTrue($customers[0]->isRelationPopulated('orders'));
544
        $this->assertTrue($customers[1]->isRelationPopulated('orders'));
545
    }
546
547
    /**
548
     * This query will do the same join twice, ensure duplicated JOIN gets removed.
549
     *
550
     * {@see https://github.com/yiisoft/yii2/pull/2650}
551
     */
552
    public function testJoinWithVia(): void
553
    {
554
        Order::getConnection()->getQueryBuilder()->setSeparator("\n");
555
556
        $rows = Order::find()->joinWith('itemsInOrder1')->joinWith([
557
            'items' => static function ($q) {
558
                $q->orderBy('item.id');
559
            },
560
        ])->all();
561
        $this->assertNotEmpty($rows);
562
    }
563
564
    public function aliasMethodProvider(): array
565
    {
566
        return [
567
            ['explicit']
568
        ];
569
    }
570
571
    /**
572
     * Tests the alias syntax for joinWith: 'alias' => 'relation'.
573
     *
574
     * @dataProvider aliasMethodProvider
575
     *
576
     * @param string $aliasMethod whether alias is specified explicitly or using the query syntax {{@tablename}}
577
     *
578
     * @throws InvalidConfigException
579
     */
580
    public function testJoinWithAlias(string $aliasMethod): void
581
    {
582
        /**
583
         * left join and eager loading
584
         * @var ActiveQuery $query
585
         */
586
        $query = Order::find()->joinWith(['customer c']);
587
588
        if ($aliasMethod === 'explicit') {
589
            $orders = $query->orderBy('c.id DESC, order.id')->all();
590
        } elseif ($aliasMethod === 'querysyntax') {
591
            $orders = $query->orderBy('{{@customer}}.id DESC, {{@order}}.id')->all();
592
        } elseif ($aliasMethod === 'applyAlias') {
593
            $orders = $query->orderBy(
594
                $query->applyAlias('customer', 'id') . ' DESC,' . $query->applyAlias('order', 'id')
595
            )->all();
596
        }
597
598
        $this->assertCount(3, $orders);
599
        $this->assertEquals(2, $orders[0]->id);
600
        $this->assertEquals(3, $orders[1]->id);
601
        $this->assertEquals(1, $orders[2]->id);
602
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
603
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
604
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
605
606
        /** inner join filtering and eager loading */
607
        $query = Order::find()->innerJoinWith(['customer c']);
608
609
        if ($aliasMethod === 'explicit') {
610
            $orders = $query->where('{{c}}.[[id]]=2')->orderBy('order.id')->all();
611
        } elseif ($aliasMethod === 'querysyntax') {
612
            $orders = $query->where('{{@customer}}.[[id]]=2')->orderBy('{{@order}}.id')->all();
613
        } elseif ($aliasMethod === 'applyAlias') {
614
            $orders = $query->where(
615
                [$query->applyAlias('customer', 'id') => 2]
616
            )->orderBy($query->applyAlias('order', 'id'))->all();
617
        }
618
619
        $this->assertCount(2, $orders);
620
        $this->assertEquals(2, $orders[0]->id);
621
        $this->assertEquals(3, $orders[1]->id);
622
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
623
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
624
625
        /** inner join filtering without eager loading */
626
        $query = Order::find()->innerJoinWith(['customer c'], false);
627
628
        if ($aliasMethod === 'explicit') {
629
            $orders = $query->where('{{c}}.[[id]]=2')->orderBy('order.id')->all();
630
        } elseif ($aliasMethod === 'querysyntax') {
631
            $orders = $query->where('{{@customer}}.[[id]]=2')->orderBy('{{@order}}.id')->all();
632
        } elseif ($aliasMethod === 'applyAlias') {
633
            $orders = $query->where(
634
                [$query->applyAlias('customer', 'id') => 2]
635
            )->orderBy($query->applyAlias('order', 'id'))->all();
636
        }
637
638
        $this->assertCount(2, $orders);
639
        $this->assertEquals(2, $orders[0]->id);
640
        $this->assertEquals(3, $orders[1]->id);
641
        $this->assertFalse($orders[0]->isRelationPopulated('customer'));
642
        $this->assertFalse($orders[1]->isRelationPopulated('customer'));
643
644
        /** join with via-relation */
645
        $query = Order::find()->innerJoinWith(['books b']);
646
647
        if ($aliasMethod === 'explicit') {
648
            $orders = $query->where(
649
                ['b.name' => 'Yii 1.1 Application Development Cookbook']
650
            )->orderBy('order.id')->all();
651
        } elseif ($aliasMethod === 'querysyntax') {
652
            $orders = $query->where(
653
                ['{{@item}}.name' => 'Yii 1.1 Application Development Cookbook']
654
            )->orderBy('{{@order}}.id')->all();
655
        } elseif ($aliasMethod === 'applyAlias') {
656
            $orders = $query->where(
657
                [$query->applyAlias('book', 'name') => 'Yii 1.1 Application Development Cookbook']
658
            )->orderBy($query->applyAlias('order', 'id'))->all();
659
        }
660
661
        $this->assertCount(2, $orders);
662
        $this->assertCount(2, $orders[0]->books);
663
        $this->assertCount(1, $orders[1]->books);
664
        $this->assertEquals(1, $orders[0]->id);
665
        $this->assertEquals(3, $orders[1]->id);
666
        $this->assertTrue($orders[0]->isRelationPopulated('books'));
667
        $this->assertTrue($orders[1]->isRelationPopulated('books'));
668
669
670
        /* joining sub relations */
671
        $query = Order::find()->innerJoinWith([
672
            'items i' => static function ($q) use ($aliasMethod) {
673
                /** @var $q ActiveQuery */
674
                if ($aliasMethod === 'explicit') {
675
                    $q->orderBy('{{i}}.id');
676
                } elseif ($aliasMethod === 'querysyntax') {
677
                    $q->orderBy('{{@item}}.id');
678
                } elseif ($aliasMethod === 'applyAlias') {
679
                    $q->orderBy($q->applyAlias('item', 'id'));
680
                }
681
            },
682
            'items.category c' => static function ($q) use ($aliasMethod) {
683
                /**  @var $q ActiveQuery */
684
                if ($aliasMethod === 'explicit') {
685
                    $q->where('{{c}}.[[id]] = 2');
686
                } elseif ($aliasMethod === 'querysyntax') {
687
                    $q->where('{{@category}}.[[id]] = 2');
688
                } elseif ($aliasMethod === 'applyAlias') {
689
                    $q->where([$q->applyAlias('category', 'id') => 2]);
690
                }
691
            },
692
        ]);
693
694
        if ($aliasMethod === 'explicit') {
695
            $orders = $query->orderBy('{{i}}.id')->all();
696
        } elseif ($aliasMethod === 'querysyntax') {
697
            $orders = $query->orderBy('{{@item}}.id')->all();
698
        } elseif ($aliasMethod === 'applyAlias') {
699
            $orders = $query->orderBy($query->applyAlias('item', 'id'))->all();
700
        }
701
702
        $this->assertCount(1, $orders);
703
        $this->assertCount(3, $orders[0]->items);
704
        $this->assertEquals(2, $orders[0]->id);
705
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
706
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
707
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
708
709
        /** join with ON condition */
710
        if ($aliasMethod === 'explicit' || $aliasMethod === 'querysyntax') {
711
            $relationName = 'books' . ucfirst($aliasMethod);
712
713
            $orders = Order::find()->joinWith(["$relationName b"])->orderBy('order.id')->all();
714
715
            $this->assertCount(3, $orders);
716
            $this->assertCount(2, $orders[0]->$relationName);
717
            $this->assertCount(0, $orders[1]->$relationName);
718
            $this->assertCount(1, $orders[2]->$relationName);
719
            $this->assertEquals(1, $orders[0]->id);
720
            $this->assertEquals(2, $orders[1]->id);
721
            $this->assertEquals(3, $orders[2]->id);
722
            $this->assertTrue($orders[0]->isRelationPopulated($relationName));
723
            $this->assertTrue($orders[1]->isRelationPopulated($relationName));
724
            $this->assertTrue($orders[2]->isRelationPopulated($relationName));
725
        }
726
727
        /** join with ON condition and alias in relation definition */
728
        if ($aliasMethod === 'explicit' || $aliasMethod === 'querysyntax') {
729
            $relationName = 'books' . ucfirst($aliasMethod) . 'A';
730
731
            $orders = Order::find()->joinWith([(string)$relationName])->orderBy('order.id')->all();
732
733
            $this->assertCount(3, $orders);
734
            $this->assertCount(2, $orders[0]->$relationName);
735
            $this->assertCount(0, $orders[1]->$relationName);
736
            $this->assertCount(1, $orders[2]->$relationName);
737
            $this->assertEquals(1, $orders[0]->id);
738
            $this->assertEquals(2, $orders[1]->id);
739
            $this->assertEquals(3, $orders[2]->id);
740
            $this->assertTrue($orders[0]->isRelationPopulated($relationName));
741
            $this->assertTrue($orders[1]->isRelationPopulated($relationName));
742
            $this->assertTrue($orders[2]->isRelationPopulated($relationName));
743
        }
744
745
        /**
746
         * join with count and query
747
         *
748
         * @var $query ActiveQuery
749
         */
750
        $query = Order::find()->joinWith(['customer c']);
751
752
        if ($aliasMethod === 'explicit') {
753
            $count = $query->count('c.id');
754
        } elseif ($aliasMethod === 'querysyntax') {
755
            $count = $query->count('{{@customer}}.id');
756
        } elseif ($aliasMethod === 'applyAlias') {
757
            $count = $query->count($query->applyAlias('customer', 'id'));
758
        }
759
760
        $this->assertEquals(3, $count);
761
762
        $orders = $query->all();
763
        $this->assertCount(3, $orders);
764
765
        /**
766
         * relational query
767
         *
768
         * @var $order Order
769
         */
770
        $order = Order::findOne(1);
771
772
        $customerQuery = $order->getCustomer()->innerJoinWith(['orders o'], false);
773
774
        if ($aliasMethod === 'explicit') {
775
            $customer = $customerQuery->where(['o.id' => 1])->one();
776
        } elseif ($aliasMethod === 'querysyntax') {
777
            $customer = $customerQuery->where(['{{@order}}.id' => 1])->one();
778
        } elseif ($aliasMethod === 'applyAlias') {
779
            $customer = $customerQuery->where([$query->applyAlias('order', 'id') => 1])->one();
780
        }
781
782
        $this->assertEquals(1, $customer->id);
783
        $this->assertNotNull($customer);
784
785
        /** join with sub-relation called inside Closure */
786
        $orders = Order::find()->joinWith([
787
            'items' => static function ($q) use ($aliasMethod) {
788
                /* @var $q ActiveQuery */
789
                $q->orderBy('item.id');
790
                $q->joinWith(['category c']);
791
792
                if ($aliasMethod === 'explicit') {
793
                    $q->where('{{c}}.[[id]] = 2');
794
                } elseif ($aliasMethod === 'querysyntax') {
795
                    $q->where('{{@category}}.[[id]] = 2');
796
                } elseif ($aliasMethod === 'applyAlias') {
797
                    $q->where([$q->applyAlias('category', 'id') => 2]);
798
                }
799
            },
800
        ])->orderBy('order.id')->all();
801
802
        $this->assertCount(1, $orders);
803
        $this->assertCount(3, $orders[0]->items);
804
        $this->assertEquals(2, $orders[0]->id);
805
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
806
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
807
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
808
    }
809
810
    public function testJoinWithSameTable(): void
811
    {
812
        /**
813
         * join with the same table but different aliases alias is defined in the relation definition without eager
814
         * loading
815
         */
816
        $query = Order::find()
817
            ->joinWith('bookItems', false)
818
            ->joinWith('movieItems', false)
819
            ->where(['movies.name' => 'Toy Story']);
820
821
        $orders = $query->all();
822
823
        $this->assertCount(
824
            1,
825
            $orders,
826
            $query->createCommand()->getRawSql() . print_r($orders, true)
827
        );
828
        $this->assertEquals(2, $orders[0]->id);
829
        $this->assertFalse($orders[0]->isRelationPopulated('bookItems'));
830
        $this->assertFalse($orders[0]->isRelationPopulated('movieItems'));
831
832
        /** with eager loading */
833
        $query = Order::find()
834
            ->joinWith('bookItems', true)
835
            ->joinWith('movieItems', true)
836
            ->where(['movies.name' => 'Toy Story']);
837
838
        $orders = $query->all();
839
840
        $this->assertCount(
841
            1,
842
            $orders,
843
            $query->createCommand()->getRawSql() . print_r($orders, true)
844
        );
845
        $this->assertCount(0, $orders[0]->bookItems);
846
        $this->assertCount(3, $orders[0]->movieItems);
847
        $this->assertEquals(2, $orders[0]->id);
848
        $this->assertTrue($orders[0]->isRelationPopulated('bookItems'));
849
        $this->assertTrue($orders[0]->isRelationPopulated('movieItems'));
850
851
852
        /**
853
         * join with the same table but different aliases alias is defined in the call to joinWith() without eager
854
         * loading
855
         */
856
        $query = Order::find()
857
            ->joinWith([
858
                'itemsIndexed books' => static function ($q) {
859
                    $q->onCondition('books.category_id = 1');
860
                },
861
            ], false)
862
            ->joinWith([
863
                'itemsIndexed movies' => static function ($q) {
864
                    $q->onCondition('movies.category_id = 2');
865
                },
866
            ], false)
867
            ->where(['movies.name' => 'Toy Story']);
868
869
        $orders = $query->all();
870
        $this->assertCount(
871
            1,
872
            $orders,
873
            $query->createCommand()->getRawSql() . print_r($orders, true)
874
        );
875
        $this->assertEquals(2, $orders[0]->id);
876
        $this->assertFalse($orders[0]->isRelationPopulated('itemsIndexed'));
877
878
        /** with eager loading, only for one relation as it would be overwritten otherwise. */
879
        $query = Order::find()
880
            ->joinWith([
881
                'itemsIndexed books' => static function ($q) {
882
                    $q->onCondition('books.category_id = 1');
883
                },
884
            ], false)
885
            ->joinWith([
886
                'itemsIndexed movies' => static function ($q) {
887
                    $q->onCondition('movies.category_id = 2');
888
                },
889
            ], true)
890
            ->where(['movies.name' => 'Toy Story']);
891
892
        $orders = $query->all();
893
        $this->assertCount(1, $orders, $query->createCommand()->getRawSql() . print_r($orders, true));
894
        $this->assertCount(3, $orders[0]->itemsIndexed);
895
        $this->assertEquals(2, $orders[0]->id);
896
        $this->assertTrue($orders[0]->isRelationPopulated('itemsIndexed'));
897
898
        /** with eager loading, and the other relation */
899
        $query = Order::find()
900
            ->joinWith([
901
                'itemsIndexed books' => static function ($q) {
902
                    $q->onCondition('books.category_id = 1');
903
                },
904
            ], true)
905
            ->joinWith([
906
                'itemsIndexed movies' => static function ($q) {
907
                    $q->onCondition('movies.category_id = 2');
908
                },
909
            ], false)
910
            ->where(['movies.name' => 'Toy Story']);
911
912
        $orders = $query->all();
913
        $this->assertCount(1, $orders, $query->createCommand()->getRawSql() . print_r($orders, true));
914
        $this->assertCount(0, $orders[0]->itemsIndexed);
915
        $this->assertEquals(2, $orders[0]->id);
916
        $this->assertTrue($orders[0]->isRelationPopulated('itemsIndexed'));
917
    }
918
919
    /**
920
     * {@see https://github.com/yiisoft/yii2/issues/10201}
921
     * {@see https://github.com/yiisoft/yii2/issues/9047}
922
     */
923
    public function testFindCompositeRelationWithJoin(): void
924
    {
925
        /** @var $orderItem OrderItem */
926
        $orderItem = OrderItem::findOne([1, 1]);
927
928
        $orderItemNoJoin = $orderItem->orderItemCompositeNoJoin;
929
930
        $this->assertInstanceOf(OrderItem::class, $orderItemNoJoin);
931
932
        $orderItemWithJoin = $orderItem->orderItemCompositeWithJoin;
933
        $this->assertInstanceOf(OrderItem::class, $orderItemWithJoin);
934
    }
935
936
    public function testFindSimpleRelationWithJoin(): void
937
    {
938
        /** @var $order Order */
939
        $order = Order::findOne(1);
940
941
        $customerNoJoin = $order->customer;
942
        $this->assertInstanceOf(Customer::class, $customerNoJoin);
943
944
        $customerWithJoin = $order->customerJoinedWithProfile;
945
        $this->assertInstanceOf(Customer::class, $customerWithJoin);
946
947
        $customerWithJoinIndexOrdered = $order->customerJoinedWithProfileIndexOrdered;
948
        $this->assertArrayHasKey('user1', $customerWithJoinIndexOrdered);
949
        $this->assertInstanceOf(Customer::class, $customerWithJoinIndexOrdered['user1']);
950
        $this->assertIsArray($customerWithJoinIndexOrdered);
951
    }
952
953
    public function tableNameProvider(): array
954
    {
955
        return [
956
            ['order', 'order_item'],
957
            ['order', '{{%order_item}}'],
958
            ['{{%order}}', 'order_item'],
959
            ['{{%order}}', '{{%order_item}}'],
960
        ];
961
    }
962
963
    /**
964
     * Test whether conditions are quoted correctly in conditions where joinWith is used.
965
     *
966
     * {@see https://github.com/yiisoft/yii2/issues/11088}
967
     *
968
     * @dataProvider tableNameProvider
969
     *
970
     * @param string $orderTableName
971
     * @param string $orderItemTableName
972
     *
973
     * @throws InvalidConfigException
974
     */
975
    public function testRelationWhereParams(string $orderTableName, string $orderItemTableName): void
976
    {
977
        /** @var $order Order */
978
        $order = Order::findOne(1);
979
        $itemsSQL = $order->getOrderitems()->createCommand()->getRawSql();
980
        $expectedSQL = $this->replaceQuotes('SELECT * FROM [[order_item]] WHERE [[order_id]]=1');
981
        $this->assertEquals($expectedSQL, $itemsSQL);
982
983
        $order = Order::findOne(1);
984
        $itemsSQL = $order->getOrderItems()->joinWith('item')->createCommand()->getRawSql();
985
        $expectedSQL = $this->replaceQuotes(
986
            'SELECT [[order_item]].* FROM [[order_item]] LEFT JOIN [[item]] ON [[order_item]].[[item_id]] = [[item]].[[id]] WHERE [[order_item]].[[order_id]]=1'
987
        );
988
        $this->assertEquals($expectedSQL, $itemsSQL);
989
990
        Order::$tableName = null;
991
        OrderItem::$tableName = null;
992
    }
993
994
    public function testOutdatedRelationsAreResetForNewRecords(): void
995
    {
996
        $orderItem = new OrderItem();
997
998
        $orderItem->order_id = 1;
999
        $orderItem->item_id = 3;
1000
        $this->assertEquals(1, $orderItem->order->id);
1001
        $this->assertEquals(3, $orderItem->item->id);
1002
1003
        /** test `__set()`. */
1004
        $orderItem->order_id = 2;
1005
        $orderItem->item_id = 1;
1006
        $this->assertEquals(2, $orderItem->order->id);
1007
        $this->assertEquals(1, $orderItem->item->id);
1008
1009
        /** test `setAttribute()`. */
1010
        $orderItem->setAttribute('order_id', 2);
1011
        $orderItem->setAttribute('item_id', 2);
1012
        $this->assertEquals(2, $orderItem->order->id);
1013
        $this->assertEquals(2, $orderItem->item->id);
1014
    }
1015
1016
    public function testOutdatedRelationsAreResetForExistingRecords(): void
1017
    {
1018
        $orderItem = OrderItem::findOne(1);
1019
1020
        $this->assertEquals(1, $orderItem->order->id);
1021
        $this->assertEquals(1, $orderItem->item->id);
1022
1023
        /** test `__set()`. */
1024
        $orderItem->order_id = 2;
1025
        $orderItem->item_id = 1;
1026
1027
        $this->assertEquals(2, $orderItem->order->id);
1028
        $this->assertEquals(1, $orderItem->item->id);
1029
1030
        /** Test `setAttribute()`. */
1031
        $orderItem->setAttribute('order_id', 3);
1032
        $orderItem->setAttribute('item_id', 1);
1033
1034
        $this->assertEquals(3, $orderItem->order->id);
1035
        $this->assertEquals(1, $orderItem->item->id);
1036
    }
1037
1038
    public function testOutdatedCompositeKeyRelationsAreReset(): void
1039
    {
1040
        $dossier = Dossier::findOne([
1041
            'department_id' => 1,
1042
            'employee_id' => 1,
1043
        ]);
1044
1045
        $this->assertEquals('John Doe', $dossier->employee->fullName);
1046
1047
        $dossier->department_id = 2;
1048
        $this->assertEquals('Ann Smith', $dossier->employee->fullName);
1049
1050
        $dossier->employee_id = 2;
1051
        $this->assertEquals('Will Smith', $dossier->employee->fullName);
1052
1053
        unset($dossier->employee_id);
1054
        $this->assertNull($dossier->employee);
1055
1056
        $dossier = new Dossier();
1057
        $this->assertNull($dossier->employee);
1058
1059
        $dossier->employee_id = 1;
1060
        $dossier->department_id = 2;
1061
        $this->assertEquals('Ann Smith', $dossier->employee->fullName);
1062
1063
        $dossier->employee_id = 2;
1064
        $this->assertEquals('Will Smith', $dossier->employee->fullName);
1065
    }
1066
1067
    public function testOutdatedViaTableRelationsAreReset(): void
1068
    {
1069
        $order = Order::findOne(1);
1070
        $orderItemIds = ArrayHelper::getColumn($order->items, 'id');
1071
        sort($orderItemIds);
1072
        $this->assertSame([1, 2], $orderItemIds);
1073
1074
        $order->id = 2;
1075
        sort($orderItemIds);
1076
        $orderItemIds = ArrayHelper::getColumn($order->items, 'id');
1077
        $this->assertSame([3, 4, 5], $orderItemIds);
1078
1079
        unset($order->id);
1080
        $this->assertSame([], $order->items);
1081
1082
        $order = new Order();
1083
        $this->assertSame([], $order->items);
1084
1085
        $order->id = 3;
1086
        $orderItemIds = ArrayHelper::getColumn($order->items, 'id');
1087
        $this->assertSame([2], $orderItemIds);
1088
    }
1089
1090
    public function testAlias(): void
1091
    {
1092
        $query = Order::find();
1093
        $this->assertNull($query->getFrom());
1094
1095
        $query = Order::find()->alias('o');
1096
        $this->assertEquals(['o' => Order::tableName()], $query->getFrom());
1097
1098
        $query = Order::find()->alias('o')->alias('ord');
1099
        $this->assertEquals(['ord' => Order::tableName()], $query->getFrom());
1100
1101
        $query = Order::find()->from([
1102
            'users',
1103
            'o' => Order::tableName(),
1104
        ])->alias('ord');
1105
        $this->assertEquals([
1106
            'users',
1107
            'ord' => Order::tableName(),
1108
        ], $query->getFrom());
1109
    }
1110
1111
    public function testInverseOf(): void
1112
    {
1113
        /** eager loading: find one and all */
1114
        $customer = Customer::find()->with('orders2')->where(['id' => 1])->one();
1115
        $this->assertSame($customer->orders2[0]->customer2, $customer);
1116
1117
        $customers = Customer::find()->with('orders2')->where(['id' => [1, 3]])->all();
1118
        $this->assertEmpty($customers[1]->orders2);
1119
        $this->assertSame($customers[0]->orders2[0]->customer2, $customers[0]);
1120
1121
        /** lazy loading */
1122
        $customer = Customer::findOne(2);
1123
        $orders = $customer->orders2;
1124
        $this->assertCount(2, $orders);
1125
        $this->assertSame($customer->orders2[0]->customer2, $customer);
1126
        $this->assertSame($customer->orders2[1]->customer2, $customer);
1127
1128
        /** ad-hoc lazy loading */
1129
        $customer = Customer::findOne(2);
1130
        $orders = $customer->getOrders2()->all();
1131
        $this->assertCount(2, $orders);
1132
        $this->assertSame($orders[0]->customer2, $customer);
1133
        $this->assertSame($orders[1]->customer2, $customer);
1134
        $this->assertTrue(
1135
            $orders[0]->isRelationPopulated('customer2'),
1136
            'inverse relation did not populate the relation'
1137
        );
1138
        $this->assertTrue(
1139
            $orders[1]->isRelationPopulated('customer2'),
1140
            'inverse relation did not populate the relation'
1141
        );
1142
1143
        /** the other way around */
1144
        $customer = Customer::find()->with('orders2')->where(['id' => 1])->asArray()->one();
1145
        $this->assertSame($customer['orders2'][0]['customer2']['id'], $customer['id']);
1146
1147
        $customers = Customer::find()->with('orders2')->where(['id' => [1, 3]])->asArray()->all();
1148
        $this->assertSame($customer['orders2'][0]['customer2']['id'], $customers[0]['id']);
1149
        $this->assertEmpty($customers[1]['orders2']);
1150
1151
        $orders = Order::find()->with('customer2')->where(['id' => 1])->all();
1152
        $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]);
1153
1154
        $order = Order::find()->with('customer2')->where(['id' => 1])->one();
1155
        $this->assertSame($order->customer2->orders2, [$order]);
1156
1157
        $orders = Order::find()->with('customer2')->where(['id' => 1])->asArray()->all();
1158
        $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1159
1160
        $order = Order::find()->with('customer2')->where(['id' => 1])->asArray()->one();
1161
        $this->assertSame($order['customer2']['orders2'][0]['id'], $orders[0]['id']);
1162
1163
        $orders = Order::find()->with('customer2')->where(['id' => [1, 3]])->all();
1164
        $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]);
1165
        $this->assertSame($orders[1]->customer2->orders2, [$orders[1]]);
1166
1167
        $orders = Order::find()->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->all();
1168
        $this->assertSame($orders[0]->customer2->orders2, $orders);
1169
        $this->assertSame($orders[1]->customer2->orders2, $orders);
1170
1171
        $orders = Order::find()->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->asArray()->all();
1172
        $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1173
        $this->assertSame($orders[0]['customer2']['orders2'][1]['id'], $orders[1]['id']);
1174
        $this->assertSame($orders[1]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1175
        $this->assertSame($orders[1]['customer2']['orders2'][1]['id'], $orders[1]['id']);
1176
    }
1177
1178
    public function testInverseOfDynamic(): void
1179
    {
1180
        $customer = Customer::findOne(1);
1181
1182
        /** request the inverseOf relation without explicitly (eagerly) loading it */
1183
        $orders2 = $customer->getOrders2()->all();
1184
        $this->assertSame($customer, $orders2[0]->customer2);
1185
1186
        $orders2 = $customer->getOrders2()->one();
1187
        $this->assertSame($customer, $orders2->customer2);
1188
1189
        /**
1190
         * request the inverseOf relation while also explicitly eager loading it (while possible, this is of course
1191
         * redundant)
1192
         */
1193
        $orders2 = $customer->getOrders2()->with('customer2')->all();
1194
        $this->assertSame($customer, $orders2[0]->customer2);
1195
1196
        $orders2 = $customer->getOrders2()->with('customer2')->one();
1197
        $this->assertSame($customer, $orders2->customer2);
1198
1199
        /** request the inverseOf relation as array */
1200
        $orders2 = $customer->getOrders2()->asArray()->all();
1201
        $this->assertEquals($customer['id'], $orders2[0]['customer2']['id']);
1202
1203
        $orders2 = $customer->getOrders2()->asArray()->one();
1204
        $this->assertEquals($customer['id'], $orders2['customer2']['id']);
1205
    }
1206
1207
    public function testDefaultValues(): void
1208
    {
1209
        $model = new Type();
1210
1211
        $model->loadDefaultValues();
1212
1213
        $this->assertEquals(1, $model->int_col2);
1214
        $this->assertEquals('something', $model->char_col2);
1215
        $this->assertEquals(1.23, $model->float_col2);
1216
        $this->assertEquals(33.22, $model->numeric_col);
1217
        $this->assertEquals(true, $model->bool_col2);
1218
        $this->assertEquals('2002-01-01 00:00:00', $model->time);
1219
1220
        $model = new Type();
1221
        $model->char_col2 = 'not something';
1222
1223
        $model->loadDefaultValues();
1224
        $this->assertEquals('not something', $model->char_col2);
1225
1226
        $model = new Type();
1227
        $model->char_col2 = 'not something';
1228
1229
        $model->loadDefaultValues(false);
1230
        $this->assertEquals('something', $model->char_col2);
1231
    }
1232
1233
    public function testUnlinkAllViaTable(): void
1234
    {
1235
        /** @var $orderClass ActiveRecordInterface */
1236
        $orderClass = Order::class;
1237
1238
        /** @var $orderItemClass ActiveRecordInterface */
1239
        $orderItemClass = OrderItem::class;
1240
1241
        /** @var $itemClass ActiveRecordInterface */
1242
        $itemClass = Item::class;
1243
1244
        /** @var $orderItemsWithNullFKClass ActiveRecordInterface */
1245
        $orderItemsWithNullFKClass = OrderItemWithNullFK::class;
1246
1247
        /**
1248
         * via table with delete.
1249
         *
1250
         * @var $order  Order
1251
         */
1252
        $order = $orderClass::findOne(1);
1253
        $this->assertCount(2, $order->booksViaTable);
1254
        $orderItemCount = $orderItemClass::find()->count();
1255
        $this->assertEquals(5, $itemClass::find()->count());
1256
        $order->unlinkAll('booksViaTable', true);
1257
        $this->assertEquals(5, $itemClass::find()->count());
1258
        $this->assertEquals($orderItemCount - 2, $orderItemClass::find()->count());
1259
        $this->assertCount(0, $order->booksViaTable);
1260
1261
        /** via table without delete */
1262
        $this->assertCount(2, $order->booksWithNullFKViaTable);
1263
        $orderItemCount = $orderItemsWithNullFKClass::find()->count();
1264
        $this->assertEquals(5, $itemClass::find()->count());
1265
        $order->unlinkAll('booksWithNullFKViaTable', false);
1266
        $this->assertCount(0, $order->booksWithNullFKViaTable);
1267
        $this->assertEquals(2, $orderItemsWithNullFKClass::find()->where(
1268
            ['AND', ['item_id' => [1, 2]], ['order_id' => null]]
1269
        )->count());
1270
        $this->assertEquals($orderItemCount, $orderItemsWithNullFKClass::find()->count());
1271
        $this->assertEquals(5, $itemClass::find()->count());
1272
    }
1273
1274
    public function testCastValues(): void
1275
    {
1276
        $model = new Type();
1277
1278
        $model->int_col = 123;
1279
        $model->int_col2 = 456;
1280
        $model->smallint_col = 42;
1281
        $model->char_col = '1337';
1282
        $model->char_col2 = 'test';
1283
        $model->char_col3 = 'test123';
1284
        $model->float_col = 3.742;
1285
        $model->float_col2 = 42.1337;
1286
        $model->bool_col = true;
1287
        $model->bool_col2 = false;
1288
        $model->save();
1289
1290
        /** @var $model Type */
1291
        $model = Type::find()->one();
1292
1293
        $this->assertSame(123, $model->int_col);
1294
        $this->assertSame(456, $model->int_col2);
1295
        $this->assertSame(42, $model->smallint_col);
1296
        $this->assertSame('1337', trim($model->char_col));
1297
        $this->assertSame('test', $model->char_col2);
1298
        $this->assertSame('test123', $model->char_col3);
1299
    }
1300
1301
    public function testIssues(): void
1302
    {
1303
        $this->loadFixture(Category::getConnection());
1304
1305
        /** {@see https://github.com/yiisoft/yii2/issues/4938} */
1306
        $category = Category::findOne(2);
1307
1308
        $this->assertInstanceOf(Category::class, $category);
1309
        $this->assertEquals(3, $category->getItems()->count());
1310
        $this->assertEquals(1, $category->getLimitedItems()->count());
1311
        $this->assertEquals(1, $category->getLimitedItems()->distinct(true)->count());
1312
1313
        /** {@see https://github.com/yiisoft/yii2/issues/3197} */
1314
        $orders = Order::find()->with('orderItems')->orderBy('id')->all();
1315
1316
        $this->assertCount(3, $orders);
1317
        $this->assertCount(2, $orders[0]->orderItems);
1318
        $this->assertCount(3, $orders[1]->orderItems);
1319
        $this->assertCount(1, $orders[2]->orderItems);
1320
1321
        $orders = Order::find()
1322
            ->with([
1323
                'orderItems' => static function ($q) {
1324
                    $q->indexBy('item_id');
1325
                },
1326
            ])
1327
            ->orderBy('id')
1328
            ->all();
1329
1330
        $this->assertCount(3, $orders);
1331
        $this->assertCount(2, $orders[0]->orderItems);
1332
        $this->assertCount(3, $orders[1]->orderItems);
1333
        $this->assertCount(1, $orders[2]->orderItems);
1334
1335
        /** {@see https://github.com/yiisoft/yii2/issues/8149} */
1336
        $model = new Customer();
1337
1338
        $model->name = 'test';
1339
        $model->email = 'test';
1340
        $model->save();
1341
        $model->updateCounters(['status' => 1]);
1342
        $this->assertEquals(1, $model->status);
1343
    }
1344
1345
    public function testPopulateRecordCallWhenQueryingOnParentClass(): void
1346
    {
1347
        (new Cat())->save();
1348
        (new Dog())->save();
1349
1350
        $animal = Animal::find()->where(['type' => Dog::class])->one();
1351
        $this->assertEquals('bark', $animal->getDoes());
1352
1353
        $animal = Animal::find()->where(['type' => Cat::class])->one();
1354
        $this->assertEquals('meow', $animal->getDoes());
1355
    }
1356
1357
    public function testSaveEmpty(): void
1358
    {
1359
        $record = new NullValues();
1360
1361
        $this->assertTrue($record->save());
1362
        $this->assertEquals(1, $record->id);
1363
    }
1364
1365
    public function testOptimisticLock(): void
1366
    {
1367
        /** @var $record Document */
1368
        $record = Document::findOne(1);
1369
1370
        $record->content = 'New Content';
1371
        $record->save();
1372
        $this->assertEquals(1, $record->version);
1373
1374
        $record = Document::findOne(1);
1375
1376
        $record->content = 'Rewrite attempt content';
1377
        $record->version = 0;
1378
        $this->expectException(StaleObjectException::class);
1379
        $record->save();
1380
    }
1381
1382
    public function testPopulateWithoutPk(): void
1383
    {
1384
        /** tests with single pk asArray */
1385
        $aggregation = Customer::find()
1386
            ->select(['{{customer}}.[[status]]', 'SUM({{order}}.[[total]]) AS [[sumtotal]]'])
1387
            ->joinWith('ordersPlain', false)
1388
            ->groupBy('{{customer}}.[[status]]')
1389
            ->orderBy('status')
1390
            ->asArray()
1391
            ->all();
1392
1393
        $expected = [
1394
            [
1395
                'status' => 1,
1396
                'sumtotal' => 183,
1397
            ],
1398
            [
1399
                'status' => 2,
1400
                'sumtotal' => 0,
1401
            ],
1402
        ];
1403
        $this->assertEquals($expected, $aggregation);
1404
1405
        /** tests with single pk with Models */
1406
        $aggregation = Customer::find()
1407
            ->select(['{{customer}}.[[status]]', 'SUM({{order}}.[[total]]) AS [[sumTotal]]'])
1408
            ->joinWith('ordersPlain', false)
1409
            ->groupBy('{{customer}}.[[status]]')
1410
            ->orderBy('status')
1411
            ->all();
1412
1413
        $this->assertCount(2, $aggregation);
1414
        $this->assertContainsOnlyInstancesOf(Customer::class, $aggregation);
1415
1416
        foreach ($aggregation as $item) {
1417
            if ($item->status === 1) {
1418
                $this->assertEquals(183, $item->sumTotal);
1419
            } elseif ($item->status === 2) {
1420
                $this->assertEquals(0, $item->sumTotal);
1421
            }
1422
        }
1423
1424
        /** tests with composite pk asArray */
1425
        $aggregation = OrderItem::find()
1426
            ->select(['[[order_id]]', 'SUM([[subtotal]]) AS [[subtotal]]'])
1427
            ->joinWith('order', false)
1428
            ->groupBy('[[order_id]]')
1429
            ->orderBy('[[order_id]]')
1430
            ->asArray()
1431
            ->all();
1432
1433
        $expected = [
1434
            [
1435
                'order_id' => 1,
1436
                'subtotal' => 70,
1437
            ],
1438
            [
1439
                'order_id' => 2,
1440
                'subtotal' => 33,
1441
            ],
1442
            [
1443
                'order_id' => 3,
1444
                'subtotal' => 40,
1445
            ],
1446
        ];
1447
        $this->assertEquals($expected, $aggregation);
1448
1449
        /** tests with composite pk with Models */
1450
        $aggregation = OrderItem::find()
1451
            ->select(['[[order_id]]', 'SUM([[subtotal]]) AS [[subtotal]]'])
1452
            ->joinWith('order', false)
1453
            ->groupBy('[[order_id]]')
1454
            ->orderBy('[[order_id]]')
1455
            ->all();
1456
1457
        $this->assertCount(3, $aggregation);
1458
        $this->assertContainsOnlyInstancesOf(OrderItem::class, $aggregation);
1459
1460
        foreach ($aggregation as $item) {
1461
            if ($item->order_id === 1) {
1462
                $this->assertEquals(70, $item->subtotal);
1463
            } elseif ($item->order_id === 2) {
1464
                $this->assertEquals(33, $item->subtotal);
1465
            } elseif ($item->order_id === 3) {
1466
                $this->assertEquals(40, $item->subtotal);
1467
            }
1468
        }
1469
    }
1470
1471
    /**
1472
     * {@see https://github.com/yiisoft/yii2/issues/9006}
1473
     */
1474
    public function testBit(): void
1475
    {
1476
        $falseBit = BitValues::findOne(1);
1477
        $this->assertEquals(false, $falseBit->val);
1478
1479
        $trueBit = BitValues::findOne(2);
1480
        $this->assertEquals(true, $trueBit->val);
1481
    }
1482
1483
    public function testLinkWhenRelationIsIndexed2(): void
1484
    {
1485
        $order = Order::find()->with('orderItems2')->where(['id' => 1])->one();
1486
1487
        $orderItem = new OrderItem();
1488
1489
        $orderItem->order_id = $order->id;
1490
        $orderItem->item_id = 3;
1491
        $orderItem->quantity = 1;
1492
        $orderItem->subtotal = 10.0;
1493
1494
        $order->link('orderItems2', $orderItem);
1495
        $this->assertTrue(isset($order->orderItems2['3']));
1496
    }
1497
1498
    public function testUpdateAttributes(): void
1499
    {
1500
        $this->loadFixture(Customer::getConnection());
1501
1502
        $order = Order::findOne(1);
1503
1504
        $newTotal = 978;
1505
        $this->assertSame(1, $order->updateAttributes(['total' => $newTotal]));
1506
        $this->assertEquals($newTotal, $order->total);
1507
1508
        $order = Order::findOne(1);
1509
1510
        $this->assertEquals($newTotal, $order->total);
1511
1512
        /** @see https://github.com/yiisoft/yii2/issues/12143 */
1513
        $newOrder = new Order();
1514
1515
        $this->assertTrue($newOrder->getIsNewRecord());
1516
1517
        $newTotal = 200;
1518
        $this->assertSame(0, $newOrder->updateAttributes(['total' => $newTotal]));
1519
        $this->assertTrue($newOrder->getIsNewRecord());
1520
        $this->assertEquals($newTotal, $newOrder->total);
1521
    }
1522
1523
    public function testEmulateExecution(): void
1524
    {
1525
        $this->assertGreaterThan(0, Customer::find()->count());
1526
1527
        $rows = Customer::find()->emulateExecution()->all();
1528
        $this->assertSame([], $rows);
1529
1530
        $row = Customer::find()->emulateExecution()->one();
1531
        $this->assertNull($row);
1532
1533
        $exists = Customer::find()->emulateExecution()->exists();
1534
        $this->assertFalse($exists);
1535
1536
        $count = Customer::find()->emulateExecution()->count();
1537
        $this->assertSame(0, $count);
1538
1539
        $sum = Customer::find()->emulateExecution()->sum('id');
1540
        $this->assertSame(0, $sum);
1541
1542
        $sum = Customer::find()->emulateExecution()->average('id');
1543
        $this->assertSame(0, $sum);
1544
1545
        $max = Customer::find()->emulateExecution()->max('id');
1546
        $this->assertNull($max);
1547
1548
        $min = Customer::find()->emulateExecution()->min('id');
1549
        $this->assertNull($min);
1550
1551
        $scalar = Customer::find()->select(['id'])->emulateExecution()->scalar();
1552
        $this->assertNull($scalar);
1553
1554
        $column = Customer::find()->select(['id'])->emulateExecution()->column();
1555
        $this->assertSame([], $column);
1556
    }
1557
1558
    /**
1559
     * {@see https://github.com/yiisoft/yii2/issues/12213}
1560
     */
1561
    public function testUnlinkAllOnCondition(): void
1562
    {
1563
        /** @var Category $categoryClass */
1564
        $categoryClass = Category::class;
1565
1566
        /** @var Item $itemClass */
1567
        $itemClass = Item::class;
1568
1569
        /** Ensure there are three items with category_id = 2 in the Items table */
1570
        $itemsCount = $itemClass::find()->where(['category_id' => 2])->count();
1571
        $this->assertEquals(3, $itemsCount);
1572
1573
        $categoryQuery = $categoryClass::find()->with('limitedItems')->where(['id' => 2]);
1574
1575
        /**
1576
         * Ensure that limitedItems relation returns only one item (category_id = 2 and id in (1,2,3))
1577
         */
1578
        $category = $categoryQuery->one();
1579
        $this->assertCount(1, $category->limitedItems);
1580
1581
        /** Unlink all items in the limitedItems relation */
1582
        $category->unlinkAll('limitedItems', true);
1583
1584
        /** Make sure that only one item was unlinked */
1585
        $itemsCount = $itemClass::find()->where(['category_id' => 2])->count();
1586
        $this->assertEquals(2, $itemsCount);
1587
1588
        /** Call $categoryQuery again to ensure no items were found */
1589
        $this->assertCount(0, $categoryQuery->one()->limitedItems);
1590
    }
1591
1592
    /**
1593
     * {@see https://github.com/yiisoft/yii2/issues/12213}
1594
     */
1595
    public function testUnlinkAllOnConditionViaTable(): void
1596
    {
1597
        $this->loadFixture(Order::getConnection());
1598
1599
        /** @var Order $orderClass */
1600
        $orderClass = Order::class;
1601
1602
        /** @var Item $itemClass */
1603
        $itemClass = Item::class;
1604
1605
        /** Ensure there are three items with category_id = 2 in the Items table */
1606
        $itemsCount = $itemClass::find()->where(['category_id' => 2])->count();
1607
        $this->assertEquals(3, $itemsCount);
1608
1609
        $orderQuery = $orderClass::find()->with('limitedItems')->where(['id' => 2]);
1610
1611
        /**
1612
         * Ensure that limitedItems relation returns only one item (category_id = 2 and id in (4, 5)).
1613
         */
1614
        $category = $orderQuery->one();
1615
        $this->assertCount(2, $category->limitedItems);
1616
1617
        /** Unlink all items in the limitedItems relation */
1618
        $category->unlinkAll('limitedItems', true);
1619
1620
        /** Call $orderQuery again to ensure that links are removed */
1621
        $this->assertCount(0, $orderQuery->one()->limitedItems);
1622
1623
        /** Make sure that only links were removed, the items were not removed */
1624
        $this->assertEquals(3, $itemClass::find()->where(['category_id' => 2])->count());
1625
    }
1626
1627
    /**
1628
     * {@see https://github.com/yiisoft/yii2/pull/13891}
1629
     */
1630
    public function testIndexByAfterLoadingRelations(): void
1631
    {
1632
        $orderClass = Order::class;
1633
1634
        $orderClass::find()->with('customer')->indexBy(function (Order $order) {
1635
            $this->assertTrue($order->isRelationPopulated('customer'));
1636
            $this->assertNotEmpty($order->customer->id);
1637
1638
            return $order->customer->id;
1639
        })->all();
1640
1641
        $orders = $orderClass::find()->with('customer')->indexBy('customer.id')->all();
1642
1643
        foreach ($orders as $customer_id => $order) {
1644
            $this->assertEquals($customer_id, $order->customer_id);
1645
        }
1646
    }
1647
1648
    /**
1649
     * Verify that {{}} are not going to be replaced in parameters.
1650
     */
1651
    public function testNoTablenameReplacement(): void
1652
    {
1653
        /** @var Customer $customer */
1654
        $customer = new Customer();
1655
1656
        $customer->name = 'Some {{weird}} name';
1657
        $customer->email = '[email protected]';
1658
        $customer->address = 'Some {{%weird}} address';
1659
        $customer->insert();
1660
        $customer->refresh();
1661
1662
        $this->assertEquals('Some {{weird}} name', $customer->name);
1663
        $this->assertEquals('Some {{%weird}} address', $customer->address);
1664
1665
        $customer->name = 'Some {{updated}} name';
1666
        $customer->address = 'Some {{%updated}} address';
1667
        $customer->update();
1668
1669
        $this->assertEquals('Some {{updated}} name', $customer->name);
1670
        $this->assertEquals('Some {{%updated}} address', $customer->address);
1671
    }
1672
1673
    /**
1674
     * Ensure no ambiguous column error occurs if ActiveQuery adds a JOIN.
1675
     *
1676
     * {@see https://github.com/yiisoft/yii2/issues/13757}
1677
     */
1678
    public function testAmbiguousColumnFindOne(): void
1679
    {
1680
        CustomerQuery::$joinWithProfile = true;
1681
1682
        $model = Customer::findOne(1);
1683
1684
        $this->assertTrue($model->refresh());
1685
1686
        CustomerQuery::$joinWithProfile = false;
1687
    }
1688
1689
    public function testFindOneByColumnName(): void
1690
    {
1691
        $model = Customer::findOne(['id' => 1]);
1692
        $this->assertEquals(1, $model->id);
1693
1694
        CustomerQuery::$joinWithProfile = true;
1695
1696
        $model = Customer::findOne(['customer.id' => 1]);
1697
1698
        $this->assertEquals(1, $model->id);
1699
1700
        CustomerQuery::$joinWithProfile = false;
1701
    }
1702
1703
    public function filterTableNamesFromAliasesProvider(): array
1704
    {
1705
        return [
1706
            'table name as string'         => ['customer', []],
1707
            'table name as array'          => [['customer'], []],
1708
            'table names'                  => [['customer', 'order'], []],
1709
            'table name and a table alias' => [['customer', 'ord' => 'order'], ['ord']],
1710
            'table alias'                  => [['csr' => 'customer'], ['csr']],
1711
            'table aliases'                => [['csr' => 'customer', 'ord' => 'order'], ['csr', 'ord']],
1712
        ];
1713
    }
1714
1715
    /**
1716
     * @dataProvider filterTableNamesFromAliasesProvider
1717
     *
1718
     * @param array|string$fromParams
1719
     * @param $expectedAliases
1720
     */
1721
    public function testFilterTableNamesFromAliases($fromParams, array $expectedAliases): void
1722
    {
1723
        $query = Customer::find()->from($fromParams);
1724
1725
        $aliases = $this->invokeMethod(new Customer(), 'filterValidAliases', [$query]);
1726
1727
        $this->assertEquals($expectedAliases, $aliases);
1728
    }
1729
1730
    public function legalValuesForFindByCondition(): array
1731
    {
1732
        return [
1733
            [Customer::class, ['id' => 1]],
1734
            [Customer::class, ['customer.id' => 1]],
1735
            [Customer::class, ['[[id]]' => 1]],
1736
            [Customer::class, ['{{customer}}.[[id]]' => 1]],
1737
            [Customer::class, ['{{%customer}}.[[id]]' => 1]],
1738
            [CustomerWithAlias::class, ['id' => 1]],
1739
            [CustomerWithAlias::class, ['customer.id' => 1]],
1740
            [CustomerWithAlias::class, ['[[id]]' => 1]],
1741
            [CustomerWithAlias::class, ['{{customer}}.[[id]]' => 1]],
1742
            [CustomerWithAlias::class, ['{{%customer}}.[[id]]' => 1]],
1743
            [CustomerWithAlias::class, ['csr.id' => 1]],
1744
            [CustomerWithAlias::class, ['{{csr}}.[[id]]' => 1]],
1745
        ];
1746
    }
1747
1748
    /**
1749
     * @dataProvider legalValuesForFindByCondition
1750
     *
1751
     * @param string $modelClassName
1752
     * @param array $validFilter
1753
     *
1754
     * @throws Exception
1755
     * @throws InvalidConfigException
1756
     * @throws NotSupportedException
1757
     */
1758
    public function testLegalValuesForFindByCondition(string $modelClassName, array $validFilter): void
1759
    {
1760
        /** @var Query $query */
1761
        $query = $this->invokeMethod(new $modelClassName(), 'findByCondition', [$validFilter]);
1762
1763
        Customer::getConnection()->getQueryBuilder()->build($query);
1764
1765
        $this->assertTrue(true);
1766
    }
1767
1768
    public function illegalValuesForFindByCondition(): array
1769
    {
1770
        return [
1771
            [Customer::class, ['id' => ['`id`=`id` and 1' => 1]]],
1772
            [Customer::class, ['id' => [
1773
                'legal' => 1,
1774
                '`id`=`id` and 1' => 1,
1775
            ]]],
1776
            [Customer::class, ['id' => [
1777
                'nested_illegal' => [
1778
                    'false or 1=' => 1
1779
                ]
1780
            ]]],
1781
            [Customer::class, [['true--' => 1]]],
1782
1783
            [CustomerWithAlias::class, ['csr.id' => ['`csr`.`id`=`csr`.`id` and 1' => 1]]],
1784
            [CustomerWithAlias::class, ['csr.id' => [
1785
                'legal' => 1,
1786
                '`csr`.`id`=`csr`.`id` and 1' => 1,
1787
            ]]],
1788
            [CustomerWithAlias::class, ['csr.id' => [
1789
                'nested_illegal' => [
1790
                    'false or 1=' => 1
1791
                ]
1792
            ]]],
1793
            [CustomerWithAlias::class, [['true--' => 1]]],
1794
        ];
1795
    }
1796
1797
    /**
1798
     * @dataProvider illegalValuesForFindByCondition
1799
     *
1800
     * @param string $modelClassName
1801
     * @param array $filterWithInjection
1802
     *
1803
     * @throws Exception
1804
     * @throws InvalidConfigException
1805
     * @throws NotSupportedException
1806
     */
1807
    public function testValueEscapingInFindByCondition(string $modelClassName, array $filterWithInjection): void
1808
    {
1809
        $this->expectException(InvalidArgumentException::class);
1810
        $this->expectExceptionMessageMatches(
1811
            '/^Key "(.+)?" is not a column name and can not be used as a filter$/'
1812
        );
1813
1814
        /** @var Query $query */
1815
        $query = $this->invokeMethod(new $modelClassName(), 'findByCondition', $filterWithInjection);
1816
1817
        Customer::getConnection()->getQueryBuilder()->build($query);
1818
    }
1819
1820
    /**
1821
     * {@see https://github.com/yiisoft/yii2/issues/5786}
1822
     *
1823
     * @depends testJoinWith
1824
     */
1825
    public function testFindWithConstructors(): void
1826
    {
1827
        /** @var OrderWithConstructor[] $orders */
1828
        $orders = OrderWithConstructor::find()->with(['customer.profile', 'orderItems'])->orderBy('id')->all();
1829
1830
        $this->assertCount(3, $orders);
1831
        $order = $orders[0];
1832
        $this->assertEquals(1, $order->id);
1833
1834
        $this->assertNotNull($order->customer);
1835
        $this->assertInstanceOf(CustomerWithConstructor::class, $order->customer);
1836
        $this->assertEquals(1, $order->customer->id);
1837
1838
        $this->assertNotNull($order->customer->profile);
1839
        $this->assertInstanceOf(ProfileWithConstructor::class, $order->customer->profile);
1840
        $this->assertEquals(1, $order->customer->profile->id);
1841
1842
        $this->assertNotNull($order->customerJoinedWithProfile);
1843
        $customerWithProfile = $order->customerJoinedWithProfile;
1844
        $this->assertInstanceOf(CustomerWithConstructor::class, $customerWithProfile);
1845
        $this->assertEquals(1, $customerWithProfile->id);
1846
1847
        $this->assertNotNull($customerProfile = $customerWithProfile->profile);
1848
        $this->assertInstanceOf(ProfileWithConstructor::class, $customerProfile);
1849
        $this->assertEquals(1, $customerProfile->id);
1850
1851
        $this->assertCount(2, $order->orderItems);
1852
1853
        $item = $order->orderItems[0];
1854
        $this->assertInstanceOf(OrderItemWithConstructor::class, $item);
1855
1856
        $this->assertEquals(1, $item->item_id);
1857
1858
        /** {@see https://github.com/yiisoft/yii2/issues/15540} */
1859
        $orders = OrderWithConstructor::find()
1860
            ->with(['customer.profile', 'orderItems'])
1861
            ->orderBy('id')
1862
            ->asArray(true)
1863
            ->all();
1864
        $this->assertCount(3, $orders);
1865
    }
1866
1867
    public function testCustomARRelation(): void
1868
    {
1869
        $this->loadFixture(Customer::getConnection());
1870
1871
        $orderItem = OrderItem::findOne(1);
1872
1873
        $this->assertInstanceOf(Order::class, $orderItem->custom);
1874
    }
1875
1876
1877
    public function testRefreshQuerySetAliasFindRecord(): void
1878
    {
1879
        $customer = new CustomerWithAlias();
1880
1881
        $customer->id = 1;
1882
        $customer->refresh();
1883
1884
        $this->assertEquals(1, $customer->id);
1885
    }
1886
1887
    public function testResetNotSavedRelation(): void
1888
    {
1889
        $order = new Order();
1890
1891
        $order->customer_id = 1;
1892
        $order->created_at = 1325502201;
1893
        $order->total = 0;
1894
1895
        $orderItem = new OrderItem();
1896
1897
        $order->orderItems;
1898
1899
        $order->populateRelation('orderItems', [$orderItem]);
1900
1901
        $order->save();
1902
1903
        $this->assertCount(1, $order->orderItems);
1904
    }
1905
1906
    public function testIssetException(): void
1907
    {
1908
        $cat = new Cat();
1909
1910
        $this->assertFalse(isset($cat->exception));
1911
    }
1912
1913
    public function testIssetThrowable(): void
1914
    {
1915
        $cat = new Cat();
1916
1917
        $this->assertFalse(isset($cat->throwable));
1918
    }
1919
1920
    /**
1921
     * {@see https://github.com/yiisoft/yii2/issues/15482}
1922
     */
1923
    public function testEagerLoadingUsingStringIdentifiers(): void
1924
    {
1925
        if (!\in_array($this->driverName, ['mysql', 'pgsql', 'sqlite'])) {
1926
            $this->markTestSkipped('This test has fixtures only for databases MySQL, PostgreSQL and SQLite.');
1927
        }
1928
1929
        $betas = Beta::find()->with('alpha')->all();
1930
1931
        $this->assertNotEmpty($betas);
1932
1933
        $alphaIdentifiers = [];
1934
1935
        /** @var Beta[] $betas */
1936
        foreach ($betas as $beta) {
1937
            $this->assertNotNull($beta->alpha);
1938
            $this->assertEquals($beta->alpha_string_identifier, $beta->alpha->string_identifier);
1939
            $alphaIdentifiers[] = $beta->alpha->string_identifier;
1940
        }
1941
1942
        $this->assertEquals(['1', '01', '001', '001', '2', '2b', '2b', '02'], $alphaIdentifiers);
1943
    }
1944
}
1945