Completed
Pull Request — master (#16)
by Timo
07:10
created

DataTable::_fetchHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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