Completed
Push — develop ( c4d556...03e9c6 )
by Nate
03:41
created

Relationship   F

Complexity

Total Complexity 85

Size/Duplication

Total Lines 644
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 85
lcom 1
cbo 9
dl 0
loc 644
ccs 0
cts 321
cp 0
rs 1.956
c 0
b 0
f 0

35 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A findOrCreate() 0 8 2
A findOrFail() 0 8 2
A findOne() 0 8 2
A getCollection() 0 23 3
A getRelationships() 0 8 2
A existingRelationships() 0 6 1
A getQuery() 0 4 1
A add() 0 8 2
A addOne() 0 30 5
A remove() 0 10 3
B save() 0 32 8
A clear() 0 5 1
A reset() 0 6 1
A isMutated() 0 4 1
A exists() 0 4 1
A count() 0 4 1
A delta() 0 31 5
A hasChanged() 0 5 2
A sync() 0 8 1
A objectArray() 0 13 5
A newRelations() 0 7 1
A addToRelations() 0 11 2
A removeFromRelations() 0 7 1
A createRelations() 0 9 2
A insertCollection() 0 9 3
A updateCollection() 0 12 3
A associationQuery() 0 10 2
A create() 0 14 3
A findKey() 0 12 3
A findRelationshipKey() 0 11 3
B resolveElement() 0 22 6
A __get() 0 8 2
A __set() 0 8 2
A __call() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like Relationship often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Relationship, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://github.com/flipboxfactory/craft-element-lists/LICENSE
6
 * @link       https://github.com/flipboxfactory/craft-element-lists/
7
 */
8
9
namespace flipbox\craft\element\lists\relationships;
10
11
use Craft;
12
use craft\base\ElementInterface;
13
use craft\base\Field;
14
use craft\elements\db\ElementQuery;
15
use craft\elements\db\ElementQueryInterface;
16
use craft\helpers\ArrayHelper;
17
use flipbox\craft\element\lists\fields\RelationalInterface;
18
use flipbox\craft\element\lists\queries\AssociationQuery;
19
use flipbox\craft\element\lists\records\Association;
20
use flipbox\organizations\records\UserAssociation;
21
use Tightenco\Collect\Support\Collection;
22
use yii\base\BaseObject;
23
use yii\base\Exception;
24
use yii\base\UnknownPropertyException;
25
use yii\db\QueryInterface;
26
27
/**
28
 * Manages User Types associated to Organization/User associations
29
 *
30
 * @author Flipbox Factory <[email protected]>
31
 * @since 3.0.0
32
 */
33
class Relationship extends BaseObject implements RelationshipInterface
34
{
35
    /**
36
     * The element the relations are related to
37
     *
38
     * @var ElementInterface|null
39
     */
40
    private $element;
41
42
    /**
43
     * The field which accesses the relations
44
     *
45
     * @var RelationalInterface|Field
46
     */
47
    private $field;
48
49
    /**
50
     * @var Collection|null
51
     */
52
    private $relations;
53
54
    /**
55
     * @var bool
56
     */
57
    protected $mutated = false;
58
59
    /**
60
     * @param ElementInterface|null $element
61
     * @param RelationalInterface $field
62
     * @param array $config
63
     */
64
    public function __construct(RelationalInterface $field, ElementInterface $element = null, array $config = [])
65
    {
66
        $this->element = $element;
67
        $this->field = $field;
68
69
        parent::__construct($config);
70
    }
71
72
73
    /************************************************************
74
     * QUERY
75
     ************************************************************/
76
77
    /**
78
     * @param Association|ElementInterface|int|string $object
79
     * @return Association
80
     */
81
    public function findOrCreate($object): Association
82
    {
83
        if (null === ($association = $this->findOne($object))) {
84
            $association = $this->create($object);
85
        }
86
87
        return $association;
88
    }
89
90
    /**
91
     * @param Association|ElementInterface|int|string $object
92
     * @return Association
93
     * @throws Exception
94
     */
95
    public function findOrFail($object): Association
96
    {
97
        if (null === ($association = $this->findOne($object))) {
98
            throw new Exception("Relationship could not be found.");
99
        }
100
101
        return $association;
102
    }
103
104
    /**
105
     * @param Association|ElementInterface|int|string|null $object
106
     * @return Association|null
107
     */
108
    public function findOne($object = null)
109
    {
110
        if (null === ($key = $this->findKey($object))) {
111
            return null;
112
        }
113
114
        return $this->getRelationships()->get($key);
115
    }
116
117
    /************************************************************
118
     * COLLECTIONS
119
     ************************************************************/
120
121
    /**
122
     * @return Collection
123
     */
124
    public function getCollection(): Collection
125
    {
126
        if (null === $this->relations) {
127
            return Collection::make(
128
                $this->field->getQuery($this->element)
0 ignored issues
show
Bug introduced by
The method getQuery does only exist in flipbox\craft\element\li...lds\RelationalInterface, but not in craft\base\Field.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
129
                    ->anyStatus()
130
                    ->all()
131
            );
132
        };
133
134
        $ids = $this->getRelationships()->pluck('targetId')->all();
135
        if (empty($ids)) {
136
            return $this->getRelationships()->pluck('target');
137
        }
138
139
        return new Collection(
140
            $this->field->getQuery($this->element)
141
                ->id($ids)
142
                ->fixedOrder(true)
143
                ->anyStatus()
144
                ->all()
145
        );
146
    }
147
148
    /**
149
     * @inheritDoc
150
     */
151
    public function getRelationships(): Collection
152
    {
153
        if (null === $this->relations) {
154
            $this->relations = $this->existingRelationships();
155
        }
156
157
        return $this->relations;
158
    }
159
    
160
    /**
161
     * @return Collection
162
     */
163
    protected function existingRelationships()
164
    {
165
        return $this->createRelations(
166
            $this->associationQuery()->all()
167
        );
168
    }
169
170
171
    /************************************************************
172
     * QUERY
173
     ************************************************************/
174
175
    /**
176
     * @return ElementQueryInterface
177
     */
178
    public function getQuery(): ElementQueryInterface
179
    {
180
        return $this->field->getQuery($this->element);
0 ignored issues
show
Bug introduced by
The method getQuery does only exist in flipbox\craft\element\li...lds\RelationalInterface, but not in craft\base\Field.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
181
    }
182
183
    /************************************************************
184
     * ADD / REMOVE
185
     ************************************************************/
186
187
    /**
188
     * @inheritDoc
189
     */
190
    public function add($objects, array $attributes = []): RelationshipInterface
191
    {
192
        foreach ($this->objectArray($objects) as $object) {
193
            $this->addOne($object, $attributes);
194
        }
195
196
        return $this;
197
    }
198
199
    /**
200
     * @param $object
201
     * @param array $attributes
202
     * @return RelationshipInterface
203
     */
204
    protected function addOne($object, array $attributes = []): RelationshipInterface
205
    {
206
        $isNew = false;
207
208
        // Check if it's already linked
209
        if (null === ($association = $this->findOne($object))) {
210
            $association = $this->create($object);
211
            $isNew = true;
212
        }
213
214
        // Modify?
215
        if (!empty($attributes)) {
216
            Craft::configure(
217
                $association,
218
                $attributes
219
            );
220
221
            $this->mutated = true;
222
223
            if (!$isNew) {
224
                $this->updateCollection($this->relations, $association);
0 ignored issues
show
Bug introduced by
It seems like $this->relations can be null; however, updateCollection() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
225
            }
226
        }
227
228
        if ($isNew) {
229
            $this->addToRelations($association);
230
        }
231
232
        return $this;
233
    }
234
235
    /**
236
     * @inheritDoc
237
     */
238
    public function remove($objects): RelationshipInterface
239
    {
240
        foreach ($this->objectArray($objects) as $object) {
241
            if (null !== ($key = $this->findKey($object))) {
242
                $this->removeFromRelations($key);
243
            }
244
        }
245
246
        return $this;
247
    }
248
249
250
    /*******************************************
251
     * SAVE
252
     *******************************************/
253
254
    /**
255
     * @return bool
256
     */
257
    public function save(): bool
258
    {
259
        // No changes?
260
        if (!$this->isMutated()) {
261
            return true;
262
        }
263
264
        $success = true;
265
266
        list($save, $delete) = $this->delta();
267
268
        // Delete those removed
269
        foreach ($delete as $relationship) {
270
            if (!$relationship->delete()) {
271
                $success = false;
272
            }
273
        }
274
275
        foreach ($save as $relationship) {
276
            if (!$relationship->save()) {
277
                $success = false;
278
            }
279
        }
280
281
        $this->reset();
282
283
        if (!$success && $this->element) {
284
            $this->element->addError($this->field->handle, 'Unable to save relationship.');
285
        }
286
287
        return $success;
288
    }
289
290
291
    /************************************************************
292
     * UTILITIES
293
     ************************************************************/
294
295
    /**
296
     * @inheritDoc
297
     */
298
    public function clear(): RelationshipInterface
299
    {
300
        $this->newRelations([]);
301
        return $this;
302
    }
303
304
    /**
305
     * @inheritDoc
306
     */
307
    public function reset(): RelationshipInterface
308
    {
309
        $this->relations = null;
310
        $this->mutated = false;
311
        return $this;
312
    }
313
314
    /**
315
     * @inheritDoc
316
     */
317
    public function isMutated(): bool
318
    {
319
        return $this->mutated;
320
    }
321
322
    /**
323
     * @inheritDoc
324
     */
325
    public function exists($object): bool
326
    {
327
        return null !== $this->findKey($object);
328
    }
329
330
    /**
331
     * @inheritDoc
332
     */
333
    public function count()
334
    {
335
        return $this->getCollection()->count();
336
    }
337
338
339
    /************************************************************
340
     * DELTA
341
     ************************************************************/
342
343
    /**
344
     * @inheritDoc
345
     */
346
    protected function delta(): array
347
    {
348
        $existingAssociations = $this->associationQuery()
349
            ->indexBy('targetId')
350
            ->all();
351
352
        $associations = [];
353
        $order = 1;
354
355
        /** @var Association $newAssociation */
356
        foreach ($this->getRelationships() as $newAssociation) {
357
            $association = ArrayHelper::remove(
358
                $existingAssociations,
359
                $newAssociation->getTargetId()
360
            );
361
362
            $newAssociation->sortOrder = $order++;
363
364
            /** @var Association $association */
365
            $association = $association ?: $newAssociation;
366
367
            // Has anything changed?
368
            if (!$association->getIsNewRecord() && !$this->hasChanged($newAssociation, $association)) {
369
                continue;
370
            }
371
372
            $associations[] = $this->sync($association, $newAssociation);
373
        }
374
375
        return [$associations, $existingAssociations];
376
    }
377
378
    /**
379
     * @param Association $new
380
     * @param Association $existing
381
     * @return bool
382
     */
383
    private function hasChanged(Association $new, Association $existing): bool
384
    {
385
        return $this->field->ensureSortOrder() &&
0 ignored issues
show
Bug introduced by
The method ensureSortOrder does only exist in flipbox\craft\element\li...lds\RelationalInterface, but not in craft\base\Field.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
386
            $new->sortOrder != $existing->sortOrder;
387
    }
388
389
    /**
390
     * @param Association $from
391
     * @param Association $to
392
     *
393
     * @return Association
394
     */
395
    private function sync(Association $to, Association $from): Association
396
    {
397
        $to->sortOrder = $from->sortOrder;
398
399
        $to->ignoreSortOrder();
400
401
        return $to;
402
    }
403
404
405
    /*******************************************
406
     * RESOLVERS
407
     *******************************************/
408
409
    /**
410
     * Ensure we're working with an array of objects, not configs, etc
411
     *
412
     * @param array|QueryInterface|Collection|ElementInterface|Association $objects
413
     * @return array
414
     */
415
    protected function objectArray($objects): array
416
    {
417
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
418
            $objects = $objects->all();
419
        }
420
421
        // proper array
422
        if (!is_array($objects) || ArrayHelper::isAssociative($objects)) {
423
            $objects = [$objects];
424
        }
425
426
        return array_filter($objects);
427
    }
428
429
430
    /*******************************************
431
     * COLLECTION UTILS
432
     *******************************************/
433
434
    /**
435
     * @param Association[] $associations
436
     * @param bool $mutated
437
     * @return static
438
     */
439
    protected function newRelations(array $associations, bool $mutated = true): self
440
    {
441
        $this->relations = $this->createRelations($associations);
442
        $this->mutated = $mutated;
443
444
        return $this;
445
    }
446
447
    /**
448
     * @param Association $association
449
     * @return static
450
     */
451
    protected function addToRelations(Association $association): self
452
    {
453
        if (null === $this->relations) {
454
            return $this->newRelations([$association], true);
455
        }
456
457
        $this->insertCollection($this->relations, $association);
458
        $this->mutated = true;
459
460
        return $this;
461
    }
462
463
    /**
464
     * @param int $key
465
     * @return static
466
     */
467
    protected function removeFromRelations(int $key): self
468
    {
469
        $this->relations->forget($key);
470
        $this->mutated = true;
471
472
        return $this;
473
    }
474
475
    /**
476
     * @param array $associations
477
     * @return Collection
478
     */
479
    protected function createRelations(array $associations = []): Collection
480
    {
481
        $collection = new Collection();
482
        foreach ($associations as $association) {
483
            $this->insertCollection($collection, $association);
484
        }
485
486
        return $collection;
487
    }
488
489
    /**
490
     * Position the relationship based on the sort order
491
     *
492
     * @inheritDoc
493
     */
494
    protected function insertCollection(Collection $collection, Association $association)
495
    {
496
        if ($this->field->ensureSortOrder() && $association->sortOrder > 0) {
0 ignored issues
show
Bug introduced by
The method ensureSortOrder does only exist in flipbox\craft\element\li...lds\RelationalInterface, but not in craft\base\Field.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
497
            $collection->splice($association->sortOrder - 1, 0, [$association]);
498
            return;
499
        }
500
501
        $collection->push($association);
502
    }
503
504
    /**
505
     * @inheritDoc
506
     */
507
    protected function updateCollection(Collection $collection, Association $association)
508
    {
509
        if (!$this->field->ensureSortOrder()) {
0 ignored issues
show
Bug introduced by
The method ensureSortOrder does only exist in flipbox\craft\element\li...lds\RelationalInterface, but not in craft\base\Field.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
510
            return;
511
        }
512
513
        if (null !== ($key = $this->findKey($association))) {
0 ignored issues
show
Documentation introduced by
$association is of type object<flipbox\craft\ele...ts\records\Association>, but the function expects a object<flipbox\organizat...ion>|integer|array|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
514
            $collection->offsetUnset($key);
515
        }
516
517
        $this->insertCollection($collection, $association);
518
    }
519
520
521
    /************************************************************
522
     * QUERY
523
     ************************************************************/
524
525
    /**
526
     * @return AssociationQuery
527
     */
528
    protected function associationQuery(): AssociationQuery
529
    {
530
        return Association::find()
531
            ->setSource($this->element->getId() ?: false)
532
            ->setField($this->field)
0 ignored issues
show
Bug introduced by
It seems like $this->field can also be of type object<flipbox\craft\ele...ds\RelationalInterface>; however, flipbox\craft\ember\quer...ributeTrait::setField() does only seem to accept string|array<integer,str...e\FieldInterface>>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
533
            ->orderBy([
534
                'sortOrder' => SORT_ASC
535
            ])
536
            ->limit(null);
537
    }
538
539
540
    /*******************************************
541
     * CREATE
542
     *******************************************/
543
544
    /**
545
     * Create a new relationship object
546
     *
547
     * @param $object
548
     * @return Association
549
     */
550
    protected function create($object): Association
551
    {
552
        if ($object instanceof Association) {
553
            return $object;
554
        }
555
556
        $element = $this->resolveElement($object);
557
558
        return new Association([
559
            'fieldId' => $this->field->id,
560
            'sourceId' => $this->element ? $this->element->getId() : null,
561
            'targetId' => $element->getId()
562
        ]);
563
    }
564
565
566
    /*******************************************
567
     * UTILS
568
     *******************************************/
569
570
    /**
571
     * @param UserAssociation|int|array|null $object
572
     * @return int|null
573
     */
574
    protected function findKey($object = null)
575
    {
576
        if ($object instanceof Association) {
577
            return $this->findRelationshipKey($object->targetId);
578
        }
579
580
        if (null === ($element = $this->resolveElement($object))) {
581
            return null;
582
        }
583
584
        return $this->findRelationshipKey($element->getId());
585
    }
586
587
    /**
588
     * @param $identifier
589
     * @return int|string|null
590
     */
591
    private function findRelationshipKey($identifier)
592
    {
593
        /** @var Association $association */
594
        foreach ($this->getRelationships()->all() as $key => $association) {
595
            if ($association->targetId == $identifier) {
596
                return $key;
597
            }
598
        }
599
600
        return null;
601
    }
602
603
    /**
604
     * @param ElementInterface|Association|int|array|null $element
605
     * @return ElementInterface|null
606
     */
607
    protected function resolveElement($element = null)
608
    {
609
        if (null === $element) {
610
            return null;
611
        }
612
613
        if ($element instanceof ElementInterface) {
614
            return $element;
615
        }
616
617
        if ($element instanceof Association) {
618
            $element = $element->targetId;
619
        }
620
621
        if (is_array($element) &&
622
            null !== ($id = ArrayHelper::getValue($element, 'id'))
623
        ) {
624
            $element = $id;
625
        }
626
627
        return $this->field->resolveElement($element);
0 ignored issues
show
Bug introduced by
The method resolveElement does only exist in flipbox\craft\element\li...lds\RelationalInterface, but not in craft\base\Field.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
628
    }
629
630
631
    /*******************************************
632
     * MAGIC (pass calls onto query)
633
     *******************************************/
634
635
    /**
636
     * @param string $name
637
     * @return mixed
638
     */
639
    public function __get($name)
640
    {
641
        try {
642
            return parent::__get($name);
643
        } catch (UnknownPropertyException $e) {
644
            return $this->getQuery()->{$name};
645
        }
646
    }
647
648
    /**
649
     * @param string $name
650
     * @param mixed $value
651
     */
652
    public function __set($name, $value)
653
    {
654
        try {
655
            return parent::__set($name, $value);
656
        } catch (UnknownPropertyException $e) {
657
            return $this->getQuery()->{$name}($value);
658
        }
659
    }
660
661
    /**
662
     * @param string $name
663
     * @param array $params
664
     * @return mixed
665
     */
666
    public function __call($name, $params)
667
    {
668
        /** @var ElementQuery $query */
669
        $query = $this->getQuery();
670
        if ($query->hasMethod($name)) {
671
            return call_user_func_array([$query, $name], $params);
672
        }
673
674
        return parent::__call($name, $params);
675
    }
676
}
677