Completed
Push — master ( 2ce646...0b078f )
by Nils
02:15
created

CollectionProvider   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 14
Bugs 1 Features 7
Metric Value
wmc 25
c 14
b 1
f 7
lcom 1
cbo 6
dl 0
loc 227
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A prepareForProcessing() 0 17 4
A process() 0 23 3
C compileCollection() 0 41 8
A searchColumn() 0 5 1
B __construct() 0 27 5
A search() 0 5 1
A order() 0 5 1
A sortCollection() 0 10 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
        // define search functions
64
        /**
65
         * @param array $data the generated data for this row
66
         * @param string $search the search value to look for
67
         * @param ColumnConfiguration[] $columns the configuration of the columns
68
         * @return bool true if the row should be included in the result, false otherwise
69
         */
70
        $this->defaultGlobalSearchFunction = function ($data, $search, array $columns) {
71
            foreach($columns as $column) {
72
                if ($column->getSearch()->isSearchable() && str_contains(mb_strtolower($data[$column->getName()]), mb_strtolower($search))) {
73
                    return true;
74
                }
75
            }
76
            return false;
77
        };
78
79
        $this->defaultGlobalOrderFunction = function(array $first, array $second, ColumnOrder $orderColumn) {
80
            if(!$orderColumn->isAscending()) {
81
                return strnatcmp($first[$orderColumn->columnName()], $second[$orderColumn->columnName()]) * -1;
82
            }
83
            return strnatcmp($first[$orderColumn->columnName()], $second[$orderColumn->columnName()]);
84
        };
85
    }
86
87
    /**
88
     * Here the DTQueryConfiguration is passed to prepare the provider for the processing of the request.
89
     * This will only be called when the DTProvider needs to handle the request.
90
     * It will never be called when the DTProvider does not need to handle the request.
91
     *
92
     * @param QueryConfiguration $queryConfiguration
93
     * @param ColumnConfiguration[] $columnConfiguration
94
     * @return mixed
95
     */
96
    public function prepareForProcessing(QueryConfiguration $queryConfiguration, array $columnConfiguration)
97
    {
98
        $this->queryConfiguration = $queryConfiguration;
99
        $this->columnConfiguration = $columnConfiguration;
100
101
        // generate a custom search function for each column
102
        foreach ($this->columnConfiguration as $col) {
103
            if (!array_key_exists($col->getName(), $this->columnSearchFunction)) {
104
                $this->columnSearchFunction[$col->getName()] = function ($data, ColumnSearch $search) use ($col) {
105
                    if (str_contains(mb_strtolower($data[$col->getName()]), mb_strtolower($search->searchValue()))) {
106
                        return true;
107
                    }
108
                    return false;
109
                };
110
            }
111
        }
112
    }
113
114
    /**
115
     * This method should process all configurations and prepare the underlying data for the view. It will arrange the
116
     * data and provide the results in a DTData object.
117
     * It will be called after {@link #prepareForProcessing} has been called and needs to return the processed data in
118
     * a DTData object so the Composer can further handle the data.
119
     *
120
     * @return ResponseData The processed data
121
     *
122
     */
123
    public function process()
124
    {
125
        // check if the query configuration is set
126
        if (is_null($this->queryConfiguration) || empty($this->columnConfiguration)) {
127
            throw new \InvalidArgumentException("Provider was not configured. Did you call prepareForProcessing first?");
128
        }
129
130
        // compile the collection first
131
        $this->compileCollection($this->columnConfiguration);
132
133
        // sort
134
        $this->sortCollection();
135
136
        // slice the result into the right size
137
        return new ResponseData(
138
            $this->collection->slice(
139
                $this->queryConfiguration->start(),
140
                $this->queryConfiguration->length()
141
            ),
142
            $this->totalInitialDataCount
143
144
        );
145
    }
146
147
148
    /**
149
     * Will compile the collection into the final collection where operations like search and order can be applied.
150
     * @param ColumnConfiguration[] $columnConfiguration
151
     */
152
    private function compileCollection(array $columnConfiguration)
153
    {
154
        $searchFunc = null;
155
        if ($this->queryConfiguration->isGlobalSearch()) {
156
            $searchFunc = $this->defaultGlobalSearchFunction;
157
        }
158
159
        $this->collection->transform(function ($data) use ($columnConfiguration, $searchFunc) {
160
            $entry = [];
161
            // for each column call the callback
162
            foreach ($columnConfiguration as $i => $col) {
163
                $func = $col->getCallable();
164
                $entry[$col->getName()] = $func($data);
165
166
                if ($this->queryConfiguration->hasSearchColumn($col->getName())) {
167
                    // column search exists, so check if the column matches the search
168
                    if (!$this->columnSearchFunction[$col->getName()]($entry,
169
                        $this->queryConfiguration->searchColumns()[$col->getName()])
170
                    ) {
171
                        // did not match, so return an empty array, the row will be removed later
172
                        return [];
173
                    }
174
                }
175
            }
176
            // also do search right away
177
            if ($this->queryConfiguration->isGlobalSearch()) {
178
                if (!$searchFunc($entry, $this->queryConfiguration->searchValue(), $this->columnConfiguration)) {
179
                    $entry = [];
180
                }
181
            }
182
            return $entry;
183
        });
184
185
        $this->collection = $this->collection->reject(function ($data) {
186
            if (empty($data)) {
187
                return true;
188
            } else {
189
                return false;
190
            }
191
        });
192
    }
193
194
    /**
195
     * Will accept a search function that should be called for the column with the given name.
196
     * If the function returns true, it will be accepted as search matching
197
     *
198
     * @param string $columnName the name of the column to pass this function to
199
     * @param callable $searchFunction the function for the searching
200
     * @return $this
201
     */
202
    public function searchColumn($columnName, callable $searchFunction)
203
    {
204
        $this->columnSearchFunction[$columnName] = $searchFunction;
205
        return $this;
206
    }
207
208
    /**
209
     * Will accept a global search function for all columns.
210
     * @param callable $searchFunction the search function to determine if a row should be included
211
     * @return $this
212
     */
213
    public function search(callable $searchFunction)
214
    {
215
        $this->defaultGlobalSearchFunction = $searchFunction;
216
        return $this;
217
    }
218
219
    /**
220
     * Will accept a global search function for all columns.
221
     * @param callable $orderFunction the order function to determine the order of the table
222
     * @return $this
223
     */
224
    public function order(callable $orderFunction)
225
    {
226
        $this->defaultGlobalOrderFunction = $orderFunction;
227
        return $this;
228
    }
229
230
    /**
231
     * Will sort the internal collection based on the given query configuration.
232
     * All tables only support the ordering by just one column, so if there is ordering just take the first ordering
233
     */
234
    private function sortCollection()
235
    {
236
        if ($this->queryConfiguration->hasOrderColumn()) {
237
            $order = $this->queryConfiguration->orderColumns()[0];
238
            $orderFunc = $this->defaultGlobalOrderFunction;
239
            $this->collection = $this->collection->sort(function ($first, $second) use ($order, $orderFunc) {
240
                return $orderFunc($first, $second, $order);
241
            });
242
        }
243
    }
244
}