Completed
Push — master ( e565c1...597177 )
by Timo
04:24
created

DataTable::columns()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
namespace hamburgscleanest\DataTables\Models;
4
5
use hamburgscleanest\DataTables\Interfaces\ColumnFormatter;
6
use hamburgscleanest\DataTables\Interfaces\HeaderFormatter;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Http\Request;
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 Request */
21
    private $_request;
22
23
    /** @var Builder */
24
    private $_queryBuilder;
25
26
    /** @var array */
27
    private $_headerFormatters = [];
28
29
    /** @var array */
30
    private $_components = [];
31
32
    /** @var string */
33
    private $_classes;
34
35
    /** @var array */
36
    private $_columns = [];
37
38
    /** @var array */
39
    private $_relations = [];
40
41
    /** @var string */
42
    private $_noDataHtml = '<div>no data</div>';
43
44
    /**
45
     * DataTable constructor.
46
     * @param Request $request
47
     */
48 46
    public function __construct(Request $request)
49
    {
50 46
        $this->_request = $request;
51 46
    }
52
53
    /**
54
     * Set the base model whose data is displayed in the table.
55
     *
56
     * @param string $modelName
57
     * @param array $columns
58
     * @return $this
59
     * @throws \RuntimeException
60
     */
61 45
    public function model(string $modelName, array $columns = []): DataTable
62
    {
63 45
        if (!\class_exists($modelName) || !\is_subclass_of($modelName, Model::class))
64
        {
65 2
            throw new RuntimeException('Class "' . $modelName . '" does not exist or is not an active record!');
66
        }
67
68 43
        $this->_queryBuilder = (new $modelName)->newQuery();
69 43
        $this->_columns = $this->_fetchColumns($columns);
70
71 43
        return $this;
72
    }
73
74
    /**
75
     * Returns an array of Column objects which may be bound to a formatter.
76
     *
77
     * @param array $columns
78
     * @return array
79
     */
80 44
    private function _fetchColumns(array $columns): array
81
    {
82 44
        $columnModels = [];
83 44
        foreach ($columns as $column => $formatter)
84
        {
85 38
            [$column, $formatter] = $this->_setColumnFormatter($column, $formatter);
86 38
            $columnModels[] = new Column($column, $formatter);
87
        }
88
89 44
        return $columnModels;
90
    }
91
92
    /**
93
     * @param $column
94
     * @param $formatter
95
     * @return array
96
     */
97 38
    private function _setColumnFormatter($column, $formatter): array
98
    {
99 38
        if (\is_int($column))
100
        {
101 36
            $column = $formatter;
1 ignored issue
show
Coding Style introduced by
Consider using a different name than the parameter $column. This often makes code more readable.
Loading history...
102 36
            $formatter = null;
1 ignored issue
show
Coding Style introduced by
Consider using a different name than the parameter $formatter. This often makes code more readable.
Loading history...
103
        }
104
105 38
        return [$column, $formatter];
106
    }
107
108
    /**
109
     * @return Builder
110
     */
111 19
    public function query(): Builder
112
    {
113 19
        return $this->_queryBuilder;
114
    }
115
116
    /**
117
     * Displayed columns
118
     *
119
     * @param array $columns
120
     * @return $this
121
     */
122 2
    public function columns(array $columns): DataTable
123
    {
124 2
        $this->_columns += $this->_fetchColumns($columns);
125
126 2
        return $this;
127
    }
128
129
    /**
130
     * 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 23
    public function addComponent(DataComponent $component): DataTable
138
    {
139 23
        $component->init($this->_queryBuilder, $this->_request);
140 23
        $this->_components[] = $component;
141
142 23
        return $this;
143
    }
144
145
    /**
146
     * Add a formatter for the column headers.
147
     *
148
     * @param HeaderFormatter $headerFormatter
149
     * @return $this
150
     */
151 8
    public function formatHeaders(HeaderFormatter $headerFormatter): DataTable
152
    {
153 8
        $this->_headerFormatters[] = $headerFormatter;
154
155 8
        return $this;
156
    }
157
158
    /**
159
     * Add a formatter for a column.
160
     *
161
     * @param string $columnName
162
     * @param ColumnFormatter $columnFormatter
163
     * @return DataTable
164
     */
165 2
    public function formatColumn(string $columnName, ColumnFormatter $columnFormatter): DataTable
166
    {
167
        /** @var Column $column */
168 2
        $column = \array_first(
169 2
            $this->_columns,
170
            function ($index, $column) use ($columnName)
171
            {
172
                /** @var Column $column */
173 2
                return $column->getName() === $columnName;
174 2
            }
175
        );
176
177 2
        if ($column !== null)
178
        {
179 2
            $column->setFormatter($columnFormatter);
180
        }
181
182 2
        return $this;
183
    }
184
185
    /**
186
     * Add classes to the table.
187
     *
188
     * @param string $classes
189
     *
190
     * @return $this
191
     */
192 1
    public function classes(string $classes): DataTable
193
    {
194 1
        $this->_classes = $classes;
195
196 1
        return $this;
197
    }
198
199
    /**
200
     * Add a relation to the table.
201
     *
202
     * @param array $relations
203
     * @return $this
204
     */
205 3
    public function with(array $relations): DataTable
206
    {
207 3
        $this->_relations += $relations;
208
209 3
        return $this;
210
    }
211
212
    /**
213
     * Set the HTML which should be displayed when the dataset is empty.
214
     *
215
     * @param string $html
216
     * @return DataTable
217
     */
218 1
    public function noDataHtml(string $html): DataTable
219
    {
220 1
        $this->_noDataHtml = $html;
221
222 1
        return $this;
223
    }
224
225
    /**
226
     * Set a view which should be displayed when the dataset is empty.
227
     *
228
     * @param string $viewName
229
     * @return DataTable
230
     */
231 1
    public function noDataView(string $viewName): DataTable
232
    {
233 1
        $this->_noDataHtml = \view($viewName)->render();
234
235 1
        return $this;
236
    }
237
238
    /**
239
     * Renders the table.
240
     *
241
     * @return string
242
     * @throws \RuntimeException
243
     */
244 34
    public function render(): string
245
    {
246 34
        $data = $this->_getData();
247
248 33
        if ($data->count() === 0)
249
        {
250 4
            return $this->_noDataHtml;
251
        }
252
253 29
        $this->_initColumns();
254
255 29
        return $this->_open() . $this->_renderHeaders() . $this->_renderBody($data) . $this->_close();
256
    }
257
258
    /**
259
     * Get data which should be displayed in the table.
260
     *
261
     * @return Collection
262
     *
263
     * @throws \RuntimeException
264
     */
265 34
    private function _getData(): Collection
266
    {
267 34
        if ($this->_queryBuilder === null)
268
        {
269 1
            throw new RuntimeException('Unknown base model!');
270
        }
271
272 33
        $this->_addRelations();
273
274
        /** @var DataComponent $component */
275 33
        foreach ($this->_components as $component)
276
        {
277 13
            $component->transformData();
278
        }
279
280 33
        return $this->_queryBuilder->get();
281
    }
282
283 33
    private function _addRelations()
284
    {
285 33
        if (\count($this->_relations) > 0)
286
        {
287 3
            $this->_queryBuilder->with($this->_relations);
288
        }
289 33
    }
290
291 29
    private function _initColumns()
292
    {
293 29
        if (\count($this->_columns) === 0)
294
        {
295 2
            $this->_columns = $this->_fetchColumns(Schema::getColumnListing($this->_queryBuilder->getQuery()->from));
296
        }
297 29
    }
298
299
    /**
300
     * Starts the table.
301
     *
302
     * @return string
303
     */
304 29
    private function _open(): string
305
    {
306 29
        return '<table class="' . ($this->_classes ?? 'table') . '">';
307
    }
308
309
    /**
310
     * Renders the column headers.
311
     *
312
     * @return string
313
     */
314 29
    private function _renderHeaders(): string
315
    {
316 29
        $html = '<tr>';
317
318
        /** @var Header $header */
319 29
        foreach ($this->_fetchHeaders() as $header)
320
        {
321 29
            $html .= $header->formatArray($this->_headerFormatters, $this->_request)->print();
322
        }
323 29
        $html .= '</tr>';
324
325 29
        return $html;
326
    }
327
328
    /**
329
     * @return array
330
     */
331 29
    private function _fetchHeaders(): array
332
    {
333 29
        return array_map(
334
            function ($column)
335
            {
336
                /** @var Column $column */
337 29
                return new Header($column);
338 29
            },
339 29
            $this->_columns
340
        );
341
    }
342
343
    /**
344
     * Displays the table body.
345
     *
346
     * @param Collection $data
347
     *
348
     * @return string
349
     */
350 29
    private function _renderBody(Collection $data): string
351
    {
352 29
        $html = '';
353 29
        foreach ($data as $row)
354
        {
355 29
            $html .= $this->_renderRow($row);
356
        }
357
358 29
        return $html;
359
    }
360
361
    /**
362
     * Displays a single row.
363
     *
364
     * @param Model $rowModel
365
     *
366
     * @return string
367
     */
368 29
    private function _renderRow(Model $rowModel): string
369
    {
370 29
        $attributes = $rowModel->getAttributes() + $this->_getMutatedAttributes($rowModel, $this->_getColumnNames());
371
372 29
        $html = '<tr>';
373
        /** @var Column $column */
374 29
        foreach ($this->_columns as $column)
375
        {
376 29
            $html .= '<td>' . $column->format($column->getRelation() !== null ? $this->_getColumnValueFromRelation($rowModel, $column) : ($attributes[$column->getName()] ?? '')) . '</td>';
377
        }
378 29
        $html .= '</tr>';
379
380 29
        return $html;
381
    }
382
383
    /**
384
     * Get all the mutated attributes which are needed.
385
     *
386
     * @param Model $model
387
     * @param array $columns
388
     * @return array
389
     */
390 29
    private function _getMutatedAttributes(Model $model, array $columns = []): array
391
    {
392 29
        $attributes = [];
393 29
        foreach (\array_intersect_key($model->getMutatedAttributes(), $columns) as $attribute)
394
        {
395 29
            $attributes[$attribute] = $model->{$attribute};
396
        }
397
398 29
        return $attributes;
399
    }
400
401
    /**
402
     * Get all column names.
403
     *
404
     * @return array
405
     */
406 29
    private function _getColumnNames(): array
407
    {
408
        return \array_map(function ($column)
409
        {
410
            /** @var Column $column */
411 29
            return $column->getName();
412 29
        },
413 29
            $this->_getColumnsWithoutRelations()
414
        );
415
    }
416
417
    /**
418
     * Get only the columns which are attributes from the base model.
419
     *
420
     * @return array
421
     */
422 29
    private function _getColumnsWithoutRelations(): array
423
    {
424 29
        return \array_filter(
425 29
            $this->_columns,
426 29
            function ($column)
427
            {
428
                /** @var Column $column */
429 29
                return $column->getRelation() === null;
430 29
            }
431
        );
432
    }
433
434
    /**
435
     * @param Model $model
436
     * @param Column $column
437
     * @return string
438
     */
439 3
    private function _getColumnValueFromRelation(Model $model, Column $column): string
440
    {
441 3
        $columnRelation = $column->getRelation();
442 3
        $relation = $model->getRelation($columnRelation->name);
443
444 3
        if ($relation instanceof Model)
445
        {
446 1
            return $relation->{$column->getName()};
447
        }
448
449 2
        return $columnRelation->getValue($column->getName(), $relation);
450
    }
451
452
    /**
453
     * Closes the table.
454
     *
455
     * @return string
456
     */
457 29
    private function _close(): string
458
    {
459 29
        return '</table>';
460
    }
461
}