Completed
Push — master ( 928d8a...2f7023 )
by Arjay
07:15
created

EloquentDataTable::getPrimaryKeyName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\DataTables;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
8
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
9
use Yajra\DataTables\Exceptions\Exception;
10
11
class EloquentDataTable extends QueryDataTable
12
{
13
    /**
14
     * @var \Illuminate\Database\Eloquent\Builder
15
     */
16
    protected $query;
17
18
    /**
19
     * EloquentEngine constructor.
20
     *
21
     * @param mixed $model
22
     */
23
    public function __construct($model)
24
    {
25
        $builder = $model instanceof Builder ? $model : $model->getQuery();
26
        parent::__construct($builder->getQuery());
27
28
        $this->query = $builder;
29
    }
30
31
    /**
32
     * If column name could not be resolved then use primary key.
33
     *
34
     * @return string
35
     */
36
    protected function getPrimaryKeyName()
37
    {
38
        return $this->query->getModel()->getKeyName();
39
    }
40
41
    /**
42
     * Perform global search for the given keyword.
43
     *
44
     * @param string $keyword
45
     */
46
    protected function globalSearch($keyword)
47
    {
48
        $this->query->where(function ($query) use ($keyword) {
49
            $query = $this->getBaseQueryBuilder($query);
50
51
            collect($this->request->searchableColumnIndex())
52
                ->map(function ($index) {
53
                    return $this->getColumnName($index);
54
                })
55
                ->reject(function ($column) {
56
                    return $this->isBlacklisted($column) && !$this->hasFilterColumn($column);
57
                })
58
                ->each(function ($column) use ($keyword, $query) {
59
                    if ($this->hasFilterColumn($column)) {
60
                        $this->applyFilterColumn($query, $column, $keyword);
61
                    } else {
62
                        if (count(explode('.', $column)) > 1) {
63
                            $this->eagerLoadSearch($query, $column, $keyword);
64
                        } else {
65
                            $this->compileQuerySearch($query, $column, $keyword);
66
                        }
67
                    }
68
69
                    $this->isFilterApplied = true;
70
                });
71
        });
72
    }
73
74
    /**
75
     * Perform search on eager loaded relation column.
76
     *
77
     * @param mixed  $query
78
     * @param string $columnName
79
     * @param string $keyword
80
     */
81
    private function eagerLoadSearch($query, $columnName, $keyword)
82
    {
83
        $eagerLoads     = $this->getEagerLoads();
84
        $parts          = explode('.', $columnName);
85
        $relationColumn = array_pop($parts);
86
        $relation       = implode('.', $parts);
87
        if (in_array($relation, $eagerLoads)) {
88
            $this->compileRelationSearch($query, $relation, $relationColumn, $keyword);
89
        } else {
90
            $this->compileQuerySearch($query, $columnName, $keyword);
91
        }
92
    }
93
94
    /**
95
     * Get eager loads keys if eloquent.
96
     *
97
     * @return array
98
     */
99
    protected function getEagerLoads()
100
    {
101
        return array_keys($this->query->getEagerLoads());
102
    }
103
104
    /**
105
     * Add relation query on global search.
106
     *
107
     * @param Builder $query
108
     * @param string  $relation
109
     * @param string  $column
110
     * @param string  $keyword
111
     */
112
    private function compileRelationSearch($query, $relation, $column, $keyword)
113
    {
114
        $myQuery = clone $this->query;
115
116
        /**
117
         * For compile nested relation, we need store all nested relation as array
118
         * and reverse order to apply where query.
119
         * With this method we can create nested sub query with properly relation.
120
         */
121
122
        /**
123
         * Store all relation data that require in next step
124
         */
125
        $relationChunk = [];
126
127
        /**
128
         * Store last eloquent query builder for get next relation.
129
         */
130
        $lastQuery = $query;
131
132
        $relations    = explode('.', $relation);
133
        $lastRelation = end($relations);
134
        foreach ($relations as $relation) {
135
            $relationType = $myQuery->getModel()->{$relation}();
136
            $myQuery->orWhereHas($relation, function ($builder) use (
137
                $column,
138
                $keyword,
139
                $query,
140
                $relationType,
141
                $relation,
142
                $lastRelation,
143
                &$relationChunk,
144
                &$lastQuery
145
            ) {
146
                $builder->select($this->connection->raw('count(1)'));
147
148
                // We will perform search on last relation only.
149
                if ($relation == $lastRelation) {
150
                    $this->compileQuerySearch($builder, $column, $keyword, '');
151
                }
152
153
                // Put require object to next step!!
154
                $relationChunk[$relation] = [
155
                    'builder'      => $builder,
156
                    'relationType' => $relationType,
157
                    'query'        => $lastQuery,
158
                ];
159
160
                // This is trick make sub query.
161
                $lastQuery = $builder;
162
            });
163
164
            // This is trick to make nested relation by pass previous relation to be next query eloquent builder
165
            $myQuery = $relationType;
166
        }
167
168
        /**
169
         * Reverse them all
170
         */
171
        $relationChunk = array_reverse($relationChunk, true);
172
173
        /**
174
         * Create valuable for use in check last relation
175
         */
176
        end($relationChunk);
177
        $lastRelation = key($relationChunk);
178
        reset($relationChunk);
179
180
        /**
181
         * Walking ...
182
         */
183
        foreach ($relationChunk as $relation => $chunk) {
184
            /** @var Builder $builder */
185
            $builder  = $chunk['builder'];
186
            $query    = $chunk['query'];
187
            $bindings = $builder->getBindings();
188
            $builder  = "({$builder->toSql()}) >= 1";
189
190
            // Check if it last relation we will use orWhereRaw
191
            if ($lastRelation == $relation) {
192
                $relationMethod = "orWhereRaw";
193
            } else {
194
                // For case parent relation of nested relation.
195
                // We must use and for properly query and get correct result
196
                $relationMethod = "whereRaw";
197
            }
198
199
            $query->{$relationMethod}($builder, $bindings);
200
        }
201
    }
202
203
    /**
204
     * Resolve the proper column name be used.
205
     *
206
     * @param string $column
207
     * @return string
208
     */
209
    protected function resolveRelationColumn($column)
210
    {
211
        if (count(explode('.', $column)) > 1) {
212
            $eagerLoads     = $this->getEagerLoads();
213
            $parts          = explode('.', $column);
214
            $relationColumn = array_pop($parts);
215
            $relation       = implode('.', $parts);
216
217
            if (in_array($relation, $eagerLoads)) {
218
                $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
219
            }
220
        }
221
222
        return $column;
223
    }
224
225
    /**
226
     * Join eager loaded relation and get the related column name.
227
     *
228
     * @param string $relation
229
     * @param string $relationColumn
230
     * @return string
231
     * @throws \Yajra\DataTables\Exceptions\Exception
232
     */
233
    protected function joinEagerLoadedColumn($relation, $relationColumn)
234
    {
235
        $table     = '';
236
        $lastQuery = $this->query;
237
        foreach (explode('.', $relation) as $eachRelation) {
238
            $model = $lastQuery->getRelation($eachRelation);
239
            switch (true) {
240
                case $model instanceof BelongsToMany:
241
                    $pivot   = $model->getTable();
242
                    $pivotPK = $model->getExistenceCompareKey();
243
                    $pivotFK = $model->getQualifiedParentKeyName();
244
                    $this->performJoin($pivot, $pivotPK, $pivotFK);
245
246
                    $related = $model->getRelated();
247
                    $table   = $related->getTable();
248
                    $tablePK = $related->getForeignKey();
249
                    $foreign = $pivot . '.' . $tablePK;
250
                    $other   = $related->getQualifiedKeyName();
251
252
                    $lastQuery->addSelect($table . '.' . $relationColumn);
253
                    $this->performJoin($table, $foreign, $other);
254
255
                    break;
256
257
                case $model instanceof HasOneOrMany:
258
                    $table   = $model->getRelated()->getTable();
259
                    $foreign = $model->getQualifiedForeignKeyName();
260
                    $other   = $model->getQualifiedParentKeyName();
261
                    break;
262
263
                case $model instanceof BelongsTo:
264
                    $table   = $model->getRelated()->getTable();
265
                    $foreign = $model->getQualifiedForeignKey();
266
                    $other   = $model->getQualifiedOwnerKeyName();
267
                    break;
268
269
                default:
270
                    throw new Exception('Relation ' . get_class($model) . ' is not yet supported.');
271
            }
272
            $this->performJoin($table, $foreign, $other);
273
            $lastQuery = $model->getQuery();
274
        }
275
276
        return $table . '.' . $relationColumn;
277
    }
278
279
    /**
280
     * Perform join query.
281
     *
282
     * @param string $table
283
     * @param string $foreign
284
     * @param string $other
285
     * @param string $type
286
     */
287
    protected function performJoin($table, $foreign, $other, $type = 'left')
288
    {
289
        $joins = [];
290
        foreach ((array) $this->getBaseQueryBuilder()->joins as $key => $join) {
291
            $joins[] = $join->table;
292
        }
293
294
        if (!in_array($table, $joins)) {
295
            $this->getBaseQueryBuilder()->join($table, $foreign, '=', $other, $type);
296
        }
297
    }
298
}
299