Completed
Push — master ( aaa409...98f154 )
by Timo
04:23
created

DataTable::_initColumns()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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