Passed
Push — master ( dab8d6...352e04 )
by Sergei
02:50
created

MagicRelationsTrait::relationQuery()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 34
rs 9.0444
cc 6
nc 5
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ActiveRecord\Trait;
6
7
use ReflectionException;
8
use ReflectionMethod;
9
use Yiisoft\ActiveRecord\ActiveQueryInterface;
10
use Yiisoft\ActiveRecord\ActiveRecordInterface;
11
use Yiisoft\Db\Exception\InvalidArgumentException;
12
13
use function is_a;
14
use function lcfirst;
15
use function method_exists;
16
use function substr;
17
use function ucfirst;
18
19
/**
20
 * Trait to define {@see ActiveRecordInterface::relationQuery()} method to access relation queries of an ActiveRecord
21
 * instance.
22
 */
23
trait MagicRelationsTrait
24
{
25
    /**
26
     * @inheritdoc
27
     *
28
     * A relation is defined by a getter method which has prefix `get` and suffix `Query` and returns an object
29
     * implementing the {@see ActiveQueryInterface}. Normally this would be a relational {@see ActiveQuery} object.
30
     *
31
     * For example, a relation named `orders` is defined using the following getter method:
32
     *
33
     * ```php
34
     * public function getOrdersQuery(): ActiveQueryInterface
35
     * {
36
     *    return $this->hasMany(Order::class, ['customer_id' => 'id']);
37
     * }
38
     * ```
39
     *
40
     * @throws InvalidArgumentException if the named relation does not exist.
41
     * @throws ReflectionException
42
     */
43
    public function relationQuery(string $name): ActiveQueryInterface
44
    {
45
        $getter = 'get' . ucfirst($name) . 'Query';
46
47
        if (!method_exists($this, $getter)) {
48
            throw new InvalidArgumentException(static::class . ' has no relation named "' . $name . '".');
49
        }
50
51
        $method = new ReflectionMethod($this, $getter);
52
        $type = $method->getReturnType();
53
54
        if (
55
            $type === null
56
            || !is_a('\\' . $type->getName(), ActiveQueryInterface::class, true)
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

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

56
            || !is_a('\\' . $type->/** @scrutinizer ignore-call */ getName(), ActiveQueryInterface::class, true)
Loading history...
57
        ) {
58
            $typeName = $type === null ? 'mixed' : $type->getName();
59
60
            throw new InvalidArgumentException(
61
                'Relation query method "' . static::class . '::' . $getter . '()" should return type "'
62
                . ActiveQueryInterface::class . '", but  returns "' . $typeName . '" type.'
63
            );
64
        }
65
66
        /** relation name is case sensitive, trying to validate it when the relation is defined within this class */
67
        $realName = lcfirst(substr($method->getName(), 3, -5));
68
69
        if ($realName !== $name) {
70
            throw new InvalidArgumentException(
71
                'Relation names are case sensitive. ' . static::class
72
                . " has a relation named \"$realName\" instead of \"$name\"."
73
            );
74
        }
75
76
        return $this->$getter();
77
    }
78
}
79