Passed
Push — master ( 31bb1c...730203 )
by Ryosuke
01:52
created

applyForCurrentRelation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 9.536
c 0
b 0
f 0
ccs 10
cts 10
cp 1
cc 3
nc 3
nop 3
crap 3
1
<?php
2
3
namespace Mpyw\EloquentHasByNonDependentSubquery;
4
5
use DomainException;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Eloquent\Relations\Relation;
8
9
/**
10
 * Class HasByNonDependentSubqueryMacro
11
 *
12
 * Convert has() and whereHas() constraints to non-dependent subqueries.
13
 */
14
class HasByNonDependentSubqueryMacro
15
{
16
    /**
17
     * @var \Illuminate\Database\Eloquent\Builder
18
     */
19
    protected $query;
20
21
    /**
22
     * HasByNonDependentSubqueryMacro constructor.
23
     *
24
     * @param \Illuminate\Database\Eloquent\Builder $query
25
     */
26 23
    public function __construct(Builder $query)
27
    {
28 23
        $this->query = $query;
29
    }
30
31
    /**
32
     * @param  string|string[]                       $relationMethod
33
     * @param  callable[]|null[]                     $constraints
34
     * @return \Illuminate\Database\Eloquent\Builder
35
     */
36 20
    public function has($relationMethod, ?callable ...$constraints): Builder
37
    {
38 20
        return $this->apply($relationMethod, 'whereIn', ...$constraints);
39
    }
40
41
    /**
42
     * @param  string|string[]                       $relationMethod
43
     * @param  callable[]|null[]                     $constraints
44
     * @return \Illuminate\Database\Eloquent\Builder
45
     */
46 1
    public function orHas($relationMethod, ?callable ...$constraints): Builder
47
    {
48 1
        return $this->apply($relationMethod, 'orWhereIn', ...$constraints);
49
    }
50
51
    /**
52
     * @param  string|string[]                       $relationMethod
53
     * @param  callable[]|null[]                     $constraints
54
     * @return \Illuminate\Database\Eloquent\Builder
55
     */
56 1
    public function doesntHave($relationMethod, ?callable ...$constraints): Builder
57
    {
58 1
        return $this->apply($relationMethod, 'whereNotIn', ...$constraints);
59
    }
60
61
    /**
62
     * @param  string|string[]                       $relationMethod
63
     * @param  callable[]|null[]                     $constraints
64
     * @return \Illuminate\Database\Eloquent\Builder
65
     */
66 1
    public function orDoesntHave($relationMethod, ?callable ...$constraints): Builder
67
    {
68 1
        return $this->apply($relationMethod, 'orWhereNotIn', ...$constraints);
69
    }
70
71
    /**
72
     * Parse nested constraints and iterate them to apply.
73
     *
74
     * @param  string|string[]                       $relationMethod
75
     * @param  string                                $whereInMethod
76
     * @param  callable[]|null[]                     $constraints
77
     * @return \Illuminate\Database\Eloquent\Builder
78
     */
79 23
    protected function apply($relationMethod, string $whereInMethod, ?callable ...$constraints): Builder
80
    {
81
        // Extract dot-chained expressions
82 23
        $relationMethods = is_string($relationMethod) ? explode('.', $relationMethod) : array_values($relationMethod);
83
84
        // Pick the first relation if exists
85 23
        if ($currentRelationMethod = array_shift($relationMethods)) {
86 23
            $this->applyForCurrentRelation(
87 23
                $currentRelationMethod,
88
                $whereInMethod,
89
                function (Relation $query) use ($relationMethods, $whereInMethod, $constraints) {
90
                    // Apply optional constraints
91 20
                    if ($currentConstraints = array_shift($constraints)) {
92 4
                        $currentConstraints($query);
93
                    }
94
                    // Apply relations nested under
95 20
                    if ($relationMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $relationMethods of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
96 3
                        (new static($query->getQuery()))->apply($relationMethods, $whereInMethod, ...$constraints);
97
                    }
98 23
                }
99
            );
100
        }
101
102 20
        return $this->query;
103
    }
104
105
    /**
106
     * Apply the current relation as a non-dependent subquery.
107
     *
108
     * @param string        $relationMethod
109
     * @param string        $whereInMethod
110
     * @param null|callable $constraints
111
     */
112 23
    protected function applyForCurrentRelation(string $relationMethod, string $whereInMethod, callable $constraints): void
113
    {
114
        // Unlike a JOIN-based approach, you don't need give table aliases.
115
        // Table names are never conflicted.
116 23
        if (preg_match('/\s+as\s+/i', $relationMethod)) {
117 1
            throw new DomainException('Table aliases are not supported.');
118
        }
119
120
        // Create a Relation instance
121 22
        $relation = $this->query->getRelation($relationMethod);
0 ignored issues
show
Bug introduced by
The method getRelation() does not seem to exist on object<Illuminate\Database\Eloquent\Builder>.

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...
122
123
        // Validate the relation and recognize key names
124 22
        $keys = new Keys($relation);
125
126
        // Apply optional constraints and relations nested under
127 20
        $constraints($relation);
128
129
        // Add an "whereIn" constraints for a non-dependent subquery
130 20
        $relation->select($keys->getQualifiedRelatedKeyName());
131 20
        if ($keys->needsPolymorphicRelatedConstraints()) {
132 3
            $relation->where($keys->getQualifiedRelatedMorphType(), $keys->getRelatedMorphClass());
133
        }
134 20
        $this->query->{$whereInMethod}($keys->getQualifiedSourceKeyName(), $relation->getQuery());
135
    }
136
}
137