Completed
Push — master ( bfa8d8...3a0338 )
by Arjay
02:05
created

EloquentEngine::onlyTrashed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\Datatables\Engines;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Yajra\Datatables\Request;
7
8
/**
9
 * Class EloquentEngine.
10
 *
11
 * @package Yajra\Datatables\Engines
12
 * @author  Arjay Angeles <[email protected]>
13
 */
14
class EloquentEngine extends QueryBuilderEngine
15
{
16
    /**
17
     * Select trashed records in count function for models with soft deletes trait.
18
     * By default we do not select soft deleted records.
19
     *
20
     * @var bool
21
     */
22
    protected $withTrashed = false;
23
24
    /**
25
     * Select only trashed records in count function for models with soft deletes trait.
26
     * By default we do not select soft deleted records.
27
     *
28
     * @var bool
29
     */
30
    protected $onlyTrashed = false;
31
32
    /**
33
     * @var \Illuminate\Database\Eloquent\Builder
34
     */
35
    protected $query;
36
37
    /**
38
     * EloquentEngine constructor.
39
     *
40
     * @param mixed                     $model
41
     * @param \Yajra\Datatables\Request $request
42
     */
43
    public function __construct($model, Request $request)
44
    {
45
        $builder = $model instanceof Builder ? $model : $model->getQuery();
46
        parent::__construct($builder->getQuery(), $request);
47
48
        $this->query = $builder;
49
    }
50
51
    /**
52
     * Counts current query.
53
     *
54
     * @return int
55
     */
56
    public function count()
57
    {
58
        $builder = $this->prepareCountQuery();
59
60
        if ($this->isSoftDeleting()) {
61
            $builder->whereNull($builder->getModel()->getQualifiedDeletedAtColumn());
0 ignored issues
show
Bug introduced by
The method getModel does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\Builder.

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...
Bug introduced by
The method whereNull does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

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...
62
        }
63
64
        if ($this->isOnlyTrashed()) {
65
            $builder->whereNotNull($builder->getModel()->getQualifiedDeletedAtColumn());
0 ignored issues
show
Bug introduced by
The method whereNotNull does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

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...
66
        }
67
68
        $table = $this->connection->raw('(' . $builder->toSql() . ') count_row_table');
0 ignored issues
show
Bug introduced by
The method toSql does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

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...
69
70
        return $this->connection->table($table)
71
                                ->setBindings($builder->getBindings())
0 ignored issues
show
Bug introduced by
The method getBindings does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

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...
72
                                ->count();
73
    }
74
75
    /**
76
     * Check if engine uses soft deletes.
77
     *
78
     * @return bool
79
     */
80
    private function isSoftDeleting()
81
    {
82
        return !$this->withTrashed && !$this->onlyTrashed && $this->modelUseSoftDeletes();
83
    }
84
85
    /**
86
     * Check if model use SoftDeletes trait.
87
     *
88
     * @return boolean
89
     */
90
    private function modelUseSoftDeletes()
91
    {
92
        return in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($this->query->getModel()));
93
    }
94
95
    /**
96
     * Check if engine uses only trashed.
97
     *
98
     * @return bool
99
     */
100
    private function isOnlyTrashed()
101
    {
102
        return $this->onlyTrashed && $this->modelUseSoftDeletes();
103
    }
104
105
    /**
106
     * Change withTrashed flag value.
107
     *
108
     * @param bool $withTrashed
109
     * @return $this
110
     */
111
    public function withTrashed($withTrashed = true)
112
    {
113
        $this->withTrashed = $withTrashed;
114
115
        return $this;
116
    }
117
118
    /**
119
     * Change onlyTrashed flag value.
120
     *
121
     * @param bool $onlyTrashed
122
     * @return $this
123
     */
124
    public function onlyTrashed($onlyTrashed = true)
125
    {
126
        $this->onlyTrashed = $onlyTrashed;
127
128
        return $this;
129
    }
130
131
    /**
132
     * If column name could not be resolved then use primary key.
133
     *
134
     * @return string
135
     */
136
    protected function getPrimaryKeyName()
137
    {
138
        return $this->query->getModel()->getKeyName();
139
    }
140
141
    /**
142
     * Perform global search for the given keyword.
143
     *
144
     * @param string $keyword
145
     */
146
    protected function globalSearch($keyword)
147
    {
148
        $this->query->where(function ($query) use ($keyword) {
149
            $query = $this->getBaseQueryBuilder($query);
150
151
            foreach ($this->request->searchableColumnIndex() as $index) {
152
                $columnName = $this->getColumnName($index);
153
                if ($this->isBlacklisted($columnName) && !$this->hasCustomFilter($columnName)) {
154
                    continue;
155
                }
156
157
                if ($this->hasCustomFilter($columnName)) {
158
                    $this->applyFilterColumn($query, $columnName, $keyword);
159
                } else {
160
                    if (count(explode('.', $columnName)) > 1) {
161
                        $this->eagerLoadSearch($query, $columnName, $keyword);
0 ignored issues
show
Bug introduced by
It seems like $query defined by $this->getBaseQueryBuilder($query) on line 149 can also be of type object<Illuminate\Database\Query\Builder>; however, Yajra\Datatables\Engines...gine::eagerLoadSearch() does only seem to accept object<Illuminate\Database\Eloquent\Builder>, 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...
162
                    } else {
163
                        $this->compileQuerySearch($query, $columnName, $keyword);
164
                    }
165
                }
166
167
                $this->isFilterApplied = true;
168
            }
169
        });
170
    }
171
172
    /**
173
     * Perform search on eager loaded relation column.
174
     *
175
     * @param \Illuminate\Database\Eloquent\Builder $query
176
     * @param string $columnName
177
     * @param string $keyword
178
     */
179
    private function eagerLoadSearch($query, $columnName, $keyword)
180
    {
181
        $eagerLoads     = $this->getEagerLoads();
182
        $parts          = explode('.', $columnName);
183
        $relationColumn = array_pop($parts);
184
        $relation       = implode('.', $parts);
185
        if (in_array($relation, $eagerLoads)) {
186
            $this->compileRelationSearch(
187
                $query,
188
                $relation,
189
                $relationColumn,
190
                $keyword
191
            );
192
        } else {
193
            $this->compileQuerySearch($query, $columnName, $keyword);
194
        }
195
    }
196
197
    /**
198
     * Add relation query on global search.
199
     *
200
     * @param mixed  $query
201
     * @param string $relation
202
     * @param string $column
203
     * @param string $keyword
204
     */
205
    private function compileRelationSearch($query, $relation, $column, $keyword)
206
    {
207
        $myQuery = clone $this->query;
208
209
        /**
210
         * For compile nested relation, we need store all nested relation as array
211
         * and reverse order to apply where query.
212
         * With this method we can create nested sub query with properly relation.
213
         */
214
215
        /**
216
         * Store all relation data that require in next step
217
         */
218
        $relationChunk = [];
219
220
        /**
221
         * Store last eloquent query builder for get next relation.
222
         */
223
        $lastQuery = $query;
224
225
        $relations    = explode('.', $relation);
226
        $lastRelation = end($relations);
227
        foreach ($relations as $relation) {
228
            $relationType = $myQuery->getModel()->{$relation}();
229
            $myQuery->orWhereHas($relation, function ($builder) use (
230
                $column,
231
                $keyword,
232
                $query,
233
                $relationType,
234
                $relation,
235
                $lastRelation,
236
                &$relationChunk,
237
                &$lastQuery
238
            ) {
239
                $builder->select($this->connection->raw('count(1)'));
240
241
                // We will perform search on last relation only.
242
                if ($relation == $lastRelation) {
243
                    $this->compileQuerySearch($builder, $column, $keyword, '');
244
                }
245
246
                // Put require object to next step!!
247
                $relationChunk[$relation] = [
248
                    'builder'      => $builder,
249
                    'relationType' => $relationType,
250
                    'query'        => $lastQuery,
251
                ];
252
253
                // This is trick make sub query.
254
                $lastQuery = $builder;
255
            });
256
257
            // This is trick to make nested relation by pass previous relation to be next query eloquent builder
258
            $myQuery = $relationType;
259
        }
260
261
        /**
262
         * Reverse them all
263
         */
264
        $relationChunk = array_reverse($relationChunk, true);
265
266
        /**
267
         * Create valuable for use in check last relation
268
         */
269
        end($relationChunk);
270
        $lastRelation = key($relationChunk);
271
        reset($relationChunk);
272
273
        /**
274
         * Walking ...
275
         */
276
        foreach ($relationChunk as $relation => $chunk) {
277
            // Prepare variables
278
            $builder  = $chunk['builder'];
279
            $query    = $chunk['query'];
280
            $bindings = $builder->getBindings();
281
            $builder  = "({$builder->toSql()}) >= 1";
282
283
            // Check if it last relation we will use orWhereRaw
284
            if ($lastRelation == $relation) {
285
                $relationMethod = "orWhereRaw";
286
            } else {
287
                // For case parent relation of nested relation.
288
                // We must use and for properly query and get correct result
289
                $relationMethod = "whereRaw";
290
            }
291
292
            $query->{$relationMethod}($builder, $bindings);
293
        }
294
    }
295
}
296