Completed
Push — master ( f1b857...d82134 )
by Nils
02:25
created

CollectionProvider   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 16
Bugs 1 Features 8
Metric Value
wmc 30
c 16
b 1
f 8
lcom 1
cbo 5
dl 0
loc 253
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 23 3
A searchColumn() 0 5 1
A __construct() 0 8 1
A prepareForProcessing() 0 17 4
C compileCollection() 0 41 8
A search() 0 5 1
A order() 0 5 1
A sortCollection() 0 10 2
A setupSearch() 0 19 4
B setupOrder() 0 24 5
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->collection->transform(function ($data) use ($columnConfiguration, $searchFunc) {
141
            $entry = [];
142
            // for each column call the callback
143
            foreach ($columnConfiguration as $i => $col) {
144
                $func = $col->getCallable();
145
                $entry[$col->getName()] = $func($data);
146
147
                if ($this->queryConfiguration->hasSearchColumn($col->getName())) {
148
                    // column search exists, so check if the column matches the search
149
                    if (!$this->columnSearchFunction[$col->getName()]($entry,
150
                        $this->queryConfiguration->searchColumns()[$col->getName()])
151
                    ) {
152
                        // did not match, so return an empty array, the row will be removed later
153
                        return [];
154
                    }
155
                }
156
            }
157
            // also do search right away
158
            if ($this->queryConfiguration->isGlobalSearch()) {
159
                if (!$searchFunc($entry, $this->queryConfiguration->searchValue(), $this->columnConfiguration)) {
160
                    $entry = [];
161
                }
162
            }
163
            return $entry;
164
        });
165
166
        $this->collection = $this->collection->reject(function ($data) {
167
            if (empty($data)) {
168
                return true;
169
            } else {
170
                return false;
171
            }
172
        });
173
    }
174
175
    /**
176
     * Will accept a search function that should be called for the column with the given name.
177
     * If the function returns true, it will be accepted as search matching
178
     *
179
     * @param string $columnName the name of the column to pass this function to
180
     * @param callable $searchFunction the function for the searching
181
     * @return $this
182
     */
183
    public function searchColumn($columnName, callable $searchFunction)
184
    {
185
        $this->columnSearchFunction[$columnName] = $searchFunction;
186
        return $this;
187
    }
188
189
    /**
190
     * Will accept a global search function for all columns.
191
     * @param callable $searchFunction the search function to determine if a row should be included
192
     * @return $this
193
     */
194
    public function search(callable $searchFunction)
195
    {
196
        $this->defaultGlobalSearchFunction = $searchFunction;
197
        return $this;
198
    }
199
200
    /**
201
     * Will accept a global search function for all columns.
202
     * @param callable $orderFunction the order function to determine the order of the table
203
     * @return $this
204
     */
205
    public function order(callable $orderFunction)
206
    {
207
        $this->defaultGlobalOrderFunction = $orderFunction;
208
        return $this;
209
    }
210
211
    /**
212
     * Will sort the internal collection based on the given query configuration.
213
     * Most tables only support the ordering by just one column, but we will enable sorting on all columns here
214
     */
215
    private function sortCollection()
216
    {
217
        if ($this->queryConfiguration->hasOrderColumn()) {
218
            $order = $this->queryConfiguration->orderColumns();
219
            $orderFunc = $this->defaultGlobalOrderFunction;
220
            $this->collection = $this->collection->sort(function ($first, $second) use ($order, $orderFunc) {
221
                return $orderFunc($first, $second, $order);
222
            });
223
        }
224
    }
225
226
    public function setupSearch()
227
    {
228
        /**
229
         * @param array $data the generated data for this row
230
         * @param string $search the search value to look for
231
         * @param ColumnConfiguration[] $columns the configuration of the columns
232
         * @return bool true if the row should be included in the result, false otherwise
233
         */
234
        $this->defaultGlobalSearchFunction = function ($data, $search, array $columns) {
235
            foreach ($columns as $column) {
236
                if ($column->getSearch()->isSearchable() && str_contains(mb_strtolower($data[$column->getName()]),
237
                        mb_strtolower($search))
238
                ) {
239
                    return true;
240
                }
241
            }
242
            return false;
243
        };
244
    }
245
246
    public function setupOrder()
247
    {
248
        /**
249
         * @param array $first
250
         * @param array $second
251
         * @param ColumnOrder[] $orderColumn
252
         * @return int
253
         */
254
        $this->defaultGlobalOrderFunction = function (array $first, array $second, array $orderColumn) {
255
            foreach($orderColumn as $order) {
256
                if(array_key_exists($order->columnName(), $first)) {
257
                    $value = strnatcmp($first[$order->columnName()], $second[$order->columnName()]);
258
                    if($value == 0) {
259
                       continue;
260
                    }
261
                    if (!$order->isAscending()) {
262
                        return $value * -1;
263
                    }
264
                    return $value;
265
                }
266
            }
267
            return 0;
268
        };
269
    }
270
}