Completed
Pull Request — master (#29)
by Tim
02:11
created

QueryBuilderProvider::setupSearch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
namespace OpenSkill\Datatable\Providers;
4
5
use Illuminate\Support\Collection;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Query\Builder as QueryBuilder;
8
use OpenSkill\Datatable\Columns\ColumnConfiguration;
9
use OpenSkill\Datatable\Columns\ColumnOrder;
10
use OpenSkill\Datatable\Columns\ColumnSearch;
11
use OpenSkill\Datatable\Columns\Searchable\DefaultSearchable;
12
use OpenSkill\Datatable\Data\ResponseData;
13
use OpenSkill\Datatable\DatatableException;
14
use OpenSkill\Datatable\Queries\QueryConfiguration;
15
16
/**
17
 * Class CollectionProvider
18
 * @package OpenSkill\Datatable\Providers
19
 *
20
 * Provider that is able to provide data based on a initial passed collection.
21
 */
22
class QueryBuilderProvider implements Provider
23
{
24
    /**
25
     * @var QueryBuilder The original query, as passed to us.
26
     */
27
    private $originalQuery;
28
29
    /**
30
     * @var QueryBuilder The query, before limits are applied
31
     */
32
    private $queryBeforeLimits;
33
34
    /**
35
     * @var QueryBuilder The underlying query
36
     */
37
    private $query;
38
39
    /**
40
     * @var QueryConfiguration
41
     */
42
    private $queryConfiguration;
43
44
    /**
45
     * @var callable the default global function to check if a row should be included
46
     */
47
    private $defaultGlobalSearchFunction;
0 ignored issues
show
Unused Code introduced by
The property $defaultGlobalSearchFunction is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
48
49
    /**
50
     * @var callable the default global order function
51
     */
52
    private $defaultGlobalOrderFunction;
0 ignored issues
show
Unused Code introduced by
The property $defaultGlobalOrderFunction is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
53
54
    /**
55
     * @var array an array of callables with local search functions to check if the row should be included
56
     */
57
    private $columnSearchFunction = [];
58
59
    /**
60
     * @var array an array that will hold the search functions for each column
61
     */
62
    private $columnConfiguration = [];
63
64
    /**
65
     * CollectionProvider constructor.
66
     * @param QueryBuilder $query The query to base the built query on
67
     */
68
    public function __construct(QueryBuilder $query)
69
    {
70
        $this->originalQuery = $query;
71
        $this->query = clone $query;
72
73
        $this->setupSearch();
74
        $this->setupOrder();
75
    }
76
77
    /**
78
     * Here the DTQueryConfiguration is passed to prepare the provider for the processing of the request.
79
     * This will only be called when the DTProvider needs to handle the request.
80
     * It will never be called when the DTProvider does not need to handle the request.
81
     *
82
     * @param QueryConfiguration $queryConfiguration
83
     * @param ColumnConfiguration[] $columnConfiguration
84
     * @return mixed
85
     */
86 View Code Duplication
    public function prepareForProcessing(QueryConfiguration $queryConfiguration, array $columnConfiguration)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
87
    {
88
        $this->queryConfiguration = $queryConfiguration;
89
        $this->columnConfiguration = $columnConfiguration;
90
91
        // generate a custom search function for each column
92
        foreach ($this->columnConfiguration as $col) {
93
            if (!array_key_exists($col->getName(), $this->columnSearchFunction)) {
94
                $this->columnSearchFunction[$col->getName()] = function ($data, ColumnSearch $search) use ($col) {
95
                    if (str_contains(mb_strtolower($data[$col->getName()]), mb_strtolower($search->searchValue()))) {
96
                        return true;
97
                    }
98
                    return false;
99
                };
100
            }
101
        }
102
    }
103
104
    /**
105
     * This method should process all configurations and prepare the underlying data for the view. It will arrange the
106
     * data and provide the results in a DTData object.
107
     * It will be called after {@link #prepareForProcessing} has been called and needs to return the processed data in
108
     * a DTData object so the Composer can further handle the data.
109
     *
110
     * @return ResponseData The processed data
111
     *
112
     */
113
    public function process()
114
    {
115
        // check if the query configuration is set
116
        if (is_null($this->queryConfiguration) || empty($this->columnConfiguration)) {
117
            throw new \InvalidArgumentException("Provider was not configured. Did you call prepareForProcessing first?");
118
        }
119
120
        // compile the query first
121
        $this->compileQuery($this->query, $this->columnConfiguration);
122
123
        // sort
124
        $this->sortQuery();
125
126
        // limit
127
        $this->queryBeforeLimits = clone $this->query;
128
        $this->limitQuery();
129
130
        // original # of items
131
        $filteredItems = $this->originalQuery->count();
132
133
        // # of items in filtered & ordered dataset
134
        $dataCount = $this->queryBeforeLimits->count();
135
136
        // the data for the response
137
        $columns = $this->compileColumnNames();
138
        $response = new Collection($this->query->get($columns));
139
140
        // slice the result into the right size
141
        return new ResponseData(
142
            $response,
143
            $filteredItems,
144
            $dataCount
0 ignored issues
show
Unused Code introduced by
The call to ResponseData::__construct() has too many arguments starting with $dataCount.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
145
        );
146
    }
147
148
    /**
149
     * Will compile the collection into the final collection where operations like search and order can be applied.
150
     *
151
     * @param QueryBuilder $query
152
     * @param ColumnConfiguration[] $columnConfiguration
153
     * @return QueryBuilder
154
     * @throws DatatableException
155
     */
156
    private function compileQuery(QueryBuilder $query, array $columnConfiguration)
157
    {
158
        if ($this->queryConfiguration->isGlobalSearch()) {
159
            $query = $this->compileGlobalQuery($query, $columnConfiguration);
160
        } elseif ($this->queryConfiguration->isColumnSearch()) {
161
            $query = $this->compileColumnQuery($query, $columnConfiguration);
162
        }
163
164
        return $query;
165
    }
166
167
    /**
168
     * When a global (single) search has been done against data in the datatable.
169
     *
170
     * @param QueryBuilder $query
171
     * @param array $columnConfiguration
172
     * @return QueryBuilder
173
     * @throws DatatableException
174
     */
175
    private function compileGlobalQuery(QueryBuilder $query, array $columnConfiguration)
176
    {
177
        foreach ($columnConfiguration as $i => $col) {
178 View Code Duplication
            if ($col->getSearch() == DefaultSearchable::NONE()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
179
                // Don't do anything, this is not a searchable field
180
            } elseif ($col->getSearch() == DefaultSearchable::NORMAL()) {
181
                $query->orWhere($col->getName(), 'LIKE', '%' . $this->queryConfiguration->searchValue() . '%');
182
            } else {
183
                throw new DatatableException('An unsupported DefaultSearchable was provided.');
184
            }
185
        }
186
187
        return $query;
188
    }
189
190
    /**
191
     * When a global query is being performed (ie, a query against a single column)
192
     *
193
     * @param QueryBuilder $query
194
     * @param ColumnConfiguration[] $columnConfiguration
195
     * @return QueryBuilder
196
     * @throws DatatableException
197
     */
198
    private function compileColumnQuery(QueryBuilder $query, array $columnConfiguration)
199
    {
200
        $searchColumns = $this->queryConfiguration->searchColumns();
201
202
        foreach ($searchColumns as $i => $col) {
203
            $column = $this->getColumnFromName($columnConfiguration, $col->columnName());
204
            if (!isset($column))
205
                continue;
206
207 View Code Duplication
            if ($column->getSearch() == DefaultSearchable::NONE()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
208
                // Don't do anything, this is not a searchable field
209
            } elseif ($column->getSearch() == DefaultSearchable::NORMAL()) {
210
                $query->orWhere($col->columnName(), 'LIKE', '%' . $col->searchValue() . '%');
211
            } else {
212
                throw new DatatableException('An unsupported DefaultSearchable was provided.');
213
            }
214
        }
215
216
        return $query;
217
    }
218
219
    /**
220
     * @param ColumnConfiguration[] $columnConfiguration
221
     * @param string $name
222
     * @return ColumnConfiguration
223
     */
224
    private function getColumnFromName($columnConfiguration, $name)
225
    {
226
        foreach ($columnConfiguration as $i => $col) {
227
            if ($col->getName() == $name)
228
                return $col;
229
        }
230
    }
231
232
    /**
233
     * When a global query is being performed (ie, a query against
234
     */
235
    private function compileColumnNames()
236
    {
237
        $columns = [];
238
        foreach($this->columnConfiguration as $column) {
239
            $columns[] = $column->getName();
240
        }
241
242
        return $columns;
243
    }
244
245
    /**
246
     * Will accept a search function that should be called for the column with the given name.
247
     * If the function returns true, it will be accepted as search matching
248
     *
249
     * @param string $columnName the name of the column to pass this function to
250
     * @param callable $searchFunction the function for the searching
251
     * @return $this
252
     */
253
    public function searchColumn($columnName, callable $searchFunction)
254
    {
255
        $this->columnSearchFunction[$columnName] = $searchFunction;
256
257
        return $this;
258
    }
259
260
    /**
261
     * Will accept a global search function for all columns.
262
     * @param callable $searchFunction the search function to determine if a row should be included
263
     * @return $this
264
     */
265
    public function search(callable $searchFunction)
0 ignored issues
show
Unused Code introduced by
The parameter $searchFunction is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
266
    {
267
        return $this;
268
    }
269
270
    /**
271
     * Will accept a global search function for all columns.
272
     * @param callable $orderFunction the order function to determine the order of the table
273
     * @return $this
274
     */
275
    public function order(callable $orderFunction)
0 ignored issues
show
Unused Code introduced by
The parameter $orderFunction is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
276
    {
277
        return $this;
278
    }
279
280
    /**
281
     * Will sort the query based on the given datatable query configuration.
282
     */
283
    private function sortQuery()
284
    {
285
        if ($this->queryConfiguration->hasOrderColumn()) {
286
            $orderColumns = $this->queryConfiguration->orderColumns();
287
288
            foreach($orderColumns as $order) {
289
                $this->query->orderBy($order->columnName(), $order->isAscending() ? 'asc' : 'desc');
290
            }
291
        }
292
    }
293
294
    /**
295
     * Will limit a query hased on the start and length given
296
     */
297
    private function limitQuery()
298
    {
299
        $this->query->skip($this->queryConfiguration->start());
300
        $this->query->limit($this->queryConfiguration->length());
301
    }
302
303
    public function setupSearch()
304
    {
305
    }
306
307
    public function setupOrder()
308
    {
309
    }
310
}