Completed
Push — master ( ae6686...307b8d )
by Tim
9s
created

CollectionProvider::transformCollectionData()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 29
rs 8.439
cc 6
eloc 14
nc 1
nop 2
1
<?php
2
3
namespace OpenSkill\Datatable\Providers;
4
5
use Illuminate\Support\Collection;
6
use OpenSkill\Datatable\Columns\ColumnConfiguration;
7
use OpenSkill\Datatable\Columns\ColumnOrder;
8
use OpenSkill\Datatable\Columns\ColumnSearch;
9
use OpenSkill\Datatable\Data\ResponseData;
10
use OpenSkill\Datatable\Queries\QueryConfiguration;
11
12
/**
13
 * Class CollectionProvider
14
 * @package OpenSkill\Datatable\Providers
15
 *
16
 * Provider that is able to provide data based on a initial passed collection.
17
 */
18
class CollectionProvider implements Provider
19
{
20
    /**
21
     * @var Collection The underlying data
22
     */
23
    private $collection;
24
25
    /**
26
     * @var QueryConfiguration
27
     */
28
    private $queryConfiguration;
29
30
    /**
31
     * @var callable the default global function to check if a row should be included
32
     */
33
    private $defaultGlobalSearchFunction;
34
35
    /**
36
     * @var callable the default global order function
37
     */
38
    private $defaultGlobalOrderFunction;
39
40
    /**
41
     * @var array an array of callables with local search functions to check if the row should be included
42
     */
43
    private $columnSearchFunction = [];
44
45
    /**
46
     * @var array an array that will hold the search functions for each column
47
     */
48
    private $columnConfiguration = [];
49
50
    /**
51
     * @var int the initial count of the items before processing
52
     */
53
    private $totalInitialDataCount;
54
55
    /**
56
     * CollectionProvider constructor.
57
     * @param Collection $collection The collection with the initial data
58
     */
59
    public function __construct(Collection $collection)
60
    {
61
        $this->collection = $collection;
62
        $this->totalInitialDataCount = $collection->count();
63
64
        $this->setupSearch();
65
        $this->setupOrder();
66
    }
67
68
    /**
69
     * Here the DTQueryConfiguration is passed to prepare the provider for the processing of the request.
70
     * This will only be called when the DTProvider needs to handle the request.
71
     * It will never be called when the DTProvider does not need to handle the request.
72
     *
73
     * @param QueryConfiguration $queryConfiguration
74
     * @param ColumnConfiguration[] $columnConfiguration
75
     * @return mixed
76
     */
77
    public function prepareForProcessing(QueryConfiguration $queryConfiguration, array $columnConfiguration)
78
    {
79
        $this->queryConfiguration = $queryConfiguration;
80
        $this->columnConfiguration = $columnConfiguration;
81
82
        // generate a custom search function for each column
83
        foreach ($this->columnConfiguration as $col) {
84
            if (!array_key_exists($col->getName(), $this->columnSearchFunction)) {
85
                $this->columnSearchFunction[$col->getName()] = function ($data, ColumnSearch $search) use ($col) {
86
                    if (str_contains(mb_strtolower($data[$col->getName()]), mb_strtolower($search->searchValue()))) {
87
                        return true;
88
                    }
89
                    return false;
90
                };
91
            }
92
        }
93
    }
94
95
    /**
96
     * This method should process all configurations and prepare the underlying data for the view. It will arrange the
97
     * data and provide the results in a DTData object.
98
     * It will be called after {@link #prepareForProcessing} has been called and needs to return the processed data in
99
     * a DTData object so the Composer can further handle the data.
100
     *
101
     * @return ResponseData The processed data
102
     *
103
     */
104
    public function process()
105
    {
106
        // check if the query configuration is set
107
        if (is_null($this->queryConfiguration) || empty($this->columnConfiguration)) {
108
            throw new \InvalidArgumentException("Provider was not configured. Did you call prepareForProcessing first?");
109
        }
110
111
        // compile the collection first
112
        $this->compileCollection($this->columnConfiguration);
113
114
        // sort
115
        $this->sortCollection();
116
117
        // slice the result into the right size
118
        return new ResponseData(
119
            $this->collection->slice(
120
                $this->queryConfiguration->start(),
121
                $this->queryConfiguration->length()
122
            ),
123
            $this->totalInitialDataCount
124
125
        );
126
    }
127
128
129
    /**
130
     * Will compile the collection into the final collection where operations like search and order can be applied.
131
     * @param ColumnConfiguration[] $columnConfiguration
132
     */
133
    private function compileCollection(array $columnConfiguration)
134
    {
135
        $searchFunc = null;
136
        if ($this->queryConfiguration->isGlobalSearch()) {
137
            $searchFunc = $this->defaultGlobalSearchFunction;
138
        }
139
140
        $this->transformCollectionData($columnConfiguration, $searchFunc);
141
        $this->removeEmptyRowsFromCollection();
142
    }
143
144
    /**
145
     * Transform collection data. Used for searches.
146
     *
147
     * @param ColumnConfiguration[] $columnConfiguration
148
     * @param $searchFunc
149
     */
150
    private function transformCollectionData($columnConfiguration, $searchFunc)
151
    {
152
        $this->collection->transform(function ($data) use ($columnConfiguration, $searchFunc) {
153
            $entry = [];
154
            // for each column call the callback
155
            foreach ($columnConfiguration as $i => $col) {
156
                $func = $col->getCallable();
157
                $entry[$col->getName()] = $func($data);
158
159
                if ($this->queryConfiguration->hasSearchColumn($col->getName())) {
160
                    // column search exists, so check if the column matches the search
161
                    if (!$this->columnSearchFunction[$col->getName()]($entry,
162
                        $this->queryConfiguration->searchColumns()[$col->getName()])
163
                    ) {
164
                        // did not match, so return an empty array, the row will be removed later
165
                        return [];
166
                    }
167
                }
168
            }
169
170
            // also do search right away
171
            if ($this->queryConfiguration->isGlobalSearch()) {
172
                if (!$searchFunc($entry, $this->queryConfiguration->searchValue(), $this->columnConfiguration)) {
173
                    $entry = [];
174
                }
175
            }
176
            return $entry;
177
        });
178
    }
179
180
    /**
181
     * Remove the empty rows from the collection
182
     *
183
     * @see compileCollection
184
     */
185
    private function removeEmptyRowsFromCollection()
186
    {
187
        $this->collection = $this->collection->reject(function ($data) {
188
            if (empty($data)) {
189
                return true;
190
            } else {
191
                return false;
192
            }
193
        });
194
    }
195
196
    /**
197
     * Will accept a search function that should be called for the column with the given name.
198
     * If the function returns true, it will be accepted as search matching
199
     *
200
     * @param string $columnName the name of the column to pass this function to
201
     * @param callable $searchFunction the function for the searching
202
     * @return $this
203
     */
204
    public function searchColumn($columnName, callable $searchFunction)
205
    {
206
        $this->columnSearchFunction[$columnName] = $searchFunction;
207
        return $this;
208
    }
209
210
    /**
211
     * Will accept a global search function for all columns.
212
     * @param callable $searchFunction the search function to determine if a row should be included
213
     * @return $this
214
     */
215
    public function search(callable $searchFunction)
216
    {
217
        $this->defaultGlobalSearchFunction = $searchFunction;
218
        return $this;
219
    }
220
221
    /**
222
     * Will accept a global search function for all columns.
223
     * @param callable $orderFunction the order function to determine the order of the table
224
     * @return $this
225
     */
226
    public function order(callable $orderFunction)
227
    {
228
        $this->defaultGlobalOrderFunction = $orderFunction;
229
        return $this;
230
    }
231
232
    /**
233
     * Will sort the internal collection based on the given query configuration.
234
     * Most tables only support the ordering by just one column, but we will enable sorting on all columns here
235
     */
236
    private function sortCollection()
237
    {
238
        if ($this->queryConfiguration->hasOrderColumn()) {
239
            $order = $this->queryConfiguration->orderColumns();
240
            $orderFunc = $this->defaultGlobalOrderFunction;
241
            $this->collection = $this->collection->sort(function ($first, $second) use ($order, $orderFunc) {
242
                return $orderFunc($first, $second, $order);
243
            });
244
        }
245
    }
246
247
    public function setupSearch()
248
    {
249
        /**
250
         * @param array $data the generated data for this row
251
         * @param string $search the search value to look for
252
         * @param ColumnConfiguration[] $columns the configuration of the columns
253
         * @return bool true if the row should be included in the result, false otherwise
254
         */
255
        $this->defaultGlobalSearchFunction = function ($data, $search, array $columns) {
256
            foreach ($columns as $column) {
257
                if ($column->getSearch()->isSearchable() && str_contains(mb_strtolower($data[$column->getName()]),
258
                        mb_strtolower($search))
259
                ) {
260
                    return true;
261
                }
262
            }
263
            return false;
264
        };
265
    }
266
267
    public function setupOrder()
268
    {
269
        /**
270
         * @param array $first
271
         * @param array $second
272
         * @param ColumnOrder[] $orderColumn
273
         * @return int
274
         */
275
        $this->defaultGlobalOrderFunction = function (array $first, array $second, array $orderColumn) {
276
            foreach($orderColumn as $order) {
277
                if(array_key_exists($order->columnName(), $first)) {
278
                    $value = strnatcmp($first[$order->columnName()], $second[$order->columnName()]);
279
                    if($value == 0) {
280
                       continue;
281
                    }
282
                    if (!$order->isAscending()) {
283
                        return $value * -1;
284
                    }
285
                    return $value;
286
                }
287
            }
288
            return 0;
289
        };
290
    }
291
}