Completed
Push — master ( 4bef56...de577b )
by Timo
15s
created

DataTable::_getData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 0
crap 3
1
<?php
2
3
namespace hamburgscleanest\DataTables\Models;
4
5
use hamburgscleanest\DataTables\Facades\TableRenderer;
6
use hamburgscleanest\DataTables\Interfaces\ColumnFormatter;
7
use hamburgscleanest\DataTables\Interfaces\HeaderFormatter;
8
use Illuminate\Database\Eloquent\Builder;
9
use Illuminate\Database\Eloquent\Model;
10
use Illuminate\Support\Collection;
11
use Illuminate\Support\Facades\Schema;
12
use RuntimeException;
13
14
/**
15
 * Class DataTable
16
 * @package hamburgscleanest\DataTables\Models
17
 */
18
class DataTable {
19
20
    /** @var Builder */
21
    private $_queryBuilder;
22
23
    /** @var array */
24
    private $_headerFormatters = [];
25
26
    /** @var array */
27
    private $_components = [];
28
29
    /** @var string */
30
    private $_classes;
31
32
    /** @var array */
33
    private $_columns = [];
34
35
    /** @var Model */
36
    private $_model;
37
38
    /** @var array */
39
    private $_relations = [];
40
41
    /** @var string */
42
    private $_noDataHtml = '<div>no data</div>';
43
44
    /**
45
     * Set the base model whose data is displayed in the table.
46
     *
47
     * @param string $modelName
48
     * @param array $columns
49 53
     * @return $this
50
     * @throws \RuntimeException
51 53
     */
52
    public function model(string $modelName, array $columns = []) : DataTable
53 2
    {
54
        if (!\is_subclass_of($modelName, Model::class))
55
        {
56 51
            throw new RuntimeException('Class "' . $modelName . '" is not an active record!');
57 51
        }
58
59 51
        $this->_model = new $modelName;
60
        $this->_queryBuilder = $this->_model->newQuery();
61
        $this->_columns = $this->_fetchColumns($columns);
62
63
        return $this;
64
    }
65
66
    /**
67
     * Returns an array of Column objects which may be bound to a formatter.
68 52
     *
69
     * @param array $columns
70 52
     * @return array
71 52
     */
72
    private function _fetchColumns(array $columns) : array
73 46
    {
74 46
        $columnModels = [];
75
        foreach ($columns as $column => $formatter)
76
        {
77 52
            [$column, $formatter] = $this->_setColumnFormatter($column, $formatter);
78
            $columnModels[] = new Column($column, $formatter, $this->_model);
79
        }
80
81
        return $columnModels;
82
    }
83
84
    /**
85 46
     * @param $column
86
     * @param $formatter
87 46
     * @return array
88
     */
89 44
    private function _setColumnFormatter($column, $formatter) : array
90 44
    {
91
        if (\is_int($column))
92
        {
93 46
            $column = $formatter;
94
            $formatter = null;
95
        }
96
97
        return [$column, $formatter];
98
    }
99 19
100
    /**
101 19
     * @return array
102
     */
103
    public function getColumns() : array
104
    {
105
        return $this->_columns;
106
    }
107
108
    /**
109
     * @return Builder
110 2
     */
111
    public function query() : Builder
112 2
    {
113
        return $this->_queryBuilder;
114 2
    }
115
116
    /**
117
     * Displayed columns
118
     *
119
     * @param array $columns
120
     * @return $this
121
     */
122
    public function columns(array $columns) : DataTable
123
    {
124
        $this->_columns += $this->_fetchColumns($columns);
125 27
126
        return $this;
127 27
    }
128 27
129
    /**
130 27
     * Add a component to the data table.
131
     * For example a "Paginator" or a "Sorter".
132
     *
133
     * @param DataComponent $component
134
     *
135
     * @return $this
136
     */
137
    public function addComponent(DataComponent $component) : DataTable
138
    {
139 12
        $this->_components[] = $component->init($this);
140
141 12
        return $this;
142
    }
143 12
144
    /**
145
     * Add a formatter for the column headers.
146
     *
147
     * @param HeaderFormatter $headerFormatter
148
     * @return DataTable
149
     */
150
    public function formatHeaders(HeaderFormatter $headerFormatter) : DataTable
151
    {
152
        $this->_headerFormatters[] = $headerFormatter;
153 2
154
        return $this;
155
    }
156 2
157 2
    /**
158
     * Add a formatter for a column.
159
     *
160 2
     * @param string $columnName
161 2
     * @param ColumnFormatter $columnFormatter
162
     * @return DataTable
163
     */
164 2
    public function formatColumn(string $columnName, ColumnFormatter $columnFormatter) : DataTable
165
    {
166 2
        /** @var Column $column */
167
        $column = \array_first(
168
            $this->_columns,
169 2
            function($index, $column) use ($columnName) {
170
                /** @var Column $column */
171
                return $column->getName() === $columnName;
172
            }
173
        );
174
175
        if ($column !== null)
176
        {
177
            $column->setFormatter($columnFormatter);
178
        }
179 1
180
        return $this;
181 1
    }
182
183 1
    /**
184
     * Add classes to the table.
185
     *
186
     * @param string $classes
187
     *
188
     * @return $this
189
     */
190
    public function classes(string $classes) : DataTable
191
    {
192 3
        $this->_classes = $classes;
193
194 3
        return $this;
195
    }
196 3
197
    /**
198
     * Add a relation to the table.
199
     *
200
     * @param array $relations
201
     * @return DataTable
202
     */
203
    public function with(array $relations) : DataTable
204
    {
205 1
        $this->_relations += $relations;
206
207 1
        return $this;
208
    }
209 1
210
    /**
211
     * Set the HTML which should be displayed when the dataset is empty.
212
     *
213
     * @param string $html
214
     * @return DataTable
215
     */
216
    public function noDataHtml(string $html) : DataTable
217
    {
218
        $this->_noDataHtml = $html;
219 1
220
        return $this;
221 1
    }
222
223 1
    /**
224
     * Set a view which should be displayed when the dataset is empty.
225
     *
226
     * @param string $viewName
227
     * @return DataTable
228
     * @throws \Throwable
229
     */
230
    public function noDataView(string $viewName) : DataTable
231
    {
232 39
        $this->_noDataHtml = \view($viewName)->render();
233
234 39
        return $this;
235
    }
236 38
237
    /**
238 4
     * Renders the table.
239
     *
240
     * @return string
241 34
     * @throws \RuntimeException
242
     */
243 34
    public function render() : string
244 34
    {
245 34
        $data = $this->_getData();
246 34
247
        if ($data->count() === 0)
248
        {
249
            return $this->_noDataHtml;
250
        }
251
252
        $this->_initColumns();
253
254
        return TableRenderer::open($this->_classes) .
255
               TableRenderer::renderHeaders($this->_fetchHeaders(), $this->_headerFormatters) .
256 39
               TableRenderer::renderBody($data, $this->_columns) .
257
               TableRenderer::close();
258 39
    }
259
260 1
    /**
261
     * Get data which should be displayed in the table.
262
     *
263 38
     * @return Collection
264
     *
265
     * @throws \RuntimeException
266 38
     */
267
    private function _getData() : Collection
268 14
    {
269
        if ($this->_queryBuilder === null)
270
        {
271 38
            throw new RuntimeException('Unknown base model!');
272
        }
273
274 38
        $this->_addRelations();
275
276 38
        /** @var DataComponent $component */
277
        foreach ($this->_components as $component)
278 3
        {
279
            $component->transformData();
280 38
        }
281
282 34
        return $this->_setSelection()->_queryBuilder->get();
283
    }
284 34
285
    private function _addRelations() : void
286 2
    {
287
        if (\count($this->_relations) === 0)
288 34
        {
289
            return;
290
        }
291
292
        foreach ($this->_relations as $relation)
293 34
        {
294
            $this->_addJoin($relation, $this->_model->$relation());
295 34
        }
296 34
297
        $this->_queryBuilder->getQuery()->groupBy($this->_model->getTable() . '.' . $this->_model->getKeyName());
298 34
    }
299 34
300 34
    /**
301
     * @param string $relation
302
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relationship
303
     */
304
    private function _addJoin(string $relation, \Illuminate\Database\Eloquent\Relations\Relation $relationship) : void
305
    {
306
        /** @var Model $related */
307
        $related = $relationship->getRelated();
308
309
        $this->_queryBuilder->join(
310
            $related->getTable() . ' AS ' . $relation,
311
            $this->_model->getTable() . '.' . $this->_model->getKeyName(),
312
            '=',
313
            $relation . '.' . $related->getForeignKey()
314
        );
315
    }
316
317
    /**
318
     * @return DataTable
319
     */
320
    private function _setSelection() : DataTable
321
    {
322
        $query = $this->_queryBuilder->getQuery();
323
324
        $columns = $this->_getColumnsForSelect();
325
        if (!empty($columns))
326
        {
327
            $query->selectRaw(
328
                \implode(',',
329
                    \array_map(function($column) {
330
                        return $column->getIdentifier();
331
                    }, $columns)
332
                )
333
            );
334
        }
335
336
        return $this;
337
    }
338
339
    /**
340
     * @return array
341
     */
342
    private function _getColumnsForSelect() : array
343
    {
344
        return \array_filter(
345
            $this->_columns,
346
            function($column) {
347
                return !$column->isMutated();
348
            }
349
        );
350
    }
351
352
    private function _initColumns() : void
353
    {
354
        if (\count($this->_columns) === 0)
355
        {
356
            $this->_columns = $this->_fetchColumns(Schema::getColumnListing($this->_queryBuilder->getQuery()->from));
357
        }
358
    }
359
360
    /**
361
     * @return array
362
     */
363
    private function _fetchHeaders() : array
364
    {
365
        return \array_map(
366
            function($column) {
367
                /** @var Column $column */
368
                return new Header($column->getKey());
369
            },
370
            $this->_columns
371
        );
372
    }
373
}