ActiveQueryTest::testJoinWithAlias()   F
last analyzed

Complexity

Conditions 35
Paths > 20000

Size

Total Lines 233
Code Lines 166

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 35
eloc 166
nc 65536
nop 1
dl 0
loc 233
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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 ReflectionException;
8
use Throwable;
9
use Yiisoft\ActiveRecord\ActiveQuery;
10
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\BitValues;
11
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Category;
12
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer;
13
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerQuery;
14
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Document;
15
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Dossier;
16
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Item;
17
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Order;
18
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItem;
19
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItemWithNullFK;
20
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderWithNullFK;
21
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Profile;
22
use Yiisoft\ActiveRecord\Tests\Support\Assert;
23
use Yiisoft\ActiveRecord\Tests\Support\DbHelper;
24
use Yiisoft\Db\Command\AbstractCommand;
25
use Yiisoft\Db\Exception\Exception;
26
use Yiisoft\Db\Exception\InvalidArgumentException;
27
use Yiisoft\Db\Exception\InvalidCallException;
28
use Yiisoft\Db\Exception\InvalidConfigException;
29
use Yiisoft\Db\Exception\StaleObjectException;
30
use Yiisoft\Db\Exception\UnknownPropertyException;
31
use Yiisoft\Db\Helper\DbArrayHelper;
32
use Yiisoft\Db\Query\QueryInterface;
33
34
use function sort;
35
use function ucfirst;
36
37
abstract class ActiveQueryTest extends TestCase
38
{
39
    public function testOptions(): void
40
    {
41
        $this->checkFixture($this->db, 'customer', true);
42
43
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
44
45
        $query = $customerQuery->on(['a' => 'b'])->joinWith('profile');
46
        $this->assertEquals($query->getARClass(), Customer::class);
47
        $this->assertEquals($query->getOn(), ['a' => 'b']);
48
        $this->assertEquals($query->getJoinWith(), [[['profile'], true, 'LEFT JOIN']]);
49
        $customerQuery->resetJoinWith();
50
        $this->assertEquals($query->getJoinWith(), []);
51
    }
52
53
    public function testPrepare(): void
54
    {
55
        $this->checkFixture($this->db, 'customer');
56
57
        $query = new ActiveQuery(Customer::class, $this->db);
58
        $this->assertInstanceOf(QueryInterface::class, $query->prepare($this->db->getQueryBuilder()));
59
    }
60
61
    public function testPopulateEmptyRows(): void
62
    {
63
        $this->checkFixture($this->db, 'customer');
64
65
        $query = new ActiveQuery(Customer::class, $this->db);
66
        $this->assertEquals([], $query->populate([]));
67
    }
68
69
    public function testPopulateFilledRows(): void
70
    {
71
        $this->checkFixture($this->db, 'customer');
72
73
        $query = new ActiveQuery(Customer::class, $this->db);
74
        $rows = $query->all();
75
        $result = $query->populate($rows);
76
        $this->assertEquals($rows, $result);
77
    }
78
79
    public function testAllPopulate(): void
80
    {
81
        $this->checkFixture($this->db, 'customer');
82
83
        $query = new ActiveQuery(Customer::class, $this->db);
84
85
        foreach ($query->allPopulate() as $customer) {
86
            $this->assertInstanceOf(Customer::class, $customer);
87
        }
88
89
        $this->assertCount(3, $query->allPopulate());
90
    }
91
92
    public function testOnePopulate(): void
93
    {
94
        $this->checkFixture($this->db, 'customer');
95
96
        $query = new ActiveQuery(Customer::class, $this->db);
97
        $this->assertInstanceOf(Customer::class, $query->onePopulate());
98
    }
99
100
    public function testCreateCommand(): void
101
    {
102
        $this->checkFixture($this->db, 'customer');
103
104
        $query = new ActiveQuery(Customer::class, $this->db);
105
        $this->assertInstanceOf(AbstractCommand::class, $query->createCommand());
106
    }
107
108
    public function testQueryScalar(): void
109
    {
110
        $this->checkFixture($this->db, 'customer');
111
112
        $query = new ActiveQuery(Customer::class, $this->db);
113
        $this->assertEquals('user1', Assert::invokeMethod($query, 'queryScalar', ['name']));
114
    }
115
116
    public function testGetJoinWith(): void
117
    {
118
        $this->checkFixture($this->db, 'customer');
119
120
        $query = new ActiveQuery(Customer::class, $this->db);
121
        $query->joinWith('profile');
122
        $this->assertEquals([[['profile'], true, 'LEFT JOIN']], $query->getJoinWith());
123
    }
124
125
    public function testInnerJoinWith(): void
126
    {
127
        $this->checkFixture($this->db, 'customer');
128
129
        $query = new ActiveQuery(Customer::class, $this->db);
130
        $query->innerJoinWith('profile');
131
        $this->assertEquals([[['profile'], true, 'INNER JOIN']], $query->getJoinWith());
132
    }
133
134
    public function testBuildJoinWithRemoveDuplicateJoinByTableName(): void
135
    {
136
        $this->checkFixture($this->db, 'customer');
137
138
        $query = new ActiveQuery(Customer::class, $this->db);
139
        $query->innerJoinWith('orders')->joinWith('orders.orderItems');
140
        Assert::invokeMethod($query, 'buildJoinWith');
141
        $this->assertEquals([
142
            [
143
                'INNER JOIN',
144
                'order',
145
                '{{customer}}.[[id]] = {{order}}.[[customer_id]]',
146
            ],
147
            [
148
                'LEFT JOIN',
149
                'order_item',
150
                '{{order}}.[[id]] = {{order_item}}.[[order_id]]',
151
            ],
152
        ], $query->getJoins());
153
    }
154
155
    public function testGetQueryTableNameFromNotSet(): void
156
    {
157
        $this->checkFixture($this->db, 'customer');
158
159
        $query = new ActiveQuery(Customer::class, $this->db);
160
        $this->assertEquals(['customer', 'customer'], Assert::invokeMethod($query, 'getTableNameAndAlias'));
161
    }
162
163
    public function testGetQueryTableNameFromSet(): void
164
    {
165
        $this->checkFixture($this->db, 'customer');
166
167
        $query = new ActiveQuery(Customer::class, $this->db);
168
        $query->from(['alias' => 'customer']);
169
        $this->assertEquals(['customer', 'alias'], Assert::invokeMethod($query, 'getTableNameAndAlias'));
170
    }
171
172
    public function testOnCondition(): void
173
    {
174
        $this->checkFixture($this->db, 'customer');
175
176
        $on = ['active' => true];
177
        $params = ['a' => 'b'];
178
179
        $query = new ActiveQuery(Customer::class, $this->db);
180
        $query->onCondition($on, $params);
181
        $this->assertEquals($on, $query->getOn());
182
        $this->assertEquals($params, $query->getParams());
183
    }
184
185
    public function testAndOnConditionOnNotSet(): void
186
    {
187
        $this->checkFixture($this->db, 'customer');
188
189
        $on = ['active' => true];
190
        $params = ['a' => 'b'];
191
        $query = new ActiveQuery(Customer::class, $this->db);
192
        $query->andOnCondition($on, $params);
193
        $this->assertEquals($on, $query->getOn());
194
        $this->assertEquals($params, $query->getParams());
195
    }
196
197
    public function testAndOnConditionOnSet(): void
198
    {
199
        $this->checkFixture($this->db, 'customer');
200
201
        $onOld = ['active' => true];
202
        $on = ['active' => true];
203
        $params = ['a' => 'b'];
204
205
        $query = new ActiveQuery(Customer::class, $this->db);
206
207
        $query->on($onOld)->andOnCondition($on, $params);
208
209
        $this->assertEquals(['and', $onOld, $on], $query->getOn());
210
        $this->assertEquals($params, $query->getParams());
211
    }
212
213
    public function testOrOnConditionOnNotSet(): void
214
    {
215
        $this->checkFixture($this->db, 'customer');
216
217
        $on = ['active' => true];
218
        $params = ['a' => 'b'];
219
220
        $query = new ActiveQuery(Customer::class, $this->db);
221
222
        $query->orOnCondition($on, $params);
223
224
        $this->assertEquals($on, $query->getOn());
225
        $this->assertEquals($params, $query->getParams());
226
    }
227
228
    public function testOrOnConditionOnSet(): void
229
    {
230
        $this->checkFixture($this->db, 'customer');
231
232
        $onOld = ['active' => true];
233
        $on = ['active' => true];
234
        $params = ['a' => 'b'];
235
236
        $query = new ActiveQuery(Customer::class, $this->db);
237
238
        $query->on($onOld)->orOnCondition($on, $params);
239
240
        $this->assertEquals(['or', $onOld, $on], $query->getOn());
241
        $this->assertEquals($params, $query->getParams());
242
    }
243
244
    public function testViaTable(): void
245
    {
246
        $this->checkFixture($this->db, 'customer');
247
248
        $order = new Order($this->db);
249
250
        $query = new ActiveQuery(Customer::class, $this->db);
251
252
        $query->primaryModel($order)->viaTable(Profile::class, ['id' => 'item_id']);
253
254
        $this->assertInstanceOf(ActiveQuery::class, $query);
255
        $this->assertInstanceOf(ActiveQuery::class, $query->getVia());
256
    }
257
258
    public function testAliasNotSet(): void
259
    {
260
        $this->checkFixture($this->db, 'customer');
261
262
        $query = new ActiveQuery(Customer::class, $this->db);
263
264
        $query->alias('alias');
265
266
        $this->assertInstanceOf(ActiveQuery::class, $query);
267
        $this->assertEquals(['alias' => 'customer'], $query->getFrom());
268
    }
269
270
    public function testAliasYetSet(): void
271
    {
272
        $this->checkFixture($this->db, 'customer');
273
274
        $aliasOld = ['old'];
275
276
        $query = new ActiveQuery(Customer::class, $this->db);
277
278
        $query->from($aliasOld)->alias('alias');
279
280
        $this->assertInstanceOf(ActiveQuery::class, $query);
281
        $this->assertEquals(['alias' => 'old'], $query->getFrom());
282
    }
283
284
    public function testGetTableNamesNotFilledFrom(): void
285
    {
286
        $this->checkFixture($this->db, 'profile');
287
288
        $query = new ActiveQuery(Profile::class, $this->db);
289
        $tableName = Profile::TABLE_NAME;
290
291
        $this->assertEquals(
292
            [
293
                '{{' . $tableName . '}}' => '{{' . $tableName . '}}',
294
            ],
295
            $query->getTablesUsedInFrom()
296
        );
297
    }
298
299
    public function testGetTableNamesWontFillFrom(): void
300
    {
301
        $this->checkFixture($this->db, 'profile');
302
303
        $query = new ActiveQuery(Profile::class, $this->db);
304
305
        $this->assertSame([], $query->getFrom());
306
307
        $query->getTablesUsedInFrom();
308
309
        $this->assertSame([], $query->getFrom());
310
    }
311
312
    /**
313
     * {@see https://github.com/yiisoft/yii2/issues/5341}
314
     *
315
     * Issue: Plan 1 -- * Account * -- * User
316
     * Our Tests: Category 1 -- * Item * -- * Order
317
     */
318
    public function testDeeplyNestedTableRelationWith(): void
319
    {
320
        $this->checkFixture($this->db, 'category', true);
321
322
        /** @var $category Category */
323
        $categoriesQuery = new ActiveQuery(Category::class, $this->db);
324
325
        $categories = $categoriesQuery->with('orders')->indexBy('id')->all();
326
        $category = $categories[1];
327
        $this->assertNotNull($category);
328
329
        $orders = $category->orders;
330
        $this->assertCount(2, $orders);
331
        $this->assertInstanceOf(Order::class, $orders[0]);
332
        $this->assertInstanceOf(Order::class, $orders[1]);
333
334
        $ids = [$orders[0]->id, $orders[1]->id];
335
        sort($ids);
336
        $this->assertEquals([1, 3], $ids);
337
338
        $category = $categories[2];
339
        $this->assertNotNull($category);
340
341
        $orders = $category->orders;
342
        $this->assertCount(1, $orders);
343
        $this->assertInstanceOf(Order::class, $orders[0]);
344
        $this->assertEquals(2, $orders[0]->id);
345
    }
346
347
    public function testGetSql(): void
348
    {
349
        $this->checkFixture($this->db, 'customer');
350
351
        $query = new ActiveQuery(Customer::class, $this->db);
352
353
        $query->sql('SELECT * FROM {{customer}} ORDER BY [[id]] DESC');
354
355
        $this->assertEquals('SELECT * FROM {{customer}} ORDER BY [[id]] DESC', $query->getSql());
356
    }
357
358
    public function testCustomColumns(): void
359
    {
360
        $this->checkFixture($this->db, 'customer');
361
362
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
363
364
        /** find custom column */
365
        if ($this->db->getDriverName() === 'oci') {
366
            $customers = $customerQuery
367
                ->select(['{{customer}}.*', '([[status]]*2) AS [[status2]]'])
368
                ->where(['name' => 'user3'])->onePopulate();
369
        } else {
370
            $customers = $customerQuery
371
                ->select(['*', '([[status]]*2) AS [[status2]]'])
372
                ->where(['name' => 'user3'])->onePopulate();
373
        }
374
375
        $this->assertEquals(3, $customers->getAttribute('id'));
376
        $this->assertEquals(4, $customers->status2);
0 ignored issues
show
Bug introduced by
Accessing status2 on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
377
    }
378
379
    public function testCallFind(): void
380
    {
381
        $this->checkFixture($this->db, 'customer');
382
383
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
384
385
        /** find count, sum, average, min, max, scalar */
386
        $this->assertEquals(3, $customerQuery->count());
387
        $this->assertEquals(6, $customerQuery->sum('[[id]]'));
388
        $this->assertEquals(2, $customerQuery->average('[[id]]'));
389
        $this->assertEquals(1, $customerQuery->min('[[id]]'));
390
        $this->assertEquals(3, $customerQuery->max('[[id]]'));
391
        $this->assertEquals(3, $customerQuery->select('COUNT(*)')->scalar());
392
        $this->assertEquals(2, $customerQuery->where('[[id]]=1 OR [[id]]=2')->count());
393
    }
394
395
    /**
396
     * {@see https://github.com/yiisoft/yii2/issues/8593}
397
     */
398
    public function testCountWithFindBySql(): void
399
    {
400
        $this->checkFixture($this->db, 'customer');
401
402
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
403
404
        $query = $customerQuery->findBySql('SELECT * FROM {{customer}}');
405
        $this->assertEquals(3, $query->count());
406
407
        $query = $customerQuery->findBySql('SELECT * FROM {{customer}} WHERE  [[id]]=:id', [':id' => 2]);
408
        $this->assertEquals(1, $query->count());
409
    }
410
411
    public function testDeeplyNestedTableRelation(): void
412
    {
413
        $this->checkFixture($this->db, 'customer');
414
415
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
416
417
        $customers = $customerQuery->findOne(1);
418
        $this->assertNotNull($customerQuery);
419
420
        $items = $customers->orderItems;
0 ignored issues
show
Bug introduced by
Accessing orderItems on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
421
422
        $this->assertCount(2, $items);
423
        $this->assertEquals(1, $items[0]->getAttribute('id'));
424
        $this->assertEquals(2, $items[1]->getAttribute('id'));
425
        $this->assertInstanceOf(Item::class, $items[0]);
426
        $this->assertInstanceOf(Item::class, $items[1]);
427
    }
428
429
    /**
430
     * {@see https://github.com/yiisoft/yii2/issues/5341}
431
     *
432
     * Issue: Plan 1 -- * Account * -- * User
433
     * Our Tests: Category 1 -- * Item * -- * Order
434
     */
435
    public function testDeeplyNestedTableRelation2(): void
436
    {
437
        $this->checkFixture($this->db, 'category');
438
439
        $categoryQuery = new ActiveQuery(Category::class, $this->db);
440
441
        $categories = $categoryQuery->where(['id' => 1])->onePopulate();
442
        $this->assertNotNull($categories);
443
444
        $orders = $categories->orders;
0 ignored issues
show
Bug introduced by
Accessing orders on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
445
        $this->assertCount(2, $orders);
446
        $this->assertInstanceOf(Order::class, $orders[0]);
447
        $this->assertInstanceOf(Order::class, $orders[1]);
448
449
        $ids = [$orders[0]->id, $orders[1]->getAttribute('id')];
450
        sort($ids);
451
        $this->assertEquals([1, 3], $ids);
452
453
        $categories = $categoryQuery->where(['id' => 2])->onePopulate();
454
        $this->assertNotNull($categories);
455
456
        $orders = $categories->orders;
457
        $this->assertCount(1, $orders);
458
        $this->assertEquals(2, $orders[0]->getAttribute('id'));
459
        $this->assertInstanceOf(Order::class, $orders[0]);
460
    }
461
462
    public function testJoinWith(): void
463
    {
464
        $this->checkFixture($this->db, 'order');
465
466
        /** left join and eager loading */
467
        $orderQuery = new ActiveQuery(Order::class, $this->db);
468
        $orders = $orderQuery->joinWith('customer')->orderBy('customer.id DESC, order.id')->all();
469
        $this->assertCount(3, $orders);
470
        $this->assertEquals(2, $orders[0]->id);
471
        $this->assertEquals(3, $orders[1]->id);
472
        $this->assertEquals(1, $orders[2]->id);
473
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
474
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
475
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
476
477
        /** inner join filtering and eager loading */
478
        $orderQuery = new ActiveQuery(Order::class, $this->db);
479
        $orders = $orderQuery->innerJoinWith(
480
            [
481
                'customer' => function ($query) {
482
                    $query->where('{{customer}}.[[id]]=2');
483
                },
484
            ]
485
        )->orderBy('order.id')->all();
486
        $this->assertCount(2, $orders);
487
        $this->assertEquals(2, $orders[0]->id);
488
        $this->assertEquals(3, $orders[1]->id);
489
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
490
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
491
492
        /** inner join filtering, eager loading, conditions on both primary and relation */
493
        $orderQuery = new ActiveQuery(Order::class, $this->db);
494
        $orders = $orderQuery->innerJoinWith(
495
            [
496
                'customer' => function ($query) {
497
                    $query->where(['customer.id' => 2]);
498
                },
499
            ]
500
        )->where(['order.id' => [1, 2]])->orderBy('order.id')->all();
501
        $this->assertCount(1, $orders);
502
        $this->assertEquals(2, $orders[0]->id);
503
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
504
505
        /** inner join filtering without eager loading */
506
        $orderQuery = new ActiveQuery(Order::class, $this->db);
507
        $orders = $orderQuery->innerJoinWith(
508
            [
509
                'customer' => static function ($query) {
510
                    $query->where('{{customer}}.[[id]]=2');
511
                },
512
            ],
513
            false
514
        )->orderBy('order.id')->all();
515
        $this->assertCount(2, $orders);
516
        $this->assertEquals(2, $orders[0]->id);
517
        $this->assertEquals(3, $orders[1]->id);
518
        $this->assertFalse($orders[0]->isRelationPopulated('customer'));
519
        $this->assertFalse($orders[1]->isRelationPopulated('customer'));
520
521
        /** inner join filtering without eager loading, conditions on both primary and relation */
522
        $orderQuery = new ActiveQuery(Order::class, $this->db);
523
        $orders = $orderQuery->innerJoinWith(
524
            [
525
                'customer' => static function ($query) {
526
                    $query->where(['customer.id' => 2]);
527
                },
528
            ],
529
            false
530
        )->where(['order.id' => [1, 2]])->orderBy('order.id')->all();
531
        $this->assertCount(1, $orders);
532
        $this->assertEquals(2, $orders[0]->id);
533
        $this->assertFalse($orders[0]->isRelationPopulated('customer'));
534
535
        /** join with via-relation */
536
        $orderQuery = new ActiveQuery(Order::class, $this->db);
537
        $orders = $orderQuery->innerJoinWith('books')->orderBy('order.id')->all();
538
        $this->assertCount(2, $orders);
539
        $this->assertCount(2, $orders[0]->books);
540
        $this->assertCount(1, $orders[1]->books);
541
        $this->assertEquals(1, $orders[0]->id);
542
        $this->assertEquals(3, $orders[1]->id);
543
        $this->assertTrue($orders[0]->isRelationPopulated('books'));
544
        $this->assertTrue($orders[1]->isRelationPopulated('books'));
545
546
        /** join with sub-relation */
547
        $orderQuery = new ActiveQuery(Order::class, $this->db);
548
        $orders = $orderQuery->innerJoinWith(
549
            [
550
                'items' => function ($q) {
551
                    $q->orderBy('item.id');
552
                },
553
                'items.category' => function ($q) {
554
                    $q->where('{{category}}.[[id]] = 2');
555
                },
556
            ]
557
        )->orderBy('order.id')->all();
558
        $this->assertCount(1, $orders);
559
        $this->assertCount(3, $orders[0]->items);
560
        $this->assertEquals(2, $orders[0]->id);
561
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
562
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
563
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
564
565
        /** join with table alias */
566
        $orderQuery = new ActiveQuery(Order::class, $this->db);
567
        $orders = $orderQuery->joinWith(
568
            [
569
                'customer' => function ($q) {
570
                    $q->from('customer c');
571
                },
572
            ]
573
        )->orderBy('c.id DESC, order.id')->all();
574
        $this->assertCount(3, $orders);
575
        $this->assertEquals(2, $orders[0]->id);
576
        $this->assertEquals(3, $orders[1]->id);
577
        $this->assertEquals(1, $orders[2]->id);
578
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
579
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
580
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
581
582
        /** join with table alias */
583
        $orderQuery = new ActiveQuery(Order::class, $this->db);
584
        $orders = $orderQuery->joinWith('customer as c')->orderBy('c.id DESC, order.id')->all();
585
        $this->assertCount(3, $orders);
586
        $this->assertEquals(2, $orders[0]->id);
587
        $this->assertEquals(3, $orders[1]->id);
588
        $this->assertEquals(1, $orders[2]->id);
589
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
590
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
591
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
592
593
        /** join with table alias sub-relation */
594
        $orderQuery = new ActiveQuery(Order::class, $this->db);
595
        $orders = $orderQuery->innerJoinWith(
596
            [
597
                'items as t' => function ($q) {
598
                    $q->orderBy('t.id');
599
                },
600
                'items.category as c' => function ($q) {
601
                    $q->where('{{c}}.[[id]] = 2');
602
                },
603
            ]
604
        )->orderBy('order.id')->all();
605
        $this->assertCount(1, $orders);
606
        $this->assertCount(3, $orders[0]->items);
607
        $this->assertEquals(2, $orders[0]->id);
608
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
609
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
610
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
611
612
        /** join with ON condition */
613
        $orderQuery = new ActiveQuery(Order::class, $this->db);
614
        $orders = $orderQuery->joinWith('books2')->orderBy('order.id')->all();
615
        $this->assertCount(3, $orders);
616
        $this->assertCount(2, $orders[0]->books2);
617
        $this->assertCount(0, $orders[1]->books2);
618
        $this->assertCount(1, $orders[2]->books2);
619
        $this->assertEquals(1, $orders[0]->id);
620
        $this->assertEquals(2, $orders[1]->id);
621
        $this->assertEquals(3, $orders[2]->id);
622
        $this->assertTrue($orders[0]->isRelationPopulated('books2'));
623
        $this->assertTrue($orders[1]->isRelationPopulated('books2'));
624
        $this->assertTrue($orders[2]->isRelationPopulated('books2'));
625
626
        /** lazy loading with ON condition */
627
        $orderQuery = new ActiveQuery(Order::class, $this->db);
628
        $order = $orderQuery->findOne(1);
629
        $this->assertCount(2, $order->books2);
0 ignored issues
show
Bug introduced by
Accessing books2 on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
630
631
        $orderQuery = new ActiveQuery(Order::class, $this->db);
632
        $order = $orderQuery->findOne(2);
633
        $this->assertCount(0, $order->books2);
634
635
        $order = new ActiveQuery(Order::class, $this->db);
636
        $order = $order->findOne(3);
637
        $this->assertCount(1, $order->books2);
638
639
        /** eager loading with ON condition */
640
        $orderQuery = new ActiveQuery(Order::class, $this->db);
641
        $orders = $orderQuery->with('books2')->all();
642
        $this->assertCount(3, $orders);
643
        $this->assertCount(2, $orders[0]->books2);
644
        $this->assertCount(0, $orders[1]->books2);
645
        $this->assertCount(1, $orders[2]->books2);
646
        $this->assertEquals(1, $orders[0]->id);
647
        $this->assertEquals(2, $orders[1]->id);
648
        $this->assertEquals(3, $orders[2]->id);
649
        $this->assertTrue($orders[0]->isRelationPopulated('books2'));
650
        $this->assertTrue($orders[1]->isRelationPopulated('books2'));
651
        $this->assertTrue($orders[2]->isRelationPopulated('books2'));
652
653
        /** join with count and query */
654
        $orderQuery = new ActiveQuery(Order::class, $this->db);
655
        $query = $orderQuery->joinWith('customer');
656
        $count = $query->count();
657
        $this->assertEquals(3, $count);
658
659
        $orders = $query->all();
660
        $this->assertCount(3, $orders);
661
662
        /** {@see https://github.com/yiisoft/yii2/issues/2880} */
663
        $orderQuery = new ActiveQuery(Order::class, $this->db);
664
        $query = $orderQuery->findOne(1);
665
        $customer = $query->getCustomer()->joinWith(
0 ignored issues
show
Bug introduced by
The method getCustomer() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. It seems like you code against a sub-type of Yiisoft\ActiveRecord\ActiveRecordInterface such as Yiisoft\ActiveRecord\Tes...tubs\ActiveRecord\Order. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

665
        $customer = $query->/** @scrutinizer ignore-call */ getCustomer()->joinWith(
Loading history...
666
            [
667
                'orders' => static function ($q) {
668
                    $q->orderBy([]);
669
                },
670
            ]
671
        )->onePopulate();
672
        $this->assertEquals(1, $customer->id);
673
674
        $orderQuery = new ActiveQuery(Order::class, $this->db);
675
        $order = $orderQuery->joinWith(
0 ignored issues
show
Unused Code introduced by
The assignment to $order is dead and can be removed.
Loading history...
676
            [
677
                'items' => static function ($q) {
678
                    $q->from(['items' => 'item'])->orderBy('items.id');
679
                },
680
            ]
681
        )->orderBy('order.id')->one();
682
683
        /** join with sub-relation called inside Closure */
684
        $orderQuery = new ActiveQuery(Order::class, $this->db);
685
        $orders = $orderQuery->joinWith(
686
            [
687
                'items' => static function ($q) {
688
                    $q->orderBy('item.id');
689
                    $q->joinWith([
690
                        'category' => static function ($q) {
691
                            $q->where('{{category}}.[[id]] = 2');
692
                        },
693
                    ]);
694
                },
695
            ]
696
        )->orderBy('order.id')->all();
697
        $this->assertCount(1, $orders);
698
        $this->assertCount(3, $orders[0]->items);
699
        $this->assertEquals(2, $orders[0]->id);
700
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
701
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
702
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
703
    }
704
705
    /**
706
     * @depends testJoinWith
707
     */
708
    public function testJoinWithAndScope(): void
709
    {
710
        $this->checkFixture($this->db, 'customer');
711
712
        /**  hasOne inner join */
713
        $customer = new CustomerQuery(Customer::class, $this->db);
714
        $customers = $customer->active()->innerJoinWith('profile')->orderBy('customer.id')->all();
715
        $this->assertCount(1, $customers);
716
        $this->assertEquals(1, $customers[0]->id);
717
        $this->assertTrue($customers[0]->isRelationPopulated('profile'));
718
719
        /** hasOne outer join */
720
        $customer = new CustomerQuery(Customer::class, $this->db);
721
        $customers = $customer->active()->joinWith('profile')->orderBy('customer.id')->all();
722
        $this->assertCount(2, $customers);
723
        $this->assertEquals(1, $customers[0]->id);
724
        $this->assertEquals(2, $customers[1]->id);
725
        $this->assertInstanceOf(Profile::class, $customers[0]->profile);
726
        $this->assertNull($customers[1]->profile);
727
        $this->assertTrue($customers[0]->isRelationPopulated('profile'));
728
        $this->assertTrue($customers[1]->isRelationPopulated('profile'));
729
730
        /** hasMany */
731
        $customer = new CustomerQuery(Customer::class, $this->db);
732
        $customers = $customer->active()->joinWith(
733
            [
734
                'orders' => static function ($q) {
735
                    $q->orderBy('order.id');
736
                },
737
            ]
738
        )->orderBy('customer.id DESC, order.id')->all();
739
        $this->assertCount(2, $customers);
740
        $this->assertEquals(2, $customers[0]->id);
741
        $this->assertEquals(1, $customers[1]->id);
742
        $this->assertTrue($customers[0]->isRelationPopulated('orders'));
743
        $this->assertTrue($customers[1]->isRelationPopulated('orders'));
744
    }
745
746
    /**
747
     * @depends testJoinWith
748
     *
749
     * This query will do the same join twice, ensure duplicated JOIN gets removed.
750
     *
751
     * {@see https://github.com/yiisoft/yii2/pull/2650}
752
     */
753
    public function testJoinWithVia(): void
754
    {
755
        $this->checkFixture($this->db, 'order');
756
757
        $orderQuery = new ActiveQuery(Order::class, $this->db);
758
759
        $this->db->getQueryBuilder()->setSeparator("\n");
760
761
        $rows = $orderQuery->joinWith('itemsInOrder1')->joinWith(
762
            [
763
                'items' => static function ($q) {
764
                    $q->orderBy('item.id');
765
                },
766
            ]
767
        )->all();
768
        $this->assertNotEmpty($rows);
769
    }
770
771
    public static function aliasMethodProvider(): array
772
    {
773
        return [
774
            ['explicit'],
775
        ];
776
    }
777
778
    /**
779
     * @depends testJoinWith
780
     *
781
     * Tests the alias syntax for joinWith: 'alias' => 'relation'.
782
     *
783
     * @dataProvider aliasMethodProvider
784
     *
785
     * @param string $aliasMethod whether alias is specified explicitly or using the query syntax {{@tablename}}
786
     *
787
     * @throws Exception|InvalidConfigException|Throwable
788
     */
789
    public function testJoinWithAlias(string $aliasMethod): void
790
    {
791
        $orders = [];
792
        $this->checkFixture($this->db, 'order');
793
794
        /** left join and eager loading */
795
        $orderQuery = new ActiveQuery(Order::class, $this->db);
796
        $query = $orderQuery->joinWith(['customer c']);
797
798
        if ($aliasMethod === 'explicit') {
799
            $orders = $query->orderBy('c.id DESC, order.id')->all();
800
        } elseif ($aliasMethod === 'querysyntax') {
801
            $orders = $query->orderBy('{{@customer}}.id DESC, {{@order}}.id')->all();
802
        } elseif ($aliasMethod === 'applyAlias') {
803
            $orders = $query->orderBy(
804
                $query->applyAlias('customer', 'id') . ' DESC,' . $query->applyAlias('order', 'id')
0 ignored issues
show
Bug introduced by
The method applyAlias() does not exist on Yiisoft\ActiveRecord\ActiveQuery. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

804
                $query->/** @scrutinizer ignore-call */ 
805
                        applyAlias('customer', 'id') . ' DESC,' . $query->applyAlias('order', 'id')

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
805
            )->all();
806
        }
807
808
        $this->assertCount(3, $orders);
809
        $this->assertEquals(2, $orders[0]->id);
810
        $this->assertEquals(3, $orders[1]->id);
811
        $this->assertEquals(1, $orders[2]->id);
812
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
813
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
814
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
815
816
        /** inner join filtering and eager loading */
817
        $orderQuery = new ActiveQuery(Order::class, $this->db);
818
        $query = $orderQuery->innerJoinWith(['customer c']);
819
820
        if ($aliasMethod === 'explicit') {
821
            $orders = $query->where('{{c}}.[[id]]=2')->orderBy('order.id')->all();
822
        } elseif ($aliasMethod === 'querysyntax') {
823
            $orders = $query->where('{{@customer}}.[[id]]=2')->orderBy('{{@order}}.id')->all();
824
        } elseif ($aliasMethod === 'applyAlias') {
825
            $orders = $query->where(
826
                [$query->applyAlias('customer', 'id') => 2]
827
            )->orderBy($query->applyAlias('order', 'id'))->all();
828
        }
829
830
        $this->assertCount(2, $orders);
831
        $this->assertEquals(2, $orders[0]->id);
832
        $this->assertEquals(3, $orders[1]->id);
833
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
834
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
835
836
        /** inner join filtering without eager loading */
837
        $orderQuery = new ActiveQuery(Order::class, $this->db);
838
        $query = $orderQuery->innerJoinWith(['customer c'], false);
839
840
        if ($aliasMethod === 'explicit') {
841
            $orders = $query->where('{{c}}.[[id]]=2')->orderBy('order.id')->all();
842
        } elseif ($aliasMethod === 'querysyntax') {
843
            $orders = $query->where('{{@customer}}.[[id]]=2')->orderBy('{{@order}}.id')->all();
844
        } elseif ($aliasMethod === 'applyAlias') {
845
            $orders = $query->where(
846
                [$query->applyAlias('customer', 'id') => 2]
847
            )->orderBy($query->applyAlias('order', 'id'))->all();
848
        }
849
850
        $this->assertCount(2, $orders);
851
        $this->assertEquals(2, $orders[0]->id);
852
        $this->assertEquals(3, $orders[1]->id);
853
        $this->assertFalse($orders[0]->isRelationPopulated('customer'));
854
        $this->assertFalse($orders[1]->isRelationPopulated('customer'));
855
856
        /** join with via-relation */
857
        $orderQuery = new ActiveQuery(Order::class, $this->db);
858
        $query = $orderQuery->innerJoinWith(['books b']);
859
860
        if ($aliasMethod === 'explicit') {
861
            $orders = $query->where(
862
                ['b.name' => 'Yii 1.1 Application Development Cookbook']
863
            )->orderBy('order.id')->all();
864
        } elseif ($aliasMethod === 'querysyntax') {
865
            $orders = $query->where(
866
                ['{{@item}}.name' => 'Yii 1.1 Application Development Cookbook']
867
            )->orderBy('{{@order}}.id')->all();
868
        } elseif ($aliasMethod === 'applyAlias') {
869
            $orders = $query->where(
870
                [$query->applyAlias('book', 'name') => 'Yii 1.1 Application Development Cookbook']
871
            )->orderBy($query->applyAlias('order', 'id'))->all();
872
        }
873
874
        $this->assertCount(2, $orders);
875
        $this->assertCount(2, $orders[0]->books);
876
        $this->assertCount(1, $orders[1]->books);
877
        $this->assertEquals(1, $orders[0]->id);
878
        $this->assertEquals(3, $orders[1]->id);
879
        $this->assertTrue($orders[0]->isRelationPopulated('books'));
880
        $this->assertTrue($orders[1]->isRelationPopulated('books'));
881
882
        /** joining sub relations */
883
        $orderQuery = new ActiveQuery(Order::class, $this->db);
884
        $query = $orderQuery->innerJoinWith(
885
            [
886
                'items i' => static function ($q) use ($aliasMethod) {
887
                    /** @var $q ActiveQuery */
888
                    if ($aliasMethod === 'explicit') {
889
                        $q->orderBy('{{i}}.id');
890
                    } elseif ($aliasMethod === 'querysyntax') {
891
                        $q->orderBy('{{@item}}.id');
892
                    } elseif ($aliasMethod === 'applyAlias') {
893
                        $q->orderBy($q->applyAlias('item', 'id'));
894
                    }
895
                },
896
                'items.category c' => static function ($q) use ($aliasMethod) {
897
                    /**  @var $q ActiveQuery */
898
                    if ($aliasMethod === 'explicit') {
899
                        $q->where('{{c}}.[[id]] = 2');
900
                    } elseif ($aliasMethod === 'querysyntax') {
901
                        $q->where('{{@category}}.[[id]] = 2');
902
                    } elseif ($aliasMethod === 'applyAlias') {
903
                        $q->where([$q->applyAlias('category', 'id') => 2]);
904
                    }
905
                },
906
            ]
907
        );
908
909
        if ($aliasMethod === 'explicit') {
910
            $orders = $query->orderBy('{{i}}.id')->all();
911
        } elseif ($aliasMethod === 'querysyntax') {
912
            $orders = $query->orderBy('{{@item}}.id')->all();
913
        } elseif ($aliasMethod === 'applyAlias') {
914
            $orders = $query->orderBy($query->applyAlias('item', 'id'))->all();
915
        }
916
917
        $this->assertCount(1, $orders);
918
        $this->assertCount(3, $orders[0]->items);
919
        $this->assertEquals(2, $orders[0]->id);
920
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
921
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
922
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
923
924
        /** join with ON condition */
925
        if ($aliasMethod === 'explicit' || $aliasMethod === 'querysyntax') {
926
            $relationName = 'books' . ucfirst($aliasMethod);
927
928
            $orderQuery = new ActiveQuery(Order::class, $this->db);
929
            $orders = $orderQuery->joinWith(["$relationName b"])->orderBy('order.id')->all();
930
931
            $this->assertCount(3, $orders);
932
            $this->assertCount(2, $orders[0]->$relationName);
933
            $this->assertCount(0, $orders[1]->$relationName);
934
            $this->assertCount(1, $orders[2]->$relationName);
935
            $this->assertEquals(1, $orders[0]->id);
936
            $this->assertEquals(2, $orders[1]->id);
937
            $this->assertEquals(3, $orders[2]->id);
938
            $this->assertTrue($orders[0]->isRelationPopulated($relationName));
939
            $this->assertTrue($orders[1]->isRelationPopulated($relationName));
940
            $this->assertTrue($orders[2]->isRelationPopulated($relationName));
941
        }
942
943
        /** join with ON condition and alias in relation definition */
944
        if ($aliasMethod === 'explicit' || $aliasMethod === 'querysyntax') {
945
            $relationName = 'books' . ucfirst($aliasMethod) . 'A';
946
947
            $orderQuery = new ActiveQuery(Order::class, $this->db);
948
            $orders = $orderQuery->joinWith([(string)$relationName])->orderBy('order.id')->all();
949
950
            $this->assertCount(3, $orders);
951
            $this->assertCount(2, $orders[0]->$relationName);
952
            $this->assertCount(0, $orders[1]->$relationName);
953
            $this->assertCount(1, $orders[2]->$relationName);
954
            $this->assertEquals(1, $orders[0]->id);
955
            $this->assertEquals(2, $orders[1]->id);
956
            $this->assertEquals(3, $orders[2]->id);
957
            $this->assertTrue($orders[0]->isRelationPopulated($relationName));
958
            $this->assertTrue($orders[1]->isRelationPopulated($relationName));
959
            $this->assertTrue($orders[2]->isRelationPopulated($relationName));
960
        }
961
962
        /** join with count and query */
963
        $orderQuery = new ActiveQuery(Order::class, $this->db);
964
        $query = $orderQuery->joinWith(['customer c']);
965
966
        if ($aliasMethod === 'explicit') {
967
            $count = $query->count('c.id');
968
        } elseif ($aliasMethod === 'querysyntax') {
969
            $count = $query->count('{{@customer}}.id');
970
        } elseif ($aliasMethod === 'applyAlias') {
971
            $count = $query->count($query->applyAlias('customer', 'id'));
972
        }
973
974
        $this->assertEquals(3, $count);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $count does not seem to be defined for all execution paths leading up to this point.
Loading history...
975
976
        $orders = $query->all();
977
        $this->assertCount(3, $orders);
978
979
        /** relational query */
980
        $orderQuery = new ActiveQuery(Order::class, $this->db);
981
        $order = $orderQuery->findOne(1);
982
983
        $customerQuery = $order->getCustomer()->innerJoinWith(['orders o'], false);
984
985
        if ($aliasMethod === 'explicit') {
986
            $customer = $customerQuery->where(['o.id' => 1])->onePopulate();
987
        } elseif ($aliasMethod === 'querysyntax') {
988
            $customer = $customerQuery->where(['{{@order}}.id' => 1])->onePopulate();
989
        } elseif ($aliasMethod === 'applyAlias') {
990
            $customer = $customerQuery->where([$query->applyAlias('order', 'id') => 1])->onePopulate();
991
        }
992
993
        $this->assertEquals(1, $customer->id);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $customer does not seem to be defined for all execution paths leading up to this point.
Loading history...
994
        $this->assertNotNull($customer);
995
996
        /** join with sub-relation called inside Closure */
997
        $orderQuery = new ActiveQuery(Order::class, $this->db);
998
        $orders = $orderQuery->joinWith(
999
            [
1000
                'items' => static function ($q) use ($aliasMethod) {
1001
                    /** @var $q ActiveQuery */
1002
                    $q->orderBy('item.id');
1003
                    $q->joinWith(['category c']);
1004
1005
                    if ($aliasMethod === 'explicit') {
1006
                        $q->where('{{c}}.[[id]] = 2');
1007
                    } elseif ($aliasMethod === 'querysyntax') {
1008
                        $q->where('{{@category}}.[[id]] = 2');
1009
                    } elseif ($aliasMethod === 'applyAlias') {
1010
                        $q->where([$q->applyAlias('category', 'id') => 2]);
1011
                    }
1012
                },
1013
            ]
1014
        )->orderBy('order.id')->all();
1015
1016
        $this->assertCount(1, $orders);
1017
        $this->assertCount(3, $orders[0]->items);
1018
        $this->assertEquals(2, $orders[0]->id);
1019
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
1020
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
1021
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
1022
    }
1023
1024
    /**
1025
     * @depends testJoinWith
1026
     */
1027
    public function testJoinWithSameTable(): void
1028
    {
1029
        $this->checkFixture($this->db, 'order');
1030
1031
        /**
1032
         * join with the same table but different aliases alias is defined in the relation definition without eager
1033
         * loading
1034
         */
1035
        $query = new ActiveQuery(Order::class, $this->db);
1036
        $query->joinWith('bookItems', false)->joinWith('movieItems', false)->where(['movies.name' => 'Toy Story']);
1037
        $orders = $query->all();
1038
        $this->assertCount(
1039
            1,
1040
            $orders,
1041
            $query->createCommand()->getRawSql() . print_r($orders, true)
0 ignored issues
show
Bug introduced by
Are you sure print_r($orders, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1041
            $query->createCommand()->getRawSql() . /** @scrutinizer ignore-type */ print_r($orders, true)
Loading history...
1042
        );
1043
        $this->assertEquals(2, $orders[0]->id);
1044
        $this->assertFalse($orders[0]->isRelationPopulated('bookItems'));
1045
        $this->assertFalse($orders[0]->isRelationPopulated('movieItems'));
1046
1047
        /** with eager loading */
1048
        $query = new ActiveQuery(Order::class, $this->db);
1049
        $query->joinWith('bookItems', true)->joinWith('movieItems', true)->where(['movies.name' => 'Toy Story']);
1050
        $orders = $query->all();
1051
        $this->assertCount(
1052
            1,
1053
            $orders,
1054
            $query->createCommand()->getRawSql() . print_r($orders, true)
1055
        );
1056
        $this->assertCount(0, $orders[0]->bookItems);
1057
        $this->assertCount(3, $orders[0]->movieItems);
1058
        $this->assertEquals(2, $orders[0]->id);
1059
        $this->assertTrue($orders[0]->isRelationPopulated('bookItems'));
1060
        $this->assertTrue($orders[0]->isRelationPopulated('movieItems'));
1061
1062
        /**
1063
         * join with the same table but different aliases alias is defined in the call to joinWith() without eager
1064
         * loading
1065
         */
1066
        $query = new ActiveQuery(Order::class, $this->db);
1067
        $query
1068
            ->joinWith(
1069
                [
1070
                    'itemsIndexed books' => static function ($q) {
1071
                        $q->onCondition('books.category_id = 1');
1072
                    },
1073
                ],
1074
                false
1075
            )->joinWith(
1076
                [
1077
                    'itemsIndexed movies' => static function ($q) {
1078
                        $q->onCondition('movies.category_id = 2');
1079
                    },
1080
                ],
1081
                false
1082
            )->where(['movies.name' => 'Toy Story']);
1083
        $orders = $query->all();
1084
        $this->assertCount(
1085
            1,
1086
            $orders,
1087
            $query->createCommand()->getRawSql() . print_r($orders, true)
1088
        );
1089
        $this->assertEquals(2, $orders[0]->id);
1090
        $this->assertFalse($orders[0]->isRelationPopulated('itemsIndexed'));
1091
1092
        /** with eager loading, only for one relation as it would be overwritten otherwise. */
1093
        $query = new ActiveQuery(Order::class, $this->db);
1094
        $query
1095
            ->joinWith(
1096
                [
1097
                    'itemsIndexed books' => static function ($q) {
1098
                        $q->onCondition('books.category_id = 1');
1099
                    },
1100
                ],
1101
                false
1102
            )
1103
            ->joinWith(
1104
                [
1105
                    'itemsIndexed movies' => static function ($q) {
1106
                        $q->onCondition('movies.category_id = 2');
1107
                    },
1108
                ],
1109
                true
1110
            )->where(['movies.name' => 'Toy Story']);
1111
        $orders = $query->all();
1112
        $this->assertCount(1, $orders, $query->createCommand()->getRawSql() . print_r($orders, true));
1113
        $this->assertCount(3, $orders[0]->itemsIndexed);
1114
        $this->assertEquals(2, $orders[0]->id);
1115
        $this->assertTrue($orders[0]->isRelationPopulated('itemsIndexed'));
1116
1117
        /** with eager loading, and the other relation */
1118
        $query = new ActiveQuery(Order::class, $this->db);
1119
        $query
1120
            ->joinWith(
1121
                [
1122
                    'itemsIndexed books' => static function ($q) {
1123
                        $q->onCondition('books.category_id = 1');
1124
                    },
1125
                ],
1126
                true
1127
            )
1128
            ->joinWith(
1129
                [
1130
                    'itemsIndexed movies' => static function ($q) {
1131
                        $q->onCondition('movies.category_id = 2');
1132
                    },
1133
                ],
1134
                false
1135
            )
1136
            ->where(['movies.name' => 'Toy Story']);
1137
        $orders = $query->all();
1138
        $this->assertCount(1, $orders, $query->createCommand()->getRawSql() . print_r($orders, true));
1139
        $this->assertCount(0, $orders[0]->itemsIndexed);
1140
        $this->assertEquals(2, $orders[0]->id);
1141
        $this->assertTrue($orders[0]->isRelationPopulated('itemsIndexed'));
1142
    }
1143
1144
    /**
1145
     * @depends testJoinWith
1146
     */
1147
    public function testJoinWithDuplicateSimple(): void
1148
    {
1149
        $this->checkFixture($this->db, 'order');
1150
1151
        /** left join and eager loading */
1152
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1153
1154
        $orders = $orderQuery
1155
            ->innerJoinWith('customer')
1156
            ->joinWith('customer')
1157
            ->orderBy('customer.id DESC, order.id')
1158
            ->all();
1159
1160
        $this->assertCount(3, $orders);
1161
        $this->assertEquals(2, $orders[0]->id);
1162
        $this->assertEquals(3, $orders[1]->id);
1163
        $this->assertEquals(1, $orders[2]->id);
1164
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
1165
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
1166
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
1167
    }
1168
1169
    /**
1170
     * @depends testJoinWith
1171
     */
1172
    public function testJoinWithDuplicateCallbackFiltering(): void
1173
    {
1174
        $this->checkFixture($this->db, 'order');
1175
1176
        /** inner join filtering and eager loading */
1177
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1178
1179
        $orders = $orderQuery
1180
            ->innerJoinWith('customer')
1181
            ->joinWith([
1182
                'customer' => function ($query) {
1183
                    $query->where('{{customer}}.[[id]]=2');
1184
                },
1185
            ])->orderBy('order.id')->all();
1186
1187
        $this->assertCount(2, $orders);
1188
        $this->assertEquals(2, $orders[0]->id);
1189
        $this->assertEquals(3, $orders[1]->id);
1190
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
1191
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
1192
    }
1193
1194
    /**
1195
     * @depends testJoinWith
1196
     */
1197
    public function testJoinWithDuplicateCallbackFilteringConditionsOnPrimary(): void
1198
    {
1199
        $this->checkFixture($this->db, 'order');
1200
1201
        /** inner join filtering, eager loading, conditions on both primary and relation */
1202
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1203
1204
        $orders = $orderQuery
1205
            ->innerJoinWith('customer')
1206
            ->joinWith([
1207
                'customer' => function ($query) {
1208
                    $query->where(['{{customer}}.[[id]]' => 2]);
1209
                },
1210
            ])->where(['order.id' => [1, 2]])->orderBy('order.id')->all();
1211
1212
        $this->assertCount(1, $orders);
1213
        $this->assertEquals(2, $orders[0]->id);
1214
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
1215
    }
1216
1217
    /**
1218
     * @depends testJoinWith
1219
     */
1220
    public function testJoinWithDuplicateWithSubRelation(): void
1221
    {
1222
        $this->checkFixture($this->db, 'order');
1223
1224
        /** join with sub-relation */
1225
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1226
1227
        $orders = $orderQuery
1228
            ->innerJoinWith('items')
1229
            ->joinWith([
1230
                'items.category' => function ($q) {
1231
                    $q->where('{{category}}.[[id]] = 2');
1232
                },
1233
            ])->orderBy('order.id')->all();
1234
1235
        $this->assertCount(1, $orders);
1236
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
1237
        $this->assertEquals(2, $orders[0]->id);
1238
        $this->assertCount(3, $orders[0]->items);
1239
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
1240
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
1241
    }
1242
1243
    /**
1244
     * @depends testJoinWith
1245
     */
1246
    public function testJoinWithDuplicateTableAlias1(): void
1247
    {
1248
        $this->checkFixture($this->db, 'order');
1249
1250
        /** join with table alias */
1251
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1252
1253
        $orders = $orderQuery
1254
            ->innerJoinWith('customer')
1255
            ->joinWith([
1256
                'customer' => function ($q) {
1257
                    $q->from('customer c');
1258
                },
1259
            ])->orderBy('c.id DESC, order.id')->all();
1260
1261
        $this->assertCount(3, $orders);
1262
        $this->assertEquals(2, $orders[0]->id);
1263
        $this->assertEquals(3, $orders[1]->id);
1264
        $this->assertEquals(1, $orders[2]->id);
1265
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
1266
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
1267
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
1268
    }
1269
1270
    /**
1271
     * @depends testJoinWith
1272
     */
1273
    public function testJoinWithDuplicateTableAlias2(): void
1274
    {
1275
        $this->checkFixture($this->db, 'order');
1276
1277
        /** join with table alias */
1278
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1279
1280
        $orders = $orderQuery
1281
            ->innerJoinWith('customer')
1282
            ->joinWith('customer as c')
1283
            ->orderBy('c.id DESC, order.id')
1284
            ->all();
1285
1286
        $this->assertCount(3, $orders);
1287
        $this->assertEquals(2, $orders[0]->id);
1288
        $this->assertEquals(3, $orders[1]->id);
1289
        $this->assertEquals(1, $orders[2]->id);
1290
        $this->assertTrue($orders[0]->isRelationPopulated('customer'));
1291
        $this->assertTrue($orders[1]->isRelationPopulated('customer'));
1292
        $this->assertTrue($orders[2]->isRelationPopulated('customer'));
1293
    }
1294
1295
    /**
1296
     * @depends testJoinWith
1297
     */
1298
    public function testJoinWithDuplicateTableAliasSubRelation(): void
1299
    {
1300
        $this->checkFixture($this->db, 'order');
1301
1302
        /** join with table alias sub-relation */
1303
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1304
1305
        $orders = $orderQuery
1306
            ->innerJoinWith([
1307
                'items as t' => function ($q) {
1308
                    $q->orderBy('t.id');
1309
                },
1310
            ])
1311
            ->joinWith([
1312
                'items.category as c' => function ($q) {
1313
                    $q->where('{{c}}.[[id]] = 2');
1314
                },
1315
            ])->orderBy('order.id')->all();
1316
1317
        $this->assertCount(1, $orders);
1318
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
1319
        $this->assertEquals(2, $orders[0]->id);
1320
        $this->assertCount(3, $orders[0]->items);
1321
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
1322
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
1323
    }
1324
1325
    /**
1326
     * @depends testJoinWith
1327
     */
1328
    public function testJoinWithDuplicateSubRelationCalledInsideClosure(): void
1329
    {
1330
        $this->checkFixture($this->db, 'order');
1331
1332
        /** join with sub-relation called inside Closure */
1333
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1334
1335
        $orders = $orderQuery
1336
            ->innerJoinWith('items')
1337
            ->joinWith([
1338
                'items' => function ($q) {
1339
                    $q->orderBy('item.id');
1340
                    $q->joinWith([
1341
                        'category' => function ($q) {
1342
                            $q->where('{{category}}.[[id]] = 2');
1343
                        },
1344
                    ]);
1345
                },
1346
            ])
1347
            ->orderBy('order.id')
1348
            ->all();
1349
1350
        $this->assertCount(1, $orders);
1351
        $this->assertTrue($orders[0]->isRelationPopulated('items'));
1352
        $this->assertEquals(2, $orders[0]->id);
1353
        $this->assertCount(3, $orders[0]->items);
1354
        $this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
1355
        $this->assertEquals(2, $orders[0]->items[0]->category->id);
1356
    }
1357
1358
    public function testAlias(): void
1359
    {
1360
        $this->checkFixture($this->db, 'order');
1361
1362
        $order = new Order($this->db);
0 ignored issues
show
Unused Code introduced by
The assignment to $order is dead and can be removed.
Loading history...
1363
1364
        $query = new ActiveQuery(Order::class, $this->db);
1365
        $this->assertSame([], $query->getFrom());
1366
1367
        $query->alias('o');
1368
        $this->assertEquals(['o' => Order::TABLE_NAME], $query->getFrom());
1369
1370
        $query->alias('o')->alias('ord');
1371
        $this->assertEquals(['ord' => Order::TABLE_NAME], $query->getFrom());
1372
1373
        $query->from(['users', 'o' => Order::TABLE_NAME])->alias('ord');
1374
        $this->assertEquals(['users', 'ord' => Order::TABLE_NAME], $query->getFrom());
1375
    }
1376
1377
    public function testInverseOf(): void
1378
    {
1379
        $this->checkFixture($this->db, 'customer');
1380
1381
        /** eager loading: find one and all */
1382
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1383
        $customer = $customerQuery->with('orders2')->where(['id' => 1])->onePopulate();
1384
        $this->assertSame($customer->orders2[0]->customer2, $customer);
0 ignored issues
show
Bug introduced by
Accessing orders2 on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1385
1386
        //$customerQuery = new ActiveQuery(Customer::class, $this->db);
1387
        $customers = $customerQuery->with('orders2')->where(['id' => [1, 3]])->all();
1388
        $this->assertEmpty($customers[1]->orders2);
1389
        $this->assertSame($customers[0]->orders2[0]->customer2, $customers[0]);
1390
1391
        /** lazy loading */
1392
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1393
        $customer = $customerQuery->findOne(2);
1394
        $orders = $customer->orders2;
1395
        $this->assertCount(2, $orders);
1396
        $this->assertSame($customer->orders2[0]->customer2, $customer);
1397
        $this->assertSame($customer->orders2[1]->customer2, $customer);
1398
1399
        /** ad-hoc lazy loading */
1400
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1401
        $customer = $customerQuery->findOne(2);
1402
        $orders = $customer->getOrders2()->all();
0 ignored issues
show
Bug introduced by
The method getOrders2() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. It seems like you code against a sub-type of Yiisoft\ActiveRecord\ActiveRecordInterface such as Yiisoft\ActiveRecord\Tes...s\ActiveRecord\Customer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1402
        $orders = $customer->/** @scrutinizer ignore-call */ getOrders2()->all();
Loading history...
1403
        $this->assertCount(2, $orders);
1404
        $this->assertSame($orders[0]->customer2, $customer);
1405
        $this->assertSame($orders[1]->customer2, $customer);
1406
        $this->assertTrue(
1407
            $orders[0]->isRelationPopulated('customer2'),
1408
            'inverse relation did not populate the relation'
1409
        );
1410
        $this->assertTrue(
1411
            $orders[1]->isRelationPopulated('customer2'),
1412
            'inverse relation did not populate the relation'
1413
        );
1414
1415
        /** the other way around */
1416
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1417
        $customer = $customerQuery->with('orders2')->where(['id' => 1])->asArray()->onePopulate();
1418
        $this->assertSame($customer['orders2'][0]['customer2']['id'], $customer['id']);
1419
1420
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1421
        $customers = $customerQuery->with('orders2')->where(['id' => [1, 3]])->asArray()->all();
1422
        $this->assertSame($customer['orders2'][0]['customer2']['id'], $customers[0]['id']);
1423
        $this->assertEmpty($customers[1]['orders2']);
1424
1425
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1426
        $orders = $orderQuery->with('customer2')->where(['id' => 1])->all();
1427
        $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]);
1428
1429
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1430
        $order = $orderQuery->with('customer2')->where(['id' => 1])->onePopulate();
1431
        $this->assertSame($order->customer2->orders2, [$order]);
0 ignored issues
show
Bug introduced by
Accessing customer2 on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1432
1433
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1434
        $orders = $orderQuery->with('customer2')->where(['id' => 1])->asArray()->all();
1435
        $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1436
1437
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1438
        $order = $orderQuery->with('customer2')->where(['id' => 1])->asArray()->onePopulate();
1439
        $this->assertSame($order['customer2']['orders2'][0]['id'], $orders[0]['id']);
1440
1441
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1442
        $orders = $orderQuery->with('customer2')->where(['id' => [1, 3]])->all();
1443
        $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]);
1444
        $this->assertSame($orders[1]->customer2->orders2, [$orders[1]]);
1445
1446
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1447
        $orders = $orderQuery->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->all();
1448
        $this->assertSame($orders[0]->customer2->orders2, $orders);
1449
        $this->assertSame($orders[1]->customer2->orders2, $orders);
1450
1451
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1452
        $orders = $orderQuery->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->asArray()->all();
1453
        $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1454
        $this->assertSame($orders[0]['customer2']['orders2'][1]['id'], $orders[1]['id']);
1455
        $this->assertSame($orders[1]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1456
        $this->assertSame($orders[1]['customer2']['orders2'][1]['id'], $orders[1]['id']);
1457
    }
1458
1459
    public function testUnlinkAllViaTable(): void
1460
    {
1461
        $this->checkFixture($this->db, 'order', true);
1462
1463
        /** via table with delete. */
1464
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1465
        $order = $orderQuery->findOne(1);
1466
        $this->assertCount(2, $order->booksViaTable);
0 ignored issues
show
Bug introduced by
Accessing booksViaTable on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1467
1468
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
1469
        $orderItemCount = $orderItemQuery->count();
1470
1471
        $itemQuery = new ActiveQuery(Item::class, $this->db);
1472
        $this->assertEquals(5, $itemQuery->count());
1473
1474
        $order->unlinkAll('booksViaTable', true);
0 ignored issues
show
Bug introduced by
The method unlinkAll() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Did you maybe mean unlink()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1474
        $order->/** @scrutinizer ignore-call */ 
1475
                unlinkAll('booksViaTable', true);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1475
        $this->assertEquals(5, $itemQuery->count());
1476
        $this->assertEquals($orderItemCount - 2, $orderItemQuery->count());
1477
        $this->assertCount(0, $order->booksViaTable);
1478
1479
        /** via table without delete */
1480
        $this->assertCount(2, $order->booksWithNullFKViaTable);
0 ignored issues
show
Bug introduced by
Accessing booksWithNullFKViaTable on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1481
1482
        $orderItemsWithNullFKQuery = new ActiveQuery(OrderItemWithNullFK::class, $this->db);
1483
        $orderItemCount = $orderItemsWithNullFKQuery->count();
1484
        $this->assertEquals(5, $itemQuery->count());
1485
1486
        $order->unlinkAll('booksWithNullFKViaTable', false);
1487
        $this->assertCount(0, $order->booksWithNullFKViaTable);
1488
        $this->assertEquals(2, $orderItemsWithNullFKQuery->where(
1489
            ['AND', ['item_id' => [1, 2]], ['order_id' => null]]
1490
        )->count());
1491
1492
        $orderItemsWithNullFKQuery = new ActiveQuery(OrderItemWithNullFK::class, $this->db);
1493
        $this->assertEquals($orderItemCount, $orderItemsWithNullFKQuery->count());
1494
        $this->assertEquals(5, $itemQuery->count());
1495
    }
1496
1497
    public function testIssues(): void
1498
    {
1499
        $this->checkFixture($this->db, 'category', true);
1500
1501
        /** {@see https://github.com/yiisoft/yii2/issues/4938} */
1502
        $categoryQuery = new ActiveQuery(Category::class, $this->db);
1503
        $category = $categoryQuery->findOne(2);
1504
        $this->assertInstanceOf(Category::class, $category);
1505
        $this->assertEquals(3, $category->getItems()->count());
1506
        $this->assertEquals(1, $category->getLimitedItems()->count());
1507
        $this->assertEquals(1, $category->getLimitedItems()->distinct(true)->count());
1508
1509
        /** {@see https://github.com/yiisoft/yii2/issues/3197} */
1510
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1511
        $orders = $orderQuery->with('orderItems')->orderBy('id')->all();
1512
        $this->assertCount(3, $orders);
1513
        $this->assertCount(2, $orders[0]->orderItems);
1514
        $this->assertCount(3, $orders[1]->orderItems);
1515
        $this->assertCount(1, $orders[2]->orderItems);
1516
1517
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1518
        $orders = $orderQuery->with(
1519
            [
1520
                'orderItems' => static function ($q) {
1521
                    $q->indexBy('item_id');
1522
                },
1523
            ]
1524
        )->orderBy('id')->all();
1525
        $this->assertCount(3, $orders);
1526
        $this->assertCount(2, $orders[0]->orderItems);
1527
        $this->assertCount(3, $orders[1]->orderItems);
1528
        $this->assertCount(1, $orders[2]->orderItems);
1529
1530
        /** {@see https://github.com/yiisoft/yii2/issues/8149} */
1531
        $arClass = new Customer($this->db);
1532
1533
        $arClass->name = 'test';
1534
        $arClass->email = 'test';
1535
        $arClass->save();
1536
1537
        $arClass->updateCounters(['status' => 1]);
1538
        $this->assertEquals(1, $arClass->status);
1539
    }
1540
1541
    public function testPopulateWithoutPk(): void
1542
    {
1543
        $this->checkFixture($this->db, 'customer', true);
1544
1545
        /** tests with single pk asArray */
1546
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1547
        $aggregation = $customerQuery
1548
            ->select(['{{customer}}.[[status]]', 'SUM({{order}}.[[total]]) AS [[sumtotal]]'])
1549
            ->joinWith('ordersPlain', false)
1550
            ->groupBy('{{customer}}.[[status]]')
1551
            ->orderBy('status')
1552
            ->asArray()
1553
            ->all();
1554
1555
        $expected = [
1556
            [
1557
                'status' => 1,
1558
                'sumtotal' => 183,
1559
            ],
1560
            [
1561
                'status' => 2,
1562
                'sumtotal' => 0,
1563
            ],
1564
        ];
1565
1566
        $this->assertEquals($expected, $aggregation);
1567
1568
        // tests with single pk asArray with eager loading
1569
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1570
        $aggregation = $customerQuery
1571
            ->select(['{{customer}}.[[status]]', 'SUM({{order}}.[[total]]) AS [[sumtotal]]'])
1572
            ->joinWith('ordersPlain')
1573
            ->groupBy('{{customer}}.[[status]]')
1574
            ->orderBy('status')
1575
            ->asArray()
1576
            ->all();
1577
1578
        $expected = [
1579
            [
1580
                'status' => 1,
1581
                'sumtotal' => 183,
1582
                'ordersPlain' => [],
1583
            ],
1584
            [
1585
                'status' => 2,
1586
                'sumtotal' => 0,
1587
                'ordersPlain' => [],
1588
            ],
1589
        ];
1590
        $this->assertEquals($expected, $aggregation);
1591
1592
        /** tests with single pk with Models */
1593
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1594
        $aggregation = $customerQuery
1595
            ->select(['{{customer}}.[[status]]', 'SUM({{order}}.[[total]]) AS [[sumTotal]]'])
1596
            ->joinWith('ordersPlain', false)
1597
            ->groupBy('{{customer}}.[[status]]')
1598
            ->orderBy('status')
1599
            ->all();
1600
1601
        $this->assertCount(2, $aggregation);
1602
        $this->assertContainsOnlyInstancesOf(Customer::class, $aggregation);
0 ignored issues
show
Bug introduced by
It seems like $aggregation can also be of type Countable; however, parameter $haystack of PHPUnit\Framework\Assert...ntainsOnlyInstancesOf() does only seem to accept iterable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1602
        $this->assertContainsOnlyInstancesOf(Customer::class, /** @scrutinizer ignore-type */ $aggregation);
Loading history...
1603
1604
        foreach ($aggregation as $item) {
1605
            if ($item->status === 1) {
1606
                $this->assertEquals(183, $item->sumTotal);
1607
            } elseif ($item->status === 2) {
1608
                $this->assertEquals(0, $item->sumTotal);
1609
            }
1610
        }
1611
1612
        /** tests with composite pk asArray */
1613
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
1614
        $aggregation = $orderItemQuery
1615
            ->select(['[[order_id]]', 'SUM([[subtotal]]) AS [[subtotal]]'])
1616
            ->joinWith('order', false)
1617
            ->groupBy('[[order_id]]')
1618
            ->orderBy('[[order_id]]')
1619
            ->asArray()
1620
            ->all();
1621
1622
        $expected = [
1623
            [
1624
                'order_id' => 1,
1625
                'subtotal' => 70,
1626
            ],
1627
            [
1628
                'order_id' => 2,
1629
                'subtotal' => 33,
1630
            ],
1631
            [
1632
                'order_id' => 3,
1633
                'subtotal' => 40,
1634
            ],
1635
        ];
1636
        $this->assertEquals($expected, $aggregation);
1637
1638
        /** tests with composite pk with Models */
1639
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
1640
        $aggregation = $orderItemQuery
1641
            ->select(['[[order_id]]', 'SUM([[subtotal]]) AS [[subtotal]]'])
1642
            ->joinWith('order', false)
1643
            ->groupBy('[[order_id]]')
1644
            ->orderBy('[[order_id]]')
1645
            ->all();
1646
1647
        $this->assertCount(3, $aggregation);
1648
        $this->assertContainsOnlyInstancesOf(OrderItem::class, $aggregation);
1649
1650
        foreach ($aggregation as $item) {
1651
            if ($item->order_id === 1) {
1652
                $this->assertEquals(70, $item->subtotal);
1653
            } elseif ($item->order_id === 2) {
1654
                $this->assertEquals(33, $item->subtotal);
1655
            } elseif ($item->order_id === 3) {
1656
                $this->assertEquals(40, $item->subtotal);
1657
            }
1658
        }
1659
    }
1660
1661
    public function testLinkWhenRelationIsIndexed2(): void
1662
    {
1663
        $this->checkFixture($this->db, 'order');
1664
1665
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1666
        $order = $orderQuery->with('orderItems2')->where(['id' => 1])->onePopulate();
1667
1668
        $orderItem = new OrderItem($this->db);
1669
1670
        $orderItem->order_id = $order->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1671
        $orderItem->item_id = 3;
1672
        $orderItem->quantity = 1;
1673
        $orderItem->subtotal = 10.0;
1674
1675
        $order->link('orderItems2', $orderItem);
1676
        $this->assertTrue(isset($order->orderItems2['3']));
0 ignored issues
show
Bug introduced by
Accessing orderItems2 on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1677
    }
1678
1679
    public function testEmulateExecution(): void
1680
    {
1681
        $this->checkFixture($this->db, 'order');
1682
1683
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1684
1685
        $this->assertGreaterThan(0, $customerQuery->count());
1686
        $this->assertSame([], $customerQuery->emulateExecution()->all());
1687
        $this->assertNull($customerQuery->emulateExecution()->one());
1688
        $this->assertFalse($customerQuery->emulateExecution()->exists());
1689
        $this->assertSame(0, $customerQuery->emulateExecution()->count());
1690
        $this->assertNull($customerQuery->emulateExecution()->sum('id'));
1691
        $this->assertNull($customerQuery->emulateExecution()->average('id'));
1692
        $this->assertNull($customerQuery->emulateExecution()->max('id'));
1693
        $this->assertNull($customerQuery->emulateExecution()->min('id'));
1694
        $this->assertNull($customerQuery->select(['id'])->emulateExecution()->scalar());
1695
        $this->assertSame([], $customerQuery->select(['id'])->emulateExecution()->column());
1696
    }
1697
1698
    /**
1699
     * {@see https://github.com/yiisoft/yii2/issues/12213}
1700
     */
1701
    public function testUnlinkAllOnCondition(): void
1702
    {
1703
        $this->checkFixture($this->db, 'item');
1704
1705
        /** Ensure there are three items with category_id = 2 in the Items table */
1706
        $itemQuery = new ActiveQuery(Item::class, $this->db);
1707
        $itemsCount = $itemQuery->where(['category_id' => 2])->count();
1708
        $this->assertEquals(3, $itemsCount);
1709
1710
        $categoryQuery = new ActiveQuery(Category::class, $this->db);
1711
        $categoryQuery = $categoryQuery->with('limitedItems')->where(['id' => 2]);
1712
1713
        /**
1714
         * Ensure that limitedItems relation returns only one item (category_id = 2 and id in (1,2,3))
1715
         */
1716
        $category = $categoryQuery->onePopulate();
1717
        $this->assertCount(1, $category->limitedItems);
0 ignored issues
show
Bug introduced by
Accessing limitedItems on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1718
1719
        /** Unlink all items in the limitedItems relation */
1720
        $category->unlinkAll('limitedItems', true);
1721
1722
        /** Make sure that only one item was unlinked */
1723
        $itemsCount = $itemQuery->where(['category_id' => 2])->count();
1724
        $this->assertEquals(2, $itemsCount);
1725
1726
        /** Call $categoryQuery again to ensure no items were found */
1727
        $this->assertCount(0, $categoryQuery->onePopulate()->limitedItems);
1728
    }
1729
1730
    /**
1731
     * {@see https://github.com/yiisoft/yii2/issues/12213}
1732
     */
1733
    public function testUnlinkAllOnConditionViaTable(): void
1734
    {
1735
        $this->checkFixture($this->db, 'item', true);
1736
1737
        /** Ensure there are three items with category_id = 2 in the Items table */
1738
        $itemQuery = new ActiveQuery(Item::class, $this->db);
1739
        $itemsCount = $itemQuery->where(['category_id' => 2])->count();
1740
        $this->assertEquals(3, $itemsCount);
1741
1742
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1743
        $orderQuery = $orderQuery->with('limitedItems')->where(['id' => 2]);
1744
1745
        /**
1746
         * Ensure that limitedItems relation returns only one item (category_id = 2 and id in (4, 5)).
1747
         */
1748
        $category = $orderQuery->onePopulate();
1749
        $this->assertCount(2, $category->limitedItems);
0 ignored issues
show
Bug introduced by
Accessing limitedItems on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1750
1751
        /** Unlink all items in the limitedItems relation */
1752
        $category->unlinkAll('limitedItems', true);
1753
1754
        /** Call $orderQuery again to ensure that links are removed */
1755
        $this->assertCount(0, $orderQuery->onePopulate()->limitedItems);
1756
1757
        /** Make sure that only links were removed, the items were not removed */
1758
        $this->assertEquals(3, $itemQuery->where(['category_id' => 2])->count());
1759
    }
1760
1761
    /**
1762
     * {@see https://github.com/yiisoft/yii2/pull/13891}
1763
     */
1764
    public function testIndexByAfterLoadingRelations(): void
1765
    {
1766
        $this->checkFixture($this->db, 'order');
1767
1768
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1769
        $orderQuery->with('customer')->indexBy(function (Order $order) {
1770
            $this->assertTrue($order->isRelationPopulated('customer'));
1771
            $this->assertNotEmpty($order->customer->id);
0 ignored issues
show
Bug Best Practice introduced by
The property customer does not exist on Yiisoft\ActiveRecord\Tes...tubs\ActiveRecord\Order. Since you implemented __get, consider adding a @property annotation.
Loading history...
1772
1773
            return $order->customer->id;
1774
        })->all();
1775
1776
        $orders = $orderQuery->with('customer')->indexBy('customer.id')->all();
1777
1778
        foreach ($orders as $customer_id => $order) {
1779
            $this->assertEquals($customer_id, $order->customer_id);
1780
        }
1781
    }
1782
1783
    public static function filterTableNamesFromAliasesProvider(): array
1784
    {
1785
        return [
1786
            'table name as string' => ['customer', []],
1787
            'table name as array' => [['customer'], []],
1788
            'table names' => [['customer', 'order'], []],
1789
            'table name and a table alias' => [['customer', 'ord' => 'order'], ['ord']],
1790
            'table alias' => [['csr' => 'customer'], ['csr']],
1791
            'table aliases' => [['csr' => 'customer', 'ord' => 'order'], ['csr', 'ord']],
1792
        ];
1793
    }
1794
1795
    /**
1796
     * @dataProvider filterTableNamesFromAliasesProvider
1797
     *
1798
     * @param $expectedAliases
1799
     *
1800
     * @throws ReflectionException
1801
     */
1802
    public function testFilterTableNamesFromAliases(array|string $fromParams, array $expectedAliases): void
1803
    {
1804
        $this->checkFixture($this->db, 'customer');
1805
1806
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1807
1808
        $query = $customerQuery->from($fromParams);
1809
1810
        $aliases = Assert::invokeMethod(new Customer($this->db), 'filterValidAliases', [$query]);
1811
1812
        $this->assertEquals($expectedAliases, $aliases);
1813
    }
1814
1815
    public function testExtraFields(): void
1816
    {
1817
        $this->checkFixture($this->db, 'customer');
1818
1819
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1820
1821
        $query = $customerQuery->with('orders2')->where(['id' => 1])->onePopulate();
1822
        $this->assertCount(1, $query->getRelatedRecords());
0 ignored issues
show
Bug introduced by
The method getRelatedRecords() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\ActiveRecord\ActiveRecordInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1822
        $this->assertCount(1, $query->/** @scrutinizer ignore-call */ getRelatedRecords());
Loading history...
1823
        $this->assertCount(1, $query->extraFields());
0 ignored issues
show
Bug introduced by
The method extraFields() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\ActiveRecord\ActiveRecordInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1823
        $this->assertCount(1, $query->/** @scrutinizer ignore-call */ extraFields());
Loading history...
1824
        $this->assertArrayHasKey('orders2', $query->getRelatedRecords());
0 ignored issues
show
Bug introduced by
It seems like $query->getRelatedRecords() can also be of type Countable; however, parameter $array of PHPUnit\Framework\Assert::assertArrayHasKey() does only seem to accept ArrayAccess|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1824
        $this->assertArrayHasKey('orders2', /** @scrutinizer ignore-type */ $query->getRelatedRecords());
Loading history...
1825
        $this->assertContains('orders2', $query->extraFields());
0 ignored issues
show
Bug introduced by
It seems like $query->extraFields() can also be of type Countable; however, parameter $haystack of PHPUnit\Framework\Assert::assertContains() does only seem to accept iterable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1825
        $this->assertContains('orders2', /** @scrutinizer ignore-type */ $query->extraFields());
Loading history...
1826
    }
1827
1828
    public static function tableNameProvider(): array
1829
    {
1830
        return [
1831
            ['order', 'order_item'],
1832
            ['order', '{{%order_item}}'],
1833
            ['{{%order}}', 'order_item'],
1834
            ['{{%order}}', '{{%order_item}}'],
1835
        ];
1836
    }
1837
1838
    /**
1839
     * Test whether conditions are quoted correctly in conditions where joinWith is used.
1840
     *
1841
     * {@see https://github.com/yiisoft/yii2/issues/11088}
1842
     *
1843
     * @dataProvider tableNameProvider
1844
     *
1845
     * @throws Exception|InvalidConfigException
1846
     */
1847
    public function testRelationWhereParams(string $orderTableName, string $orderItemTableName): void
1848
    {
1849
        $driverName = $this->db->getDriverName();
1850
1851
        $this->checkFixture($this->db, 'order');
1852
1853
        $order = new Order(db: $this->db, tableName: $orderTableName);
0 ignored issues
show
Unused Code introduced by
The assignment to $order is dead and can be removed.
Loading history...
1854
        $orderItem = new OrderItem(db: $this->db, tableName: $orderItemTableName);
0 ignored issues
show
Unused Code introduced by
The assignment to $orderItem is dead and can be removed.
Loading history...
1855
1856
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1857
        $order = $orderQuery->findOne(1);
1858
        $itemsSQL = $order->getOrderitems()->createCommand()->getRawSql();
0 ignored issues
show
Bug introduced by
The method getOrderitems() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. It seems like you code against a sub-type of Yiisoft\ActiveRecord\ActiveRecordInterface such as Yiisoft\ActiveRecord\Tes...s\ActiveRecord\Customer or Yiisoft\ActiveRecord\Tes...tubs\ActiveRecord\Order or Yiisoft\ActiveRecord\Tes...s\ActiveRecord\Category. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1858
        $itemsSQL = $order->/** @scrutinizer ignore-call */ getOrderitems()->createCommand()->getRawSql();
Loading history...
1859
        $expectedSQL = DbHelper::replaceQuotes(
1860
            <<<SQL
1861
            SELECT * FROM [[order_item]] WHERE [[order_id]]=1
1862
            SQL,
1863
            $driverName,
1864
        );
1865
        $this->assertEquals($expectedSQL, $itemsSQL);
1866
1867
        $order = $orderQuery->findOne(1);
1868
        $itemsSQL = $order->getOrderItems()->joinWith('item')->createCommand()->getRawSql();
0 ignored issues
show
Bug introduced by
The method getOrderItems() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. It seems like you code against a sub-type of Yiisoft\ActiveRecord\ActiveRecordInterface such as Yiisoft\ActiveRecord\Tes...s\ActiveRecord\Customer or Yiisoft\ActiveRecord\Tes...tubs\ActiveRecord\Order or Yiisoft\ActiveRecord\Tes...s\ActiveRecord\Category. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1868
        $itemsSQL = $order->/** @scrutinizer ignore-call */ getOrderItems()->joinWith('item')->createCommand()->getRawSql();
Loading history...
1869
        $expectedSQL = DbHelper::replaceQuotes(
1870
            <<<SQL
1871
            SELECT [[order_item]].* FROM [[order_item]] LEFT JOIN [[item]] ON [[order_item]].[[item_id]] = [[item]].[[id]] WHERE [[order_item]].[[order_id]]=1
1872
            SQL,
1873
            $driverName,
1874
        );
1875
        $this->assertEquals($expectedSQL, $itemsSQL);
1876
    }
1877
1878
    public function testOutdatedRelationsAreResetForExistingRecords(): void
1879
    {
1880
        $this->checkFixture($this->db, 'order_item', true);
1881
1882
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
1883
        $orderItems = $orderItemQuery->findOne(1);
1884
        $this->assertEquals(1, $orderItems->order->id);
0 ignored issues
show
Bug introduced by
Accessing order on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1885
        $this->assertEquals(1, $orderItems->item->id);
0 ignored issues
show
Bug introduced by
Accessing item on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1886
1887
        /** test `__set()`. */
1888
        $orderItems->order_id = 2;
0 ignored issues
show
Bug introduced by
Accessing order_id on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1889
        $orderItems->item_id = 1;
0 ignored issues
show
Bug introduced by
Accessing item_id on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1890
        $this->assertEquals(2, $orderItems->order->id);
1891
        $this->assertEquals(1, $orderItems->item->id);
1892
1893
        /** Test `setAttribute()`. */
1894
        $orderItems->setAttribute('order_id', 3);
1895
        $orderItems->setAttribute('item_id', 1);
1896
        $this->assertEquals(3, $orderItems->order->id);
1897
        $this->assertEquals(1, $orderItems->item->id);
1898
    }
1899
1900
    public function testOutdatedCompositeKeyRelationsAreReset(): void
1901
    {
1902
        $this->checkFixture($this->db, 'dossier');
1903
1904
        $dossierQuery = new ActiveQuery(Dossier::class, $this->db);
1905
1906
        $dossiers = $dossierQuery->findOne(['department_id' => 1, 'employee_id' => 1]);
1907
        $this->assertEquals('John Doe', $dossiers->employee->fullName);
0 ignored issues
show
Bug introduced by
Accessing employee on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1908
1909
        $dossiers->department_id = 2;
0 ignored issues
show
Bug introduced by
Accessing department_id on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1910
        $this->assertEquals('Ann Smith', $dossiers->employee->fullName);
1911
1912
        $dossiers->employee_id = 2;
0 ignored issues
show
Bug introduced by
Accessing employee_id on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1913
        $this->assertEquals('Will Smith', $dossiers->employee->fullName);
1914
1915
        unset($dossiers->employee_id);
1916
        $this->assertNull($dossiers->employee);
1917
1918
        $dossier = new Dossier($this->db);
1919
        $this->assertNull($dossier->employee);
1920
1921
        $dossier->employee_id = 1;
1922
        $dossier->department_id = 2;
1923
        $this->assertEquals('Ann Smith', $dossier->employee->fullName);
1924
1925
        $dossier->employee_id = 2;
1926
        $this->assertEquals('Will Smith', $dossier->employee->fullName);
1927
    }
1928
1929
    public function testOutdatedViaTableRelationsAreReset(): void
1930
    {
1931
        $this->checkFixture($this->db, 'order', true);
1932
1933
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1934
1935
        $orders = $orderQuery->findOne(1);
1936
        $orderItemIds = DbArrayHelper::getColumn($orders->items, 'id');
0 ignored issues
show
Bug introduced by
Accessing items on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1937
        sort($orderItemIds);
1938
        $this->assertSame([1, 2], $orderItemIds);
1939
1940
        $orders->id = 2;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1941
        sort($orderItemIds);
1942
        $orderItemIds = DbArrayHelper::getColumn($orders->items, 'id');
1943
        $this->assertSame([3, 4, 5], $orderItemIds);
1944
1945
        unset($orders->id);
1946
        $this->assertSame([], $orders->items);
1947
1948
        $order = new Order($this->db);
1949
        $this->assertSame([], $order->items);
0 ignored issues
show
Bug Best Practice introduced by
The property items does not exist on Yiisoft\ActiveRecord\Tes...tubs\ActiveRecord\Order. Since you implemented __get, consider adding a @property annotation.
Loading history...
1950
1951
        $order->id = 3;
1952
        $orderItemIds = DbArrayHelper::getColumn($order->items, 'id');
0 ignored issues
show
Bug introduced by
It seems like $order->items can also be of type null; however, parameter $array of Yiisoft\Db\Helper\DbArrayHelper::getColumn() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1952
        $orderItemIds = DbArrayHelper::getColumn(/** @scrutinizer ignore-type */ $order->items, 'id');
Loading history...
1953
        $this->assertSame([2], $orderItemIds);
1954
    }
1955
1956
    public function testInverseOfDynamic(): void
1957
    {
1958
        $this->checkFixture($this->db, 'customer');
1959
1960
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1961
1962
        $customer = $customerQuery->findOne(1);
1963
1964
        /** request the inverseOf relation without explicitly (eagerly) loading it */
1965
        $orders2 = $customer->getOrders2()->all();
1966
        $this->assertSame($customer, $orders2[0]->customer2);
1967
1968
        $orders2 = $customer->getOrders2()->onePopulate();
1969
        $this->assertSame($customer, $orders2->customer2);
1970
1971
        /**
1972
         * request the inverseOf relation while also explicitly eager loading it (while possible, this is of course
1973
         * redundant)
1974
         */
1975
        $orders2 = $customer->getOrders2()->with('customer2')->all();
1976
        $this->assertSame($customer, $orders2[0]->customer2);
1977
1978
        $orders2 = $customer->getOrders2()->with('customer2')->onePopulate();
1979
        $this->assertSame($customer, $orders2->customer2);
1980
1981
        /** request the inverseOf relation as array */
1982
        $orders2 = $customer->getOrders2()->asArray()->all();
1983
        $this->assertEquals($customer['id'], $orders2[0]['customer2']['id']);
1984
1985
        $orders2 = $customer->getOrders2()->asArray()->onePopulate();
1986
        $this->assertEquals($customer['id'], $orders2['customer2']['id']);
1987
    }
1988
1989
    public function testOptimisticLock(): void
1990
    {
1991
        $this->checkFixture($this->db, 'document');
1992
1993
        $documentQuery = new ActiveQuery(Document::class, $this->db);
1994
        $record = $documentQuery->findOne(1);
1995
1996
        $record->content = 'New Content';
0 ignored issues
show
Bug introduced by
Accessing content on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1997
        $record->save();
1998
        $this->assertEquals(1, $record->version);
0 ignored issues
show
Bug introduced by
Accessing version on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1999
2000
        $record = $documentQuery->findOne(1);
2001
2002
        $record->content = 'Rewrite attempt content';
2003
        $record->version = 0;
2004
        $this->expectException(StaleObjectException::class);
2005
        $record->save();
2006
    }
2007
2008
    public function testOptimisticLockOnDelete(): void
2009
    {
2010
        $this->checkFixture($this->db, 'document', true);
2011
2012
        $documentQuery = new ActiveQuery(Document::class, $this->db);
2013
        $document = $documentQuery->findOne(1);
2014
2015
        $this->assertSame(0, $document->version);
0 ignored issues
show
Bug introduced by
Accessing version on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2016
2017
        $document->version = 1;
2018
2019
        $this->expectException(StaleObjectException::class);
2020
        $document->delete();
2021
    }
2022
2023
    public function testOptimisticLockAfterDelete(): void
2024
    {
2025
        $this->checkFixture($this->db, 'document', true);
2026
2027
        $documentQuery = new ActiveQuery(Document::class, $this->db);
2028
        $document = $documentQuery->findOne(1);
2029
2030
        $this->assertSame(0, $document->version);
0 ignored issues
show
Bug introduced by
Accessing version on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2031
        $this->assertSame(1, $document->delete());
2032
        $this->assertTrue($document->getIsNewRecord());
2033
2034
        $this->expectException(StaleObjectException::class);
2035
        $document->delete();
2036
    }
2037
2038
    /**
2039
     * {@see https://github.com/yiisoft/yii2/issues/9006}
2040
     */
2041
    public function testBit(): void
2042
    {
2043
        $this->checkFixture($this->db, 'bit_values');
2044
2045
        $bitValueQuery = new ActiveQuery(BitValues::class, $this->db);
2046
        $falseBit = $bitValueQuery->findOne(1);
2047
        $this->assertEquals(false, $falseBit->val);
0 ignored issues
show
Bug introduced by
Accessing val on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2048
2049
        $bitValueQuery = new ActiveQuery(BitValues::class, $this->db);
2050
        $trueBit = $bitValueQuery->findOne(2);
2051
        $this->assertEquals(true, $trueBit->val);
2052
    }
2053
2054
    public function testUpdateAttributes(): void
2055
    {
2056
        $this->checkFixture($this->db, 'order');
2057
2058
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2059
        $order = $orderQuery->findOne(1);
2060
        $newTotal = 978;
2061
        $this->assertSame(1, $order->updateAttributes(['total' => $newTotal]));
2062
        $this->assertEquals($newTotal, $order->total);
0 ignored issues
show
Bug introduced by
Accessing total on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2063
2064
        $order = $orderQuery->findOne(1);
2065
        $this->assertEquals($newTotal, $order->total);
2066
2067
        /** @see https://github.com/yiisoft/yii2/issues/12143 */
2068
        $newOrder = new Order($this->db);
2069
        $this->assertTrue($newOrder->getIsNewRecord());
2070
2071
        $newTotal = 200;
2072
        $this->assertSame(0, $newOrder->updateAttributes(['total' => $newTotal]));
2073
        $this->assertTrue($newOrder->getIsNewRecord());
2074
        $this->assertEquals($newTotal, $newOrder->total);
2075
    }
2076
2077
    /**
2078
     * Ensure no ambiguous column error occurs if ActiveQuery adds a JOIN.
2079
     *
2080
     * {@see https://github.com/yiisoft/yii2/issues/13757}
2081
     */
2082
    public function testAmbiguousColumnFindOne(): void
2083
    {
2084
        $this->checkFixture($this->db, 'customer');
2085
2086
        $customerQuery = new CustomerQuery(Customer::class, $this->db);
2087
2088
        $customerQuery->joinWithProfile = true;
2089
2090
        $arClass = $customerQuery->findOne(1);
2091
2092
        $this->assertTrue($arClass->refresh());
0 ignored issues
show
Bug introduced by
The method refresh() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\ActiveRecord\ActiveRecordInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2092
        $this->assertTrue($arClass->/** @scrutinizer ignore-call */ refresh());
Loading history...
2093
2094
        $customerQuery->joinWithProfile = false;
2095
    }
2096
2097
    public function testCustomARRelation(): void
2098
    {
2099
        $this->checkFixture($this->db, 'order_item');
2100
2101
        $orderItem = new ActiveQuery(OrderItem::class, $this->db);
2102
2103
        $orderItem = $orderItem->findOne(1);
2104
2105
        $this->assertInstanceOf(Order::class, $orderItem->custom);
0 ignored issues
show
Bug introduced by
Accessing custom on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2106
    }
2107
2108
    public function testGetAttributes(): void
2109
    {
2110
        $attributesExpected = [];
2111
        $this->checkFixture($this->db, 'customer');
2112
2113
        $attributesExpected['id'] = 1;
2114
        $attributesExpected['email'] = '[email protected]';
2115
        $attributesExpected['name'] = 'user1';
2116
        $attributesExpected['address'] = 'address1';
2117
        $attributesExpected['status'] = 1;
2118
2119
        if ($this->db->getDriverName() === 'pgsql') {
2120
            $attributesExpected['bool_status'] = true;
2121
        }
2122
2123
        if ($this->db->getDriverName() === 'oci') {
2124
            $attributesExpected['bool_status'] = '1';
2125
        }
2126
2127
        $attributesExpected['profile_id'] = 1;
2128
2129
        $customer = new ActiveQuery(Customer::class, $this->db);
2130
2131
        $attributes = $customer->findOne(1)->getAttributes();
2132
2133
        $this->assertEquals($attributes, $attributesExpected);
2134
    }
2135
2136
    public function testGetAttributesOnly(): void
2137
    {
2138
        $this->checkFixture($this->db, 'customer');
2139
2140
        $customer = new ActiveQuery(Customer::class, $this->db);
2141
2142
        $attributes = $customer->findOne(1)->getAttributes(['id', 'email', 'name']);
2143
2144
        $this->assertEquals(['id' => 1, 'email' => '[email protected]', 'name' => 'user1'], $attributes);
2145
    }
2146
2147
    public function testGetAttributesExcept(): void
2148
    {
2149
        $this->checkFixture($this->db, 'customer');
2150
2151
        $customer = new ActiveQuery(Customer::class, $this->db);
2152
2153
        $attributes = $customer->findOne(1)->getAttributes(null, ['status', 'bool_status', 'profile_id']);
2154
2155
        $this->assertEquals(
2156
            $attributes,
2157
            ['id' => 1, 'email' => '[email protected]', 'name' => 'user1', 'address' => 'address1']
2158
        );
2159
    }
2160
2161
    public function testFields(): void
2162
    {
2163
        $this->checkFixture($this->db, 'order_item');
2164
2165
        $orderItem = new ActiveQuery(OrderItem::class, $this->db);
2166
2167
        $fields = $orderItem->findOne(['order_id' => 1, 'item_id' => 2])->fields();
0 ignored issues
show
Bug introduced by
The method fields() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\ActiveRecord\ActiveRecordInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2167
        $fields = $orderItem->findOne(['order_id' => 1, 'item_id' => 2])->/** @scrutinizer ignore-call */ fields();
Loading history...
2168
2169
        $this->assertEquals(
2170
            $fields,
2171
            ['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => '40', 'price' => 20]
2172
        );
2173
    }
2174
2175
    public function testGetOldAttribute(): void
2176
    {
2177
        $this->checkFixture($this->db, 'customer');
2178
2179
        $customer = new ActiveQuery(Customer::class, $this->db);
2180
2181
        $query = $customer->findOne(1);
2182
        $this->assertEquals('user1', $query->getOldAttribute('name'));
0 ignored issues
show
Bug introduced by
The method getOldAttribute() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Did you maybe mean getOldAttributes()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2182
        $this->assertEquals('user1', $query->/** @scrutinizer ignore-call */ getOldAttribute('name'));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2183
        $this->assertEquals($query->getAttributes(), $query->getOldAttributes());
2184
2185
        $query->setAttribute('name', 'samdark');
2186
        $this->assertEquals('samdark', $query->getAttribute('name'));
2187
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2188
        $this->assertNotEquals($query->getAttribute('name'), $query->getOldAttribute('name'));
2189
    }
2190
2191
    public function testGetOldAttributes(): void
2192
    {
2193
        $attributes = [];
2194
        $attributesNew = [];
2195
        $this->checkFixture($this->db, 'customer');
2196
2197
        $attributes['id'] = 1;
2198
        $attributes['email'] = '[email protected]';
2199
        $attributes['name'] = 'user1';
2200
        $attributes['address'] = 'address1';
2201
        $attributes['status'] = 1;
2202
2203
        if ($this->db->getDriverName() === 'pgsql') {
2204
            $attributes['bool_status'] = true;
2205
        }
2206
2207
        if ($this->db->getDriverName() === 'oci') {
2208
            $attributes['bool_status'] = '1';
2209
        }
2210
2211
        $attributes['profile_id'] = 1;
2212
2213
        $customer = new ActiveQuery(Customer::class, $this->db);
2214
2215
        $query = $customer->findOne(1);
2216
        $this->assertEquals($query->getAttributes(), $attributes);
2217
        $this->assertEquals($query->getAttributes(), $query->getOldAttributes());
2218
2219
        $query->setAttribute('name', 'samdark');
2220
        $attributesNew['id'] = 1;
2221
        $attributesNew['email'] = '[email protected]';
2222
        $attributesNew['name'] = 'samdark';
2223
        $attributesNew['address'] = 'address1';
2224
        $attributesNew['status'] = 1;
2225
2226
        if ($this->db->getDriverName() === 'pgsql') {
2227
            $attributesNew['bool_status'] = true;
2228
        }
2229
2230
        if ($this->db->getDriverName() === 'oci') {
2231
            $attributesNew['bool_status'] = '1';
2232
        }
2233
2234
        $attributesNew['profile_id'] = 1;
2235
2236
        $this->assertEquals($attributesNew, $query->getAttributes());
2237
        $this->assertEquals($attributes, $query->getOldAttributes());
2238
        $this->assertNotEquals($query->getAttributes(), $query->getOldAttributes());
2239
    }
2240
2241
    public function testIsAttributeChanged(): void
2242
    {
2243
        $this->checkFixture($this->db, 'customer');
2244
2245
        $customer = new ActiveQuery(Customer::class, $this->db);
2246
2247
        $query = $customer->findOne(1);
2248
        $this->assertEquals('user1', $query->getAttribute('name'));
2249
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2250
2251
        $query->setAttribute('name', 'samdark');
2252
        $this->assertEquals('samdark', $query->getAttribute('name'));
2253
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2254
        $this->assertNotEquals($query->getAttribute('name'), $query->getOldAttribute('name'));
2255
        $this->assertTrue($query->isAttributeChanged('name', true));
0 ignored issues
show
Bug introduced by
The method isAttributeChanged() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\ActiveRecord\ActiveRecordInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2255
        $this->assertTrue($query->/** @scrutinizer ignore-call */ isAttributeChanged('name', true));
Loading history...
2256
    }
2257
2258
    public function testIsAttributeChangedNotIdentical(): void
2259
    {
2260
        $this->checkFixture($this->db, 'customer');
2261
2262
        $customer = new ActiveQuery(Customer::class, $this->db);
2263
2264
        $query = $customer->findOne(1);
2265
        $this->assertEquals('user1', $query->getAttribute('name'));
2266
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2267
2268
        $query->setAttribute('name', 'samdark');
2269
        $this->assertEquals('samdark', $query->getAttribute('name'));
2270
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2271
        $this->assertNotEquals($query->getAttribute('name'), $query->getOldAttribute('name'));
2272
        $this->assertTrue($query->isAttributeChanged('name', false));
2273
    }
2274
2275
    public function testOldAttributeAfterInsertAndUpdate(): void
2276
    {
2277
        $this->checkFixture($this->db, 'customer');
2278
2279
        $customer = new Customer($this->db);
2280
2281
        $customer->setAttributes([
2282
            'email' => '[email protected]',
2283
            'name' => 'Jack',
2284
            'address' => '123 Ocean Dr',
2285
            'status' => 1,
2286
        ]);
2287
2288
        $this->assertNull($customer->getOldAttribute('name'));
2289
        $this->assertTrue($customer->save());
2290
        $this->assertSame('Jack', $customer->getOldAttribute('name'));
2291
2292
        $customer->setAttribute('name', 'Harry');
2293
2294
        $this->assertTrue($customer->save());
2295
        $this->assertSame('Harry', $customer->getOldAttribute('name'));
2296
    }
2297
2298
    public function testCheckRelationUnknownPropertyException(): void
2299
    {
2300
        $this->checkFixture($this->db, 'customer');
2301
2302
        $customer = new ActiveQuery(Customer::class, $this->db);
2303
2304
        $query = $customer->findOne(1);
2305
2306
        $this->expectException(UnknownPropertyException::class);
2307
        $this->expectExceptionMessage('Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer::noExist');
2308
        $query->noExist;
0 ignored issues
show
Bug introduced by
Accessing noExist on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2309
    }
2310
2311
    public function testCheckRelationInvalidCallException(): void
2312
    {
2313
        $this->checkFixture($this->db, 'customer');
2314
2315
        $customer = new ActiveQuery(Customer::class, $this->db);
2316
2317
        $query = $customer->findOne(2);
2318
2319
        $this->expectException(InvalidCallException::class);
2320
        $this->expectExceptionMessage(
2321
            'Getting write-only property: Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer::ordersReadOnly'
2322
        );
2323
        $query->ordersReadOnly;
0 ignored issues
show
Bug introduced by
Accessing ordersReadOnly on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2324
    }
2325
2326
    public function testGetRelationInvalidArgumentException(): void
2327
    {
2328
        $this->checkFixture($this->db, 'customer');
2329
2330
        $customer = new ActiveQuery(Customer::class, $this->db);
2331
2332
        $query = $customer->findOne(1);
2333
2334
        /** Without throwing exception */
2335
        $this->assertEmpty($query->getRelation('items', false));
2336
2337
        /** Throwing exception */
2338
        $this->expectException(InvalidArgumentException::class);
2339
        $this->expectExceptionMessage(
2340
            'Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer has no relation named "items".'
2341
        );
2342
        $query->getRelation('items');
2343
    }
2344
2345
    public function testGetRelationInvalidArgumentExceptionHasNoRelationNamed(): void
2346
    {
2347
        $this->checkFixture($this->db, 'customer');
2348
2349
        $customer = new ActiveQuery(Customer::class, $this->db);
2350
2351
        $query = $customer->findOne(1);
2352
2353
        /** Without throwing exception */
2354
        $this->assertEmpty($query->getRelation('item', false));
2355
2356
        $this->expectException(InvalidArgumentException::class);
2357
        $this->expectExceptionMessage(
2358
            'Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer has no relation named "item"'
2359
        );
2360
        $query->getRelation('item');
2361
    }
2362
2363
    public function testGetRelationInvalidArgumentExceptionCaseSensitive(): void
2364
    {
2365
        $this->checkFixture($this->db, 'customer');
2366
2367
        $customer = new ActiveQuery(Customer::class, $this->db);
2368
2369
        $query = $customer->findOne(1);
2370
2371
        $this->assertEmpty($query->getRelation('expensiveorders', false));
2372
2373
        $this->expectException(InvalidArgumentException::class);
2374
        $this->expectExceptionMessage(
2375
            'Relation names are case sensitive. Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer ' .
2376
            'has a relation named "expensiveOrders" instead of "expensiveorders"'
2377
        );
2378
        $query->getRelation('expensiveorders');
2379
    }
2380
2381
    public function testExists(): void
2382
    {
2383
        $this->checkFixture($this->db, 'customer');
2384
2385
        $customer = new ActiveQuery(Customer::class, $this->db);
2386
2387
        $this->assertTrue($customer->where(['id' => 2])->exists());
2388
        $this->assertFalse($customer->where(['id' => 5])->exists());
2389
        $this->assertTrue($customer->where(['name' => 'user1'])->exists());
2390
        $this->assertFalse($customer->where(['name' => 'user5'])->exists());
2391
        $this->assertTrue($customer->where(['id' => [2, 3]])->exists());
2392
        $this->assertTrue($customer->where(['id' => [2, 3]])->offset(1)->exists());
2393
        $this->assertFalse($customer->where(['id' => [2, 3]])->offset(2)->exists());
2394
    }
2395
2396
    public function testUnlink(): void
2397
    {
2398
        $this->checkFixture($this->db, 'customer');
2399
2400
        /** has many without delete */
2401
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2402
        $customer = $customerQuery->findOne(2);
2403
        $this->assertCount(2, $customer->ordersWithNullFK);
0 ignored issues
show
Bug introduced by
Accessing ordersWithNullFK on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2404
        $customer->unlink('ordersWithNullFK', $customer->ordersWithNullFK[1], false);
2405
        $this->assertCount(1, $customer->ordersWithNullFK);
2406
2407
        $orderWithNullFKQuery = new ActiveQuery(OrderWithNullFK::class, $this->db);
2408
        $orderWithNullFK = $orderWithNullFKQuery->findOne(3);
2409
        $this->assertEquals(3, $orderWithNullFK->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2410
        $this->assertNull($orderWithNullFK->customer_id);
0 ignored issues
show
Bug introduced by
Accessing customer_id on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2411
2412
        /** has many with delete */
2413
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2414
        $customer = $customerQuery->findOne(2);
2415
        $this->assertCount(2, $customer->orders);
0 ignored issues
show
Bug introduced by
Accessing orders on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2416
2417
        $customer->unlink('orders', $customer->orders[1], true);
2418
        $this->assertCount(1, $customer->orders);
2419
2420
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2421
        $this->assertNull($orderQuery->findOne(3));
2422
2423
        /** via model with delete */
2424
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2425
        $order = $orderQuery->findOne(2);
2426
        $this->assertCount(3, $order->items);
0 ignored issues
show
Bug introduced by
Accessing items on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2427
        $this->assertCount(3, $order->orderItems);
0 ignored issues
show
Bug introduced by
Accessing orderItems on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2428
        $order->unlink('items', $order->items[2], true);
2429
        $this->assertCount(2, $order->items);
2430
        $this->assertCount(2, $order->orderItems);
2431
2432
        /** via model without delete */
2433
        $this->assertCount(2, $order->itemsWithNullFK);
0 ignored issues
show
Bug introduced by
Accessing itemsWithNullFK on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2434
        $order->unlink('itemsWithNullFK', $order->itemsWithNullFK[1], false);
2435
2436
        $this->assertCount(1, $order->itemsWithNullFK);
2437
        $this->assertCount(2, $order->orderItems);
2438
    }
2439
2440
    public function testUnlinkAllAndConditionSetNull(): void
2441
    {
2442
        $this->checkFixture($this->db, 'order_item_with_null_fk');
2443
2444
        /** in this test all orders are owned by customer 1 */
2445
        $orderWithNullFKInstance = new OrderWithNullFK($this->db);
2446
        $orderWithNullFKInstance->updateAll(['customer_id' => 1]);
2447
2448
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2449
        $customer = $customerQuery->findOne(1);
2450
        $this->assertCount(3, $customer->ordersWithNullFK);
0 ignored issues
show
Bug introduced by
Accessing ordersWithNullFK on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2451
        $this->assertCount(1, $customer->expensiveOrdersWithNullFK);
0 ignored issues
show
Bug introduced by
Accessing expensiveOrdersWithNullFK on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2452
2453
        $orderWithNullFKQuery = new ActiveQuery(OrderWithNullFK::class, $this->db);
2454
        $this->assertEquals(3, $orderWithNullFKQuery->count());
2455
2456
        $customer->unlinkAll('expensiveOrdersWithNullFK');
2457
        $this->assertCount(3, $customer->ordersWithNullFK);
2458
        $this->assertCount(0, $customer->expensiveOrdersWithNullFK);
2459
        $this->assertEquals(3, $orderWithNullFKQuery->count());
2460
2461
        $customer = $customerQuery->findOne(1);
2462
        $this->assertCount(2, $customer->ordersWithNullFK);
2463
        $this->assertCount(0, $customer->expensiveOrdersWithNullFK);
2464
    }
2465
2466
    public function testUnlinkAllAndConditionDelete(): void
2467
    {
2468
        $this->checkFixture($this->db, 'customer', true);
2469
2470
        /** in this test all orders are owned by customer 1 */
2471
        $orderInstance = new Order($this->db);
2472
        $orderInstance->updateAll(['customer_id' => 1]);
2473
2474
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2475
        $customer = $customerQuery->findOne(1);
2476
        $this->assertCount(3, $customer->orders);
0 ignored issues
show
Bug introduced by
Accessing orders on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2477
        $this->assertCount(1, $customer->expensiveOrders);
0 ignored issues
show
Bug introduced by
Accessing expensiveOrders on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2478
2479
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2480
        $this->assertEquals(3, $orderQuery->count());
2481
2482
        $customer->unlinkAll('expensiveOrders', true);
2483
        $this->assertCount(3, $customer->orders);
2484
        $this->assertCount(0, $customer->expensiveOrders);
2485
        $this->assertEquals(2, $orderQuery->count());
2486
2487
        $customer = $customerQuery->findOne(1);
2488
        $this->assertCount(2, $customer->orders);
2489
        $this->assertCount(0, $customer->expensiveOrders);
2490
    }
2491
2492
    public function testUpdate(): void
2493
    {
2494
        $this->checkFixture($this->db, 'customer');
2495
2496
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2497
        $customer = $customerQuery->findOne(2);
2498
        $this->assertInstanceOf(Customer::class, $customer);
2499
        $this->assertEquals('user2', $customer->getAttribute('name'));
2500
        $this->assertFalse($customer->getIsNewRecord());
2501
        $this->assertEmpty($customer->getDirtyAttributes());
2502
2503
        $customer->setAttribute('name', 'user2x');
2504
        $customer->save();
2505
        $this->assertEquals('user2x', $customer->getAttribute('name'));
2506
        $this->assertFalse($customer->getIsNewRecord());
2507
2508
        $customer2 = $customerQuery->findOne(2);
2509
        $this->assertEquals('user2x', $customer2->getAttribute('name'));
2510
2511
        /** no update */
2512
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2513
        $customer = $customerQuery->findOne(1);
2514
2515
        $customer->setAttribute('name', 'user1');
2516
        $this->assertEquals(0, $customer->update());
2517
2518
        /** updateAll */
2519
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2520
        $customer = $customerQuery->findOne(3);
2521
        $this->assertEquals('user3', $customer->getAttribute('name'));
2522
2523
        $ret = $customer->updateAll(['name' => 'temp'], ['id' => 3]);
2524
        $this->assertEquals(1, $ret);
2525
2526
        $customer = $customerQuery->findOne(3);
2527
        $this->assertEquals('temp', $customer->getAttribute('name'));
2528
2529
        $ret = $customer->updateAll(['name' => 'tempX']);
2530
        $this->assertEquals(3, $ret);
2531
2532
        $ret = $customer->updateAll(['name' => 'temp'], ['name' => 'user6']);
2533
        $this->assertEquals(0, $ret);
2534
    }
2535
2536
    public function testUpdateCounters(): void
2537
    {
2538
        $this->checkFixture($this->db, 'order_item', true);
2539
2540
        /** updateCounters */
2541
        $pk = ['order_id' => 2, 'item_id' => 4];
2542
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
2543
        $orderItem = $orderItemQuery->findOne($pk);
2544
        $this->assertEquals(1, $orderItem->quantity);
0 ignored issues
show
Bug introduced by
Accessing quantity on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2545
2546
        $ret = $orderItem->updateCounters(['quantity' => -1]);
0 ignored issues
show
Bug introduced by
The method updateCounters() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Did you maybe mean update()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2546
        /** @scrutinizer ignore-call */ 
2547
        $ret = $orderItem->updateCounters(['quantity' => -1]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2547
        $this->assertTrue($ret);
2548
        $this->assertEquals(0, $orderItem->quantity);
2549
2550
        $orderItem = $orderItemQuery->findOne($pk);
2551
        $this->assertEquals(0, $orderItem->quantity);
2552
2553
        /** updateAllCounters */
2554
        $pk = ['order_id' => 1, 'item_id' => 2];
2555
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
2556
        $orderItem = $orderItemQuery->findOne($pk);
2557
        $this->assertEquals(2, $orderItem->quantity);
2558
2559
        $orderItem = new OrderItem($this->db);
2560
        $ret = $orderItem->updateAllCounters(['quantity' => 3, 'subtotal' => -10], $pk);
2561
        $this->assertEquals(1, $ret);
2562
2563
        $orderItem = $orderItemQuery->findOne($pk);
2564
        $this->assertEquals(5, $orderItem->quantity);
2565
        $this->assertEquals(30, $orderItem->subtotal);
0 ignored issues
show
Bug introduced by
Accessing subtotal on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2566
    }
2567
2568
    public function testDelete(): void
2569
    {
2570
        $this->checkFixture($this->db, 'customer', true);
2571
2572
        /** delete */
2573
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2574
        $customer = $customerQuery->findOne(2);
2575
        $this->assertInstanceOf(Customer::class, $customer);
2576
        $this->assertEquals('user2', $customer->name);
2577
2578
        $customer->delete();
2579
2580
        $customer = $customerQuery->findOne(2);
2581
        $this->assertNull($customer);
2582
2583
        /** deleteAll */
2584
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2585
        $customers = $customerQuery->all();
2586
        $this->assertCount(2, $customers);
2587
2588
        $customer = new Customer($this->db);
2589
        $ret = $customer->deleteAll();
2590
        $this->assertEquals(2, $ret);
2591
2592
        $customers = $customerQuery->all();
2593
        $this->assertCount(0, $customers);
2594
2595
        $ret = $customer->deleteAll();
2596
        $this->assertEquals(0, $ret);
2597
    }
2598
2599
    /**
2600
     * {@see https://github.com/yiisoft/yii2/issues/17089}
2601
     */
2602
    public function testViaWithCallable(): void
2603
    {
2604
        $this->checkFixture($this->db, 'order', true);
2605
2606
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2607
2608
        $order = $orderQuery->findOne(2);
2609
2610
        $expensiveItems = $order->expensiveItemsUsingViaWithCallable;
0 ignored issues
show
Bug introduced by
Accessing expensiveItemsUsingViaWithCallable on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2611
        $cheapItems = $order->cheapItemsUsingViaWithCallable;
0 ignored issues
show
Bug introduced by
Accessing cheapItemsUsingViaWithCallable on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2612
2613
        $this->assertCount(2, $expensiveItems);
2614
        $this->assertEquals(4, $expensiveItems[0]->id);
2615
        $this->assertEquals(5, $expensiveItems[1]->id);
2616
        $this->assertCount(1, $cheapItems);
2617
        $this->assertEquals(3, $cheapItems[0]->id);
2618
    }
2619
2620
    public function testLink(): void
2621
    {
2622
        $this->checkFixture($this->db, 'customer', true);
2623
2624
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2625
        $customer = $customerQuery->findOne(2);
2626
        $this->assertCount(2, $customer->orders);
0 ignored issues
show
Bug introduced by
Accessing orders on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2627
2628
        /** has many */
2629
        $order = new Order($this->db);
2630
2631
        $order->total = 100;
2632
        $order->created_at = time();
2633
        $this->assertTrue($order->isNewRecord);
0 ignored issues
show
Bug Best Practice introduced by
The property isNewRecord does not exist on Yiisoft\ActiveRecord\Tes...tubs\ActiveRecord\Order. Since you implemented __get, consider adding a @property annotation.
Loading history...
2634
2635
        /** belongs to */
2636
        $order = new Order($this->db);
2637
2638
        $order->total = 100;
2639
        $order->created_at = time();
2640
        $this->assertTrue($order->isNewRecord);
2641
2642
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2643
        $customer = $customerQuery->findOne(1);
2644
        $this->assertNull($order->customer);
0 ignored issues
show
Bug Best Practice introduced by
The property customer does not exist on Yiisoft\ActiveRecord\Tes...tubs\ActiveRecord\Order. Since you implemented __get, consider adding a @property annotation.
Loading history...
2645
2646
        $order->link('customer', $customer);
0 ignored issues
show
Bug introduced by
It seems like $customer can also be of type array and null; however, parameter $arClass of Yiisoft\ActiveRecord\BaseActiveRecord::link() does only seem to accept Yiisoft\ActiveRecord\ActiveRecordInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2646
        $order->link('customer', /** @scrutinizer ignore-type */ $customer);
Loading history...
2647
        $this->assertFalse($order->isNewRecord);
2648
        $this->assertEquals(1, $order->customer_id);
2649
        $this->assertEquals(1, $order->customer->primaryKey);
2650
2651
        /** via model */
2652
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2653
        $order = $orderQuery->findOne(1);
2654
        $this->assertCount(2, $order->items);
0 ignored issues
show
Bug introduced by
Accessing items on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2655
        $this->assertCount(2, $order->orderItems);
0 ignored issues
show
Bug introduced by
Accessing orderItems on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2656
2657
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
2658
        $orderItem = $orderItemQuery->findOne(['order_id' => 1, 'item_id' => 3]);
2659
        $this->assertNull($orderItem);
2660
2661
        $itemQuery = new ActiveQuery(Item::class, $this->db);
2662
        $item = $itemQuery->findOne(3);
2663
        $order->link('items', $item, ['quantity' => 10, 'subtotal' => 100]);
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type array and null; however, parameter $arClass of Yiisoft\ActiveRecord\ActiveRecordInterface::link() does only seem to accept Yiisoft\ActiveRecord\ActiveRecordInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2663
        $order->link('items', /** @scrutinizer ignore-type */ $item, ['quantity' => 10, 'subtotal' => 100]);
Loading history...
2664
        $this->assertCount(3, $order->items);
2665
        $this->assertCount(3, $order->orderItems);
2666
2667
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
2668
        $orderItem = $orderItemQuery->findOne(['order_id' => 1, 'item_id' => 3]);
2669
        $this->assertInstanceOf(OrderItem::class, $orderItem);
2670
        $this->assertEquals(10, $orderItem->quantity);
2671
        $this->assertEquals(100, $orderItem->subtotal);
2672
    }
2673
2674
    public function testEqual(): void
2675
    {
2676
        $this->checkFixture($this->db, 'customer');
2677
2678
        $customerA = (new ActiveQuery(Customer::class, $this->db))->findOne(1);
2679
        $customerB = (new ActiveQuery(Customer::class, $this->db))->findOne(2);
2680
        $this->assertFalse($customerA->equals($customerB));
0 ignored issues
show
Bug introduced by
It seems like $customerB can also be of type array and null; however, parameter $record of Yiisoft\ActiveRecord\Act...cordInterface::equals() does only seem to accept Yiisoft\ActiveRecord\ActiveRecordInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2680
        $this->assertFalse($customerA->equals(/** @scrutinizer ignore-type */ $customerB));
Loading history...
2681
2682
        $customerB = (new ActiveQuery(Customer::class, $this->db))->findOne(1);
2683
        $this->assertTrue($customerA->equals($customerB));
2684
2685
        $customerA = (new ActiveQuery(Customer::class, $this->db))->findOne(1);
2686
        $customerB = (new ActiveQuery(Item::class, $this->db))->findOne(1);
2687
        $this->assertFalse($customerA->equals($customerB));
2688
    }
2689
}
2690