Completed
Push — master ( 87eeaa...4d0ec4 )
by Timo
04:22
created

DataTable::model()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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