Completed
Push — 5.1 ( c7eca6...1f8916 )
by Jarek
05:49
created

Joiner::getJoinKeys()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 24
rs 8.5125
cc 6
eloc 12
nc 6
nop 1
1
<?php
2
3
namespace Sofa\Eloquence\Relations;
4
5
use LogicException;
6
use Illuminate\Database\Query\Builder;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Query\JoinClause as Join;
9
use Illuminate\Database\Eloquent\Relations\MorphTo;
10
use Illuminate\Database\Eloquent\Relations\Relation;
11
use Illuminate\Database\Eloquent\Relations\BelongsTo;
12
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
13
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
14
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
15
use Illuminate\Database\Eloquent\Relations\MorphOneOrMany;
16
use Sofa\Eloquence\Contracts\Relations\Joiner as JoinerContract;
17
18
class Joiner implements JoinerContract
19
{
20
    /**
21
     * Processed query instance.
22
     *
23
     * @var \Illuminate\Database\Query\Builder
24
     */
25
    protected $query;
26
27
    /**
28
     * Parent model.
29
     *
30
     * @var \Illuminate\Database\Eloquent\Model
31
     */
32
    protected $model;
33
34
    /**
35
     * Create new joiner instance.
36
     *
37
     * @param \Illuminate\Database\Query\Builder
38
     * @param \Illuminate\Database\Eloquent\Model
39
     */
40
    public function __construct(Builder $query, Model $model)
41
    {
42
        $this->query = $query;
43
        $this->model = $model;
44
    }
45
46
    /**
47
     * Join related tables.
48
     *
49
     * @param  string $target
50
     * @param  string $type
51
     * @return \Illuminate\Database\Eloquent\Model
52
     */
53
    public function join($target, $type = 'inner')
54
    {
55
        $related = $this->model;
56
57
        foreach (explode('.', $target) as $segment) {
58
            $related = $this->joinSegment($related, $segment, $type);
59
        }
60
61
        return $related;
62
    }
63
64
    /**
65
     * Left join related tables.
66
     *
67
     * @param  string $target
68
     * @return \Illuminate\Database\Eloquent\Model
69
     */
70
    public function leftJoin($target)
71
    {
72
        return $this->join($target, 'left');
73
    }
74
75
    /**
76
     * Right join related tables.
77
     *
78
     * @param  string $target
79
     * @return \Illuminate\Database\Eloquent\Model
80
     */
81
    public function rightJoin($target)
82
    {
83
        return $this->join($target, 'right');
84
    }
85
86
    /**
87
     * Join relation's table accordingly.
88
     *
89
     * @param  \Illuminate\Database\Eloquent\Model $parent
90
     * @param  string $segment
91
     * @param  string $type
92
     * @return \Illuminate\Database\Eloquent\Model
93
     */
94
    protected function joinSegment(Model $parent, $segment, $type)
95
    {
96
        $relation = $parent->{$segment}();
97
        $related  = $relation->getRelated();
98
        $table    = $related->getTable();
99
100
        if ($relation instanceof BelongsToMany || $relation instanceof HasManyThrough) {
101
            $this->joinIntermediate($parent, $relation, $type);
102
        }
103
104
        if (!$this->alreadyJoined($join = $this->getJoinClause($parent, $relation, $table, $type))) {
105
            $this->query->joins[] = $join;
106
        }
107
108
        return $related;
109
    }
110
111
    /**
112
     * Determine whether the related table has been already joined.
113
     *
114
     * @param  \Illuminate\Database\Query\JoinClause $join
115
     * @return boolean
116
     */
117
    protected function alreadyJoined(Join $join)
118
    {
119
        return in_array($join, (array) $this->query->joins);
120
    }
121
122
    /**
123
     * Get the join clause for related table.
124
     *
125
     * @param  \Illuminate\Database\Eloquent\Model $parent
126
     * @param  \Illuminate\Database\Eloquent\Relations\Relation $relation
127
     * @param  string $type
128
     * @param  string $table
129
     * @return \Illuminate\Database\Query\JoinClause
130
     */
131
    protected function getJoinClause(Model $parent, Relation $relation, $table, $type)
132
    {
133
        list($fk, $pk) = $this->getJoinKeys($relation);
134
135
        $join = (new Join($type, $table))->on($fk, '=', $pk);
136
137
        if ($relation instanceof MorphOneOrMany) {
138
            $join->where($relation->getMorphType(), '=', $parent->getMorphClass());
139
        }
140
141
        return $join;
142
    }
143
144
    /**
145
     * Join pivot or 'through' table.
146
     *
147
     * @param  \Illuminate\Database\Eloquent\Model $parent
148
     * @param  \Illuminate\Database\Eloquent\Relations\Relation $relation
149
     * @param  string $type
150
     * @return void
151
     */
152
    protected function joinIntermediate(Model $parent, Relation $relation, $type)
153
    {
154
        if ($relation instanceof BelongsToMany) {
155
            $table = $relation->getTable();
156
            $fk    = $relation->getForeignKey();
157
        } else {
158
            $table = $relation->getParent()->getTable();
159
            $fk    = $table.'.'.$parent->getForeignKey();
160
        }
161
162
        $pk = $parent->getQualifiedKeyName();
163
164
        if (!$this->alreadyJoined($join = (new Join($type, $table))->on($fk, '=', $pk))) {
165
            $this->query->joins[] = $join;
166
        }
167
    }
168
169
    /**
170
     * Get pair of the keys from relation in order to join the table.
171
     *
172
     * @param  \Illuminate\Database\Eloquent\Relations\Relation $relation
173
     * @return array
174
     *
175
     * @throws \LogicException
176
     */
177
    protected function getJoinKeys(Relation $relation)
178
    {
179
        if ($relation instanceof MorphTo) {
180
            throw new LogicException("MorphTo relation cannot be joined.");
181
        }
182
183
        if ($relation instanceof HasOneOrMany) {
184
            return [$relation->getForeignKey(), $relation->getQualifiedParentKeyName()];
185
        }
186
187
        if ($relation instanceof BelongsTo) {
188
            return [$relation->getQualifiedForeignKey(), $relation->getQualifiedOtherKeyName()];
189
        }
190
191
        if ($relation instanceof BelongsToMany) {
192
            return [$relation->getOtherKey(), $relation->getRelated()->getQualifiedKeyName()];
193
        }
194
195
        if ($relation instanceof HasManyThrough) {
196
            $fk = $relation->getRelated()->getTable().'.'.$relation->getParent()->getForeignKey();
197
198
            return [$fk, $relation->getParent()->getQualifiedKeyName()];
199
        }
200
    }
201
}
202