Passed
Pull Request — master (#239)
by Wilmer
02:42
created

ActiveQueryTest::testJoinWithVia()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

1400
        $orders = $customer->/** @scrutinizer ignore-call */ getOrders2()->all();
Loading history...
1401
        $this->assertCount(2, $orders);
1402
        $this->assertSame($orders[0]->customer2, $customer);
1403
        $this->assertSame($orders[1]->customer2, $customer);
1404
        $this->assertTrue(
1405
            $orders[0]->isRelationPopulated('customer2'),
1406
            'inverse relation did not populate the relation'
1407
        );
1408
        $this->assertTrue(
1409
            $orders[1]->isRelationPopulated('customer2'),
1410
            'inverse relation did not populate the relation'
1411
        );
1412
1413
        /** the other way around */
1414
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1415
        $customer = $customerQuery->with('orders2')->where(['id' => 1])->asArray()->onePopulate();
1416
        $this->assertSame($customer['orders2'][0]['customer2']['id'], $customer['id']);
1417
1418
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1419
        $customers = $customerQuery->with('orders2')->where(['id' => [1, 3]])->asArray()->all();
1420
        $this->assertSame($customer['orders2'][0]['customer2']['id'], $customers[0]['id']);
1421
        $this->assertEmpty($customers[1]['orders2']);
1422
1423
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1424
        $orders = $orderQuery->with('customer2')->where(['id' => 1])->all();
1425
        $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]);
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...
1426
1427
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1428
        $order = $orderQuery->with('customer2')->where(['id' => 1])->onePopulate();
1429
        $this->assertSame($order->customer2->orders2, [$order]);
1430
1431
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1432
        $orders = $orderQuery->with('customer2')->where(['id' => 1])->asArray()->all();
1433
        $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1434
1435
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1436
        $order = $orderQuery->with('customer2')->where(['id' => 1])->asArray()->onePopulate();
1437
        $this->assertSame($order['customer2']['orders2'][0]['id'], $orders[0]['id']);
1438
1439
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1440
        $orders = $orderQuery->with('customer2')->where(['id' => [1, 3]])->all();
1441
        $this->assertSame($orders[0]->customer2->orders2, [$orders[0]]);
1442
        $this->assertSame($orders[1]->customer2->orders2, [$orders[1]]);
1443
1444
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1445
        $orders = $orderQuery->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->all();
1446
        $this->assertSame($orders[0]->customer2->orders2, $orders);
1447
        $this->assertSame($orders[1]->customer2->orders2, $orders);
1448
1449
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1450
        $orders = $orderQuery->with('customer2')->where(['id' => [2, 3]])->orderBy('id')->asArray()->all();
1451
        $this->assertSame($orders[0]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1452
        $this->assertSame($orders[0]['customer2']['orders2'][1]['id'], $orders[1]['id']);
1453
        $this->assertSame($orders[1]['customer2']['orders2'][0]['id'], $orders[0]['id']);
1454
        $this->assertSame($orders[1]['customer2']['orders2'][1]['id'], $orders[1]['id']);
1455
    }
1456
1457
    public function testUnlinkAllViaTable(): void
1458
    {
1459
        $this->checkFixture($this->db, 'order');
1460
1461
        $this->loadFixture($this->db);
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());
0 ignored issues
show
Bug introduced by
The method getItems() 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 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

1505
        $this->assertEquals(3, $category->/** @scrutinizer ignore-call */ getItems()->count());
Loading history...
1506
        $this->assertEquals(1, $category->getLimitedItems()->count());
0 ignored issues
show
Bug introduced by
The method getLimitedItems() 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 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

1506
        $this->assertEquals(1, $category->/** @scrutinizer ignore-call */ getLimitedItems()->count());
Loading history...
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);
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...
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);
1603
1604
        foreach ($aggregation as $item) {
1605
            if ($item->status === 1) {
0 ignored issues
show
Bug introduced by
Accessing status on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1606
                $this->assertEquals(183, $item->sumTotal);
0 ignored issues
show
Bug introduced by
Accessing sumTotal on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
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) {
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...
1652
                $this->assertEquals(70, $item->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...
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);
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...
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 = $this->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());
1825
        $this->assertContains('orders2', $query->extraFields());
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
     * @param string $orderTableName
1846
     * @param string $orderItemTableName
1847
     *
1848
     * @throws Exception|InvalidConfigException
1849
     */
1850
    public function testRelationWhereParams(string $orderTableName, string $orderItemTableName): void
1851
    {
1852
        $this->checkFixture($this->db, 'order');
1853
1854
        $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...
1855
        $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...
1856
1857
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1858
        $order = $orderQuery->findOne(1);
1859
        $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

1859
        $itemsSQL = $order->/** @scrutinizer ignore-call */ getOrderitems()->createCommand()->getRawSql();
Loading history...
1860
        $expectedSQL = $this->replaceQuotes('SELECT * FROM [[order_item]] WHERE [[order_id]]=1');
1861
        $this->assertEquals($expectedSQL, $itemsSQL);
1862
1863
        $order = $orderQuery->findOne(1);
1864
        $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

1864
        $itemsSQL = $order->/** @scrutinizer ignore-call */ getOrderItems()->joinWith('item')->createCommand()->getRawSql();
Loading history...
1865
        $expectedSQL = $this->replaceQuotes(
1866
            'SELECT [[order_item]].* FROM [[order_item]] LEFT JOIN [[item]] ' .
1867
            'ON [[order_item]].[[item_id]] = [[item]].[[id]] WHERE [[order_item]].[[order_id]]=1'
1868
        );
1869
        $this->assertEquals($expectedSQL, $itemsSQL);
1870
    }
1871
1872
    public function testOutdatedRelationsAreResetForExistingRecords(): void
1873
    {
1874
        $this->checkFixture($this->db, 'order_item', true);
1875
1876
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
1877
        $orderItems = $orderItemQuery->findOne(1);
1878
        $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...
1879
        $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...
1880
1881
        /** test `__set()`. */
1882
        $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...
1883
        $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...
1884
        $this->assertEquals(2, $orderItems->order->id);
1885
        $this->assertEquals(1, $orderItems->item->id);
1886
1887
        /** Test `setAttribute()`. */
1888
        $orderItems->setAttribute('order_id', 3);
1889
        $orderItems->setAttribute('item_id', 1);
1890
        $this->assertEquals(3, $orderItems->order->id);
1891
        $this->assertEquals(1, $orderItems->item->id);
1892
    }
1893
1894
    public function testOutdatedCompositeKeyRelationsAreReset(): void
1895
    {
1896
        $this->checkFixture($this->db, 'dossier');
1897
1898
        $dossierQuery = new ActiveQuery(Dossier::class, $this->db);
1899
1900
        $dossiers = $dossierQuery->findOne(['department_id' => 1, 'employee_id' => 1]);
1901
        $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...
1902
1903
        $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...
1904
        $this->assertEquals('Ann Smith', $dossiers->employee->fullName);
1905
1906
        $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...
1907
        $this->assertEquals('Will Smith', $dossiers->employee->fullName);
1908
1909
        unset($dossiers->employee_id);
1910
        $this->assertNull($dossiers->employee);
1911
1912
        $dossier = new Dossier($this->db);
1913
        $this->assertNull($dossier->employee);
1914
1915
        $dossier->employee_id = 1;
1916
        $dossier->department_id = 2;
1917
        $this->assertEquals('Ann Smith', $dossier->employee->fullName);
1918
1919
        $dossier->employee_id = 2;
1920
        $this->assertEquals('Will Smith', $dossier->employee->fullName);
1921
    }
1922
1923
    public function testOutdatedViaTableRelationsAreReset(): void
1924
    {
1925
        $this->checkFixture($this->db, 'order');
1926
1927
        $this->loadFixture($this->db);
1928
1929
        $orderQuery = new ActiveQuery(Order::class, $this->db);
1930
1931
        $orders = $orderQuery->findOne(1);
1932
        $orderItemIds = ArrayHelper::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...
1933
        sort($orderItemIds);
1934
        $this->assertSame([1, 2], $orderItemIds);
1935
1936
        $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...
1937
        sort($orderItemIds);
1938
        $orderItemIds = ArrayHelper::getColumn($orders->items, 'id');
1939
        $this->assertSame([3, 4, 5], $orderItemIds);
1940
1941
        unset($orders->id);
1942
        $this->assertSame([], $orders->items);
1943
1944
        $order = new Order($this->db);
1945
        $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...
1946
1947
        $order->id = 3;
1948
        $orderItemIds = ArrayHelper::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\ArrayHelper::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

1948
        $orderItemIds = ArrayHelper::getColumn(/** @scrutinizer ignore-type */ $order->items, 'id');
Loading history...
1949
        $this->assertSame([2], $orderItemIds);
1950
    }
1951
1952
    public function testInverseOfDynamic(): void
1953
    {
1954
        $this->checkFixture($this->db, 'customer');
1955
1956
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
1957
1958
        $customer = $customerQuery->findOne(1);
1959
1960
        /** request the inverseOf relation without explicitly (eagerly) loading it */
1961
        $orders2 = $customer->getOrders2()->all();
1962
        $this->assertSame($customer, $orders2[0]->customer2);
1963
1964
        $orders2 = $customer->getOrders2()->onePopulate();
1965
        $this->assertSame($customer, $orders2->customer2);
1966
1967
        /**
1968
         * request the inverseOf relation while also explicitly eager loading it (while possible, this is of course
1969
         * redundant)
1970
         */
1971
        $orders2 = $customer->getOrders2()->with('customer2')->all();
1972
        $this->assertSame($customer, $orders2[0]->customer2);
1973
1974
        $orders2 = $customer->getOrders2()->with('customer2')->onePopulate();
1975
        $this->assertSame($customer, $orders2->customer2);
1976
1977
        /** request the inverseOf relation as array */
1978
        $orders2 = $customer->getOrders2()->asArray()->all();
1979
        $this->assertEquals($customer['id'], $orders2[0]['customer2']['id']);
1980
1981
        $orders2 = $customer->getOrders2()->asArray()->onePopulate();
1982
        $this->assertEquals($customer['id'], $orders2['customer2']['id']);
1983
    }
1984
1985
    public function testOptimisticLock(): void
1986
    {
1987
        $this->checkFixture($this->db, 'document');
1988
1989
        $documentQuery = new ActiveQuery(Document::class, $this->db);
1990
        $record = $documentQuery->findOne(1);
1991
1992
        $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...
1993
        $record->save();
1994
        $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...
1995
1996
        $record = $documentQuery->findOne(1);
1997
1998
        $record->content = 'Rewrite attempt content';
1999
        $record->version = 0;
2000
        $this->expectException(StaleObjectException::class);
2001
        $record->save();
2002
    }
2003
2004
    /**
2005
     * {@see https://github.com/yiisoft/yii2/issues/9006}
2006
     */
2007
    public function testBit(): void
2008
    {
2009
        $this->checkFixture($this->db, 'bit_values');
2010
2011
        $bitValueQuery = new ActiveQuery(BitValues::class, $this->db);
2012
        $falseBit = $bitValueQuery->findOne(1);
2013
        $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...
2014
2015
        $bitValueQuery = new ActiveQuery(BitValues::class, $this->db);
2016
        $trueBit = $bitValueQuery->findOne(2);
2017
        $this->assertEquals(true, $trueBit->val);
2018
    }
2019
2020
    public function testUpdateAttributes(): void
2021
    {
2022
        $this->checkFixture($this->db, 'order');
2023
2024
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2025
        $order = $orderQuery->findOne(1);
2026
        $newTotal = 978;
2027
        $this->assertSame(1, $order->updateAttributes(['total' => $newTotal]));
0 ignored issues
show
Bug introduced by
The method updateAttributes() 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

2027
        $this->assertSame(1, $order->/** @scrutinizer ignore-call */ updateAttributes(['total' => $newTotal]));

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...
2028
        $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...
2029
2030
        $order = $orderQuery->findOne(1);
2031
        $this->assertEquals($newTotal, $order->total);
2032
2033
        /** @see https://github.com/yiisoft/yii2/issues/12143 */
2034
        $newOrder = new Order($this->db);
2035
        $this->assertTrue($newOrder->getIsNewRecord());
2036
2037
        $newTotal = 200;
2038
        $this->assertSame(0, $newOrder->updateAttributes(['total' => $newTotal]));
2039
        $this->assertTrue($newOrder->getIsNewRecord());
2040
        $this->assertEquals($newTotal, $newOrder->total);
2041
    }
2042
2043
    /**
2044
     * Ensure no ambiguous column error occurs if ActiveQuery adds a JOIN.
2045
     *
2046
     * {@see https://github.com/yiisoft/yii2/issues/13757}
2047
     */
2048
    public function testAmbiguousColumnFindOne(): void
2049
    {
2050
        $this->checkFixture($this->db, 'customer');
2051
2052
        $customerQuery = new CustomerQuery(Customer::class, $this->db);
2053
2054
        $customerQuery->joinWithProfile = true;
2055
2056
        $arClass = $customerQuery->findOne(1);
2057
2058
        $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

2058
        $this->assertTrue($arClass->/** @scrutinizer ignore-call */ refresh());
Loading history...
2059
2060
        $customerQuery->joinWithProfile = false;
2061
    }
2062
2063
    public function testCustomARRelation(): void
2064
    {
2065
        $this->checkFixture($this->db, 'order_item');
2066
2067
        $orderItem = new ActiveQuery(OrderItem::class, $this->db);
2068
2069
        $orderItem = $orderItem->findOne(1);
2070
2071
        $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...
2072
    }
2073
2074
    public function testGetAttributes(): void
2075
    {
2076
        $attributesExpected = [];
2077
        $this->checkFixture($this->db, 'customer');
2078
2079
        $attributesExpected['id'] = 1;
2080
        $attributesExpected['email'] = '[email protected]';
2081
        $attributesExpected['name'] = 'user1';
2082
        $attributesExpected['address'] = 'address1';
2083
        $attributesExpected['status'] = 1;
2084
2085
        if ($this->driverName === 'pgsql') {
2086
            $attributesExpected['bool_status'] = true;
2087
        }
2088
2089
        if ($this->driverName === 'oci') {
2090
            $attributesExpected['bool_status'] = '1';
2091
        }
2092
2093
        $attributesExpected['profile_id'] = 1;
2094
2095
        $customer = new ActiveQuery(Customer::class, $this->db);
2096
2097
        $attributes = $customer->findOne(1)->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Did you maybe mean getAttribute()? ( Ignorable by Annotation )

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

2097
        $attributes = $customer->findOne(1)->/** @scrutinizer ignore-call */ getAttributes();

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...
2098
2099
        $this->assertEquals($attributes, $attributesExpected);
2100
    }
2101
2102
    public function testGetAttributesExcept(): void
2103
    {
2104
        $this->checkFixture($this->db, 'customer');
2105
2106
        $customer = new ActiveQuery(Customer::class, $this->db);
2107
2108
        $attributes = $customer->findOne(1)->getAttributes(null, ['status', 'bool_status', 'profile_id']);
2109
2110
        $this->assertEquals(
2111
            $attributes,
2112
            ['id' => 1, 'email' => '[email protected]', 'name' => 'user1', 'address' => 'address1']
2113
        );
2114
    }
2115
2116
    public function testFields(): void
2117
    {
2118
        $this->checkFixture($this->db, 'order_item');
2119
2120
        $orderItem = new ActiveQuery(OrderItem::class, $this->db);
2121
2122
        $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

2122
        $fields = $orderItem->findOne(['order_id' => 1, 'item_id' => 2])->/** @scrutinizer ignore-call */ fields();
Loading history...
2123
2124
        $this->assertEquals(
2125
            $fields,
2126
            ['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => '40', 'price' => 20]
2127
        );
2128
    }
2129
2130
    public function testGetOldAttribute(): void
2131
    {
2132
        $this->checkFixture($this->db, 'customer');
2133
2134
        $customer = new ActiveQuery(Customer::class, $this->db);
2135
2136
        $query = $customer->findOne(1);
2137
        $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 getAttribute()? ( Ignorable by Annotation )

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

2137
        $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...
2138
        $this->assertEquals($query->getAttributes(), $query->getOldAttributes());
0 ignored issues
show
Bug introduced by
The method getOldAttributes() does not exist on Yiisoft\ActiveRecord\ActiveRecordInterface. Did you maybe mean getAttribute()? ( Ignorable by Annotation )

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

2138
        $this->assertEquals($query->getAttributes(), $query->/** @scrutinizer ignore-call */ getOldAttributes());

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...
2139
2140
        $query->setAttribute('name', 'samdark');
2141
        $this->assertEquals('samdark', $query->getAttribute('name'));
2142
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2143
        $this->assertNotEquals($query->getAttribute('name'), $query->getOldAttribute('name'));
2144
    }
2145
2146
    public function testGetOldAttributes(): void
2147
    {
2148
        $attributes = [];
2149
        $attributesNew = [];
2150
        $this->checkFixture($this->db, 'customer');
2151
2152
        $attributes['id'] = 1;
2153
        $attributes['email'] = '[email protected]';
2154
        $attributes['name'] = 'user1';
2155
        $attributes['address'] = 'address1';
2156
        $attributes['status'] = 1;
2157
2158
        if ($this->driverName === 'pgsql') {
2159
            $attributes['bool_status'] = true;
2160
        }
2161
2162
        if ($this->driverName === 'oci') {
2163
            $attributes['bool_status'] = '1';
2164
        }
2165
2166
        $attributes['profile_id'] = 1;
2167
2168
        $customer = new ActiveQuery(Customer::class, $this->db);
2169
2170
        $query = $customer->findOne(1);
2171
        $this->assertEquals($query->getAttributes(), $attributes);
2172
        $this->assertEquals($query->getAttributes(), $query->getOldAttributes());
2173
2174
        $query->setAttribute('name', 'samdark');
2175
        $attributesNew['id'] = 1;
2176
        $attributesNew['email'] = '[email protected]';
2177
        $attributesNew['name'] = 'samdark';
2178
        $attributesNew['address'] = 'address1';
2179
        $attributesNew['status'] = 1;
2180
2181
        if ($this->driverName === 'pgsql') {
2182
            $attributesNew['bool_status'] = true;
2183
        }
2184
2185
        if ($this->driverName === 'oci') {
2186
            $attributesNew['bool_status'] = '1';
2187
        }
2188
2189
        $attributesNew['profile_id'] = 1;
2190
2191
        $this->assertEquals($attributesNew, $query->getAttributes());
2192
        $this->assertEquals($attributes, $query->getOldAttributes());
2193
        $this->assertNotEquals($query->getAttributes(), $query->getOldAttributes());
2194
    }
2195
2196
    public function testIsAttributeChanged(): void
2197
    {
2198
        $this->checkFixture($this->db, 'customer');
2199
2200
        $customer = new ActiveQuery(Customer::class, $this->db);
2201
2202
        $query = $customer->findOne(1);
2203
        $this->assertEquals('user1', $query->getAttribute('name'));
2204
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2205
2206
        $query->setAttribute('name', 'samdark');
2207
        $this->assertEquals('samdark', $query->getAttribute('name'));
2208
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2209
        $this->assertNotEquals($query->getAttribute('name'), $query->getOldAttribute('name'));
2210
        $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

2210
        $this->assertTrue($query->/** @scrutinizer ignore-call */ isAttributeChanged('name', true));
Loading history...
2211
    }
2212
2213
    public function testIsAttributeChangedNotIdentical(): void
2214
    {
2215
        $this->checkFixture($this->db, 'customer');
2216
2217
        $customer = new ActiveQuery(Customer::class, $this->db);
2218
2219
        $query = $customer->findOne(1);
2220
        $this->assertEquals('user1', $query->getAttribute('name'));
2221
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2222
2223
        $query->setAttribute('name', 'samdark');
2224
        $this->assertEquals('samdark', $query->getAttribute('name'));
2225
        $this->assertEquals('user1', $query->getOldAttribute('name'));
2226
        $this->assertNotEquals($query->getAttribute('name'), $query->getOldAttribute('name'));
2227
        $this->assertTrue($query->isAttributeChanged('name', false));
2228
    }
2229
2230
    public function testCheckRelationUnknownPropertyException(): void
2231
    {
2232
        $this->checkFixture($this->db, 'customer');
2233
2234
        $customer = new ActiveQuery(Customer::class, $this->db);
2235
2236
        $query = $customer->findOne(1);
2237
2238
        $this->expectException(UnknownPropertyException::class);
2239
        $this->expectExceptionMessage('Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer::noExist');
2240
        $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...
2241
    }
2242
2243
    public function testCheckRelationInvalidCallException(): void
2244
    {
2245
        $this->checkFixture($this->db, 'customer');
2246
2247
        $customer = new ActiveQuery(Customer::class, $this->db);
2248
2249
        $query = $customer->findOne(2);
2250
2251
        $this->expectException(InvalidCallException::class);
2252
        $this->expectExceptionMessage(
2253
            'Getting write-only property: Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer::ordersReadOnly'
2254
        );
2255
        $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...
2256
    }
2257
2258
    public function testGetRelationInvalidArgumentException(): void
2259
    {
2260
        $this->checkFixture($this->db, 'customer');
2261
2262
        $customer = new ActiveQuery(Customer::class, $this->db);
2263
2264
        $query = $customer->findOne(1);
2265
2266
        /** Without throwing exception */
2267
        $this->assertEmpty($query->getRelation('items', false));
2268
2269
        /** Throwing exception */
2270
        $this->expectException(InvalidArgumentException::class);
2271
        $this->expectExceptionMessage(
2272
            'Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer has no relation named "items".'
2273
        );
2274
        $query->getRelation('items');
2275
    }
2276
2277
    public function testGetRelationInvalidArgumentExceptionHasNoRelationNamed(): void
2278
    {
2279
        $this->checkFixture($this->db, 'customer');
2280
2281
        $customer = new ActiveQuery(Customer::class, $this->db);
2282
2283
        $query = $customer->findOne(1);
2284
2285
        /** Without throwing exception */
2286
        $this->assertEmpty($query->getRelation('item', false));
2287
2288
        $this->expectException(InvalidArgumentException::class);
2289
        $this->expectExceptionMessage(
2290
            'Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer has no relation named "item"'
2291
        );
2292
        $query->getRelation('item');
2293
    }
2294
2295
    public function testGetRelationInvalidArgumentExceptionCaseSensitive(): void
2296
    {
2297
        $this->checkFixture($this->db, 'customer');
2298
2299
        $customer = new ActiveQuery(Customer::class, $this->db);
2300
2301
        $query = $customer->findOne(1);
2302
2303
        $this->assertEmpty($query->getRelation('expensiveorders', false));
2304
2305
        $this->expectException(InvalidArgumentException::class);
2306
        $this->expectExceptionMessage(
2307
            'Relation names are case sensitive. Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer ' .
2308
            'has a relation named "expensiveOrders" instead of "expensiveorders"'
2309
        );
2310
        $query->getRelation('expensiveorders');
2311
    }
2312
2313
    public function testExists(): void
2314
    {
2315
        $this->checkFixture($this->db, 'customer');
2316
2317
        $customer = new ActiveQuery(Customer::class, $this->db);
2318
2319
        $this->assertTrue($customer->where(['id' => 2])->exists());
2320
        $this->assertFalse($customer->where(['id' => 5])->exists());
2321
        $this->assertTrue($customer->where(['name' => 'user1'])->exists());
2322
        $this->assertFalse($customer->where(['name' => 'user5'])->exists());
2323
        $this->assertTrue($customer->where(['id' => [2, 3]])->exists());
2324
        $this->assertTrue($customer->where(['id' => [2, 3]])->offset(1)->exists());
2325
        $this->assertFalse($customer->where(['id' => [2, 3]])->offset(2)->exists());
2326
    }
2327
2328
    public function testUnlink(): void
2329
    {
2330
        $this->checkFixture($this->db, 'customer');
2331
2332
        /** has many without delete */
2333
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2334
        $customer = $customerQuery->findOne(2);
2335
        $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...
2336
        $customer->unlink('ordersWithNullFK', $customer->ordersWithNullFK[1], false);
2337
        $this->assertCount(1, $customer->ordersWithNullFK);
2338
2339
        $orderWithNullFKQuery = new ActiveQuery(OrderWithNullFK::class, $this->db);
2340
        $orderWithNullFK = $orderWithNullFKQuery->findOne(3);
2341
        $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...
2342
        $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...
2343
2344
        /** has many with delete */
2345
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2346
        $customer = $customerQuery->findOne(2);
2347
        $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...
2348
2349
        $customer->unlink('orders', $customer->orders[1], true);
2350
        $this->assertCount(1, $customer->orders);
2351
2352
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2353
        $this->assertNull($orderQuery->findOne(3));
2354
2355
        /** via model with delete */
2356
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2357
        $order = $orderQuery->findOne(2);
2358
        $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...
2359
        $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...
2360
        $order->unlink('items', $order->items[2], true);
2361
        $this->assertCount(2, $order->items);
2362
        $this->assertCount(2, $order->orderItems);
2363
2364
        /** via model without delete */
2365
        $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...
2366
        $order->unlink('itemsWithNullFK', $order->itemsWithNullFK[1], false);
2367
2368
        $this->assertCount(1, $order->itemsWithNullFK);
2369
        $this->assertCount(2, $order->orderItems);
2370
    }
2371
2372
    public function testUnlinkAllAndConditionSetNull(): void
2373
    {
2374
        $this->checkFixture($this->db, 'order_item_with_null_fk');
2375
2376
        /** in this test all orders are owned by customer 1 */
2377
        $orderWithNullFKInstance = new OrderWithNullFK($this->db);
2378
        $orderWithNullFKInstance->updateAll(['customer_id' => 1]);
2379
2380
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2381
        $customer = $customerQuery->findOne(1);
2382
        $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...
2383
        $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...
2384
2385
        $orderWithNullFKQuery = new ActiveQuery(OrderWithNullFK::class, $this->db);
2386
        $this->assertEquals(3, $orderWithNullFKQuery->count());
2387
2388
        $customer->unlinkAll('expensiveOrdersWithNullFK');
2389
        $this->assertCount(3, $customer->ordersWithNullFK);
2390
        $this->assertCount(0, $customer->expensiveOrdersWithNullFK);
2391
        $this->assertEquals(3, $orderWithNullFKQuery->count());
2392
2393
        $customer = $customerQuery->findOne(1);
2394
        $this->assertCount(2, $customer->ordersWithNullFK);
2395
        $this->assertCount(0, $customer->expensiveOrdersWithNullFK);
2396
    }
2397
2398
    public function testUnlinkAllAndConditionDelete(): void
2399
    {
2400
        $this->checkFixture($this->db, 'customer', true);
2401
2402
        /** in this test all orders are owned by customer 1 */
2403
        $orderInstance = new Order($this->db);
2404
        $orderInstance->updateAll(['customer_id' => 1]);
2405
2406
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2407
        $customer = $customerQuery->findOne(1);
2408
        $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...
2409
        $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...
2410
2411
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2412
        $this->assertEquals(3, $orderQuery->count());
2413
2414
        $customer->unlinkAll('expensiveOrders', true);
2415
        $this->assertCount(3, $customer->orders);
2416
        $this->assertCount(0, $customer->expensiveOrders);
2417
        $this->assertEquals(2, $orderQuery->count());
2418
2419
        $customer = $customerQuery->findOne(1);
2420
        $this->assertCount(2, $customer->orders);
2421
        $this->assertCount(0, $customer->expensiveOrders);
2422
    }
2423
2424
    public function testUpdate(): void
2425
    {
2426
        $this->checkFixture($this->db, 'customer');
2427
2428
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2429
        $customer = $customerQuery->findOne(2);
2430
        $this->assertInstanceOf(Customer::class, $customer);
2431
        $this->assertEquals('user2', $customer->getAttribute('name'));
2432
        $this->assertFalse($customer->getIsNewRecord());
2433
        $this->assertEmpty($customer->getDirtyAttributes());
0 ignored issues
show
Bug introduced by
The method getDirtyAttributes() 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

2433
        $this->assertEmpty($customer->/** @scrutinizer ignore-call */ getDirtyAttributes());
Loading history...
2434
2435
        $customer->setAttribute('name', 'user2x');
2436
        $customer->save();
2437
        $this->assertEquals('user2x', $customer->getAttribute('name'));
2438
        $this->assertFalse($customer->getIsNewRecord());
2439
2440
        $customer2 = $customerQuery->findOne(2);
2441
        $this->assertEquals('user2x', $customer2->getAttribute('name'));
2442
2443
        /** no update */
2444
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2445
        $customer = $customerQuery->findOne(1);
2446
2447
        $customer->setAttribute('name', 'user1');
2448
        $this->assertEquals(0, $customer->update());
2449
2450
        /** updateAll */
2451
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2452
        $customer = $customerQuery->findOne(3);
2453
        $this->assertEquals('user3', $customer->getAttribute('name'));
2454
2455
        $ret = $customer->updateAll(['name' => 'temp'], ['id' => 3]);
2456
        $this->assertEquals(1, $ret);
2457
2458
        $customer = $customerQuery->findOne(3);
2459
        $this->assertEquals('temp', $customer->getAttribute('name'));
2460
2461
        $ret = $customer->updateAll(['name' => 'tempX']);
2462
        $this->assertEquals(3, $ret);
2463
2464
        $ret = $customer->updateAll(['name' => 'temp'], ['name' => 'user6']);
2465
        $this->assertEquals(0, $ret);
2466
    }
2467
2468
    public function testUpdateCounters(): void
2469
    {
2470
        $this->checkFixture($this->db, 'order_item', true);
2471
2472
        /** updateCounters */
2473
        $pk = ['order_id' => 2, 'item_id' => 4];
2474
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
2475
        $orderItem = $orderItemQuery->findOne($pk);
2476
        $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...
2477
2478
        $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

2478
        /** @scrutinizer ignore-call */ 
2479
        $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...
2479
        $this->assertTrue($ret);
2480
        $this->assertEquals(0, $orderItem->quantity);
2481
2482
        $orderItem = $orderItemQuery->findOne($pk);
2483
        $this->assertEquals(0, $orderItem->quantity);
2484
2485
        /** updateAllCounters */
2486
        $pk = ['order_id' => 1, 'item_id' => 2];
2487
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
2488
        $orderItem = $orderItemQuery->findOne($pk);
2489
        $this->assertEquals(2, $orderItem->quantity);
2490
2491
        $orderItem = new OrderItem($this->db);
2492
        $ret = $orderItem->updateAllCounters(['quantity' => 3, 'subtotal' => -10], $pk);
2493
        $this->assertEquals(1, $ret);
2494
2495
        $orderItem = $orderItemQuery->findOne($pk);
2496
        $this->assertEquals(5, $orderItem->quantity);
2497
        $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...
2498
    }
2499
2500
    public function testDelete(): void
2501
    {
2502
        $this->checkFixture($this->db, 'customer', true);
2503
2504
        /** delete */
2505
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2506
        $customer = $customerQuery->findOne(2);
2507
        $this->assertInstanceOf(Customer::class, $customer);
2508
        $this->assertEquals('user2', $customer->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Yiisoft\ActiveRecord\ActiveRecordInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2509
2510
        $customer->delete();
2511
2512
        $customer = $customerQuery->findOne(2);
2513
        $this->assertNull($customer);
2514
2515
        /** deleteAll */
2516
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2517
        $customers = $customerQuery->all();
2518
        $this->assertCount(2, $customers);
2519
2520
        $customer = new Customer($this->db);
2521
        $ret = $customer->deleteAll();
2522
        $this->assertEquals(2, $ret);
2523
2524
        $customers = $customerQuery->all();
2525
        $this->assertCount(0, $customers);
2526
2527
        $ret = $customer->deleteAll();
2528
        $this->assertEquals(0, $ret);
2529
    }
2530
2531
    /**
2532
     * {@see https://github.com/yiisoft/yii2/issues/17089}
2533
     */
2534
    public function testViaWithCallable(): void
2535
    {
2536
        $this->checkFixture($this->db, 'order', true);
2537
2538
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2539
2540
        $order = $orderQuery->findOne(2);
2541
2542
        $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...
2543
        $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...
2544
2545
        $this->assertCount(2, $expensiveItems);
2546
        $this->assertEquals(4, $expensiveItems[0]->id);
2547
        $this->assertEquals(5, $expensiveItems[1]->id);
2548
        $this->assertCount(1, $cheapItems);
2549
        $this->assertEquals(3, $cheapItems[0]->id);
2550
    }
2551
2552
    public function testLink(): void
2553
    {
2554
        $this->checkFixture($this->db, 'customer', true);
2555
2556
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2557
        $customer = $customerQuery->findOne(2);
2558
        $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...
2559
2560
        /** has many */
2561
        $order = new Order($this->db);
2562
2563
        $order->total = 100;
2564
        $order->created_at = time();
2565
        $this->assertTrue($order->isNewRecord);
2566
2567
        /** belongs to */
2568
        $order = new Order($this->db);
2569
2570
        $order->total = 100;
2571
        $order->created_at = time();
2572
        $this->assertTrue($order->isNewRecord);
2573
2574
        $customerQuery = new ActiveQuery(Customer::class, $this->db);
2575
        $customer = $customerQuery->findOne(1);
2576
        $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...
2577
2578
        $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

2578
        $order->link('customer', /** @scrutinizer ignore-type */ $customer);
Loading history...
2579
        $this->assertFalse($order->isNewRecord);
2580
        $this->assertEquals(1, $order->customer_id);
2581
        $this->assertEquals(1, $order->customer->primaryKey);
2582
2583
        /** via model */
2584
        $orderQuery = new ActiveQuery(Order::class, $this->db);
2585
        $order = $orderQuery->findOne(1);
2586
        $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...
2587
        $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...
2588
2589
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
2590
        $orderItem = $orderItemQuery->findOne(['order_id' => 1, 'item_id' => 3]);
2591
        $this->assertNull($orderItem);
2592
2593
        $itemQuery = new ActiveQuery(Item::class, $this->db);
2594
        $item = $itemQuery->findOne(3);
2595
        $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

2595
        $order->link('items', /** @scrutinizer ignore-type */ $item, ['quantity' => 10, 'subtotal' => 100]);
Loading history...
2596
        $this->assertCount(3, $order->items);
2597
        $this->assertCount(3, $order->orderItems);
2598
2599
        $orderItemQuery = new ActiveQuery(OrderItem::class, $this->db);
2600
        $orderItem = $orderItemQuery->findOne(['order_id' => 1, 'item_id' => 3]);
2601
        $this->assertInstanceOf(OrderItem::class, $orderItem);
2602
        $this->assertEquals(10, $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...
2603
        $this->assertEquals(100, $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...
2604
    }
2605
2606
    public function testEqual(): void
2607
    {
2608
        $this->checkFixture($this->db, 'customer');
2609
2610
        $customerA = (new ActiveQuery(Customer::class, $this->db))->findOne(1);
2611
        $customerB = (new ActiveQuery(Customer::class, $this->db))->findOne(2);
2612
        $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

2612
        $this->assertFalse($customerA->equals(/** @scrutinizer ignore-type */ $customerB));
Loading history...
2613
2614
        $customerB = (new ActiveQuery(Customer::class, $this->db))->findOne(1);
2615
        $this->assertTrue($customerA->equals($customerB));
2616
2617
        $customerA = (new ActiveQuery(Customer::class, $this->db))->findOne(1);
2618
        $customerB = (new ActiveQuery(Item::class, $this->db))->findOne(1);
2619
        $this->assertFalse($customerA->equals($customerB));
2620
    }
2621
}
2622