Completed
Push — master ( 407812...a7eca7 )
by Timo
03:58
created

DataTable::_fetchColumns()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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