Completed
Push — master ( bc8cb2...0bbbf0 )
by Timo
05:53
created

DataTable   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 403
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 99.16%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 9
dl 0
loc 403
ccs 118
cts 119
cp 0.9916
rs 8.3999
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A query() 0 4 1
A columns() 0 6 1
A model() 0 13 2
A _fetchColumns() 0 11 2
A _setColumnFormatter() 0 10 2
A getColumns() 0 4 1
A addComponent() 0 12 2
A _getComponentName() 0 9 2
A componentExists() 0 4 1
A formatHeaders() 0 6 1
A formatColumn() 0 18 2
A classes() 0 6 1
A with() 0 6 1
A noDataHtml() 0 6 1
A noDataView() 0 6 1
A render() 0 16 2
A _getData() 0 17 3
A _addRelations() 0 14 3
A _addJoin() 0 12 1
A _setSelection() 0 18 2
A _getColumnsForSelect() 0 9 1
A _initColumns() 0 7 2
A _fetchHeaders() 0 10 1
A __get() 0 9 2
1
<?php
2
3
namespace hamburgscleanest\DataTables\Models;
4
5
use hamburgscleanest\DataTables\Exceptions\MultipleComponentAssertionException;
6
use hamburgscleanest\DataTables\Facades\TableRenderer;
7
use hamburgscleanest\DataTables\Interfaces\ColumnFormatter;
8
use hamburgscleanest\DataTables\Interfaces\HeaderFormatter;
9
use Illuminate\Database\Eloquent\Builder;
10
use Illuminate\Database\Eloquent\Model;
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 Builder */
22
    private $_queryBuilder;
23
24
    /** @var array */
25
    private $_headerFormatters = [];
26
27
    /** @var array */
28
    private $_components = [];
29
30
    /** @var string */
31
    private $_classes;
32
33
    /** @var array */
34
    private $_columns = [];
35
36
    /** @var Model */
37
    private $_model;
38
39
    /** @var array */
40
    private $_relations = [];
41
42
    /** @var string */
43
    private $_noDataHtml = '<div>no data</div>';
44
45
    /**
46
     * Set the base model whose data is displayed in the table.
47
     *
48
     * @param string $modelName
49
     * @param array $columns
50
     * @return $this
51
     * @throws \RuntimeException
52
     */
53 59
    public function model(string $modelName, array $columns = []) : DataTable
54
    {
55 59
        if (!\is_subclass_of($modelName, Model::class))
56
        {
57 2
            throw new RuntimeException('Class "' . $modelName . '" is not an active record!');
58
        }
59
60 57
        $this->_model = new $modelName;
61 57
        $this->_queryBuilder = $this->_model->newQuery();
62 57
        $this->_columns = $this->_fetchColumns($columns);
63
64 57
        return $this;
65
    }
66
67
    /**
68
     * Returns an array of Column objects which may be bound to a formatter.
69
     *
70
     * @param array $columns
71
     * @return array
72
     */
73 58
    private function _fetchColumns(array $columns) : array
74
    {
75 58
        $columnModels = [];
76 58
        foreach ($columns as $column => $formatter)
77
        {
78 47
            [$column, $formatter] = $this->_setColumnFormatter($column, $formatter);
79 47
            $columnModels[] = new Column($column, $formatter, $this->_model);
80
        }
81
82 58
        return $columnModels;
83
    }
84
85
    /**
86
     * @param $column
87
     * @param $formatter
88
     * @return array
89
     */
90 47
    private function _setColumnFormatter($column, $formatter) : array
91
    {
92 47
        if (\is_int($column))
93
        {
94 45
            $column = $formatter;
95 45
            $formatter = null;
96
        }
97
98 47
        return [$column, $formatter];
99
    }
100
101
    /**
102
     * @return array
103
     */
104 5
    public function getColumns() : array
105
    {
106 5
        return $this->_columns;
107
    }
108
109
    /**
110
     * @return Builder
111
     */
112 38
    public function query() : Builder
113
    {
114 38
        return $this->_queryBuilder;
115
    }
116
117
    /**
118
     * Displayed columns
119
     *
120
     * @param array $columns
121
     * @return $this
122
     */
123 3
    public function columns(array $columns) : DataTable
124
    {
125 3
        $this->_columns += $this->_fetchColumns($columns);
126
127 3
        return $this;
128
    }
129
130
    /**
131
     * Add a component to the data table.
132
     * For example a "Paginator" or a "Sorter".
133
     *
134
     * @param DataComponent $component
135
     *
136
     * @param null|string $name
137
     * @return DataTable
138
     * @throws \hamburgscleanest\DataTables\Exceptions\MultipleComponentAssertionException
139
     */
140 32
    public function addComponent(DataComponent $component, ? string $name = null) : DataTable
141
    {
142 32
        $componentName = $this->_getComponentName($component, $name);
143 32
        if ($this->componentExists($componentName))
144
        {
145 1
            throw new MultipleComponentAssertionException();
146
        }
147
148 32
        $this->_components[$componentName] = $component->init($this);
149
150 32
        return $this;
151
    }
152
153
    /**
154
     * @param DataComponent $component
155
     * @param null|string $name
156
     * @return string
157
     */
158 32
    private function _getComponentName(DataComponent $component, ? string $name = null) : string
159
    {
160 32
        if ($name !== null)
161
        {
162 2
            return \str_replace(' ', '', \mb_strtolower($name));
163
        }
164
165 30
        return $component->getName();
166
    }
167
168
    /**
169
     * Check whether a component exists for the given data table.
170
     * @param string $componentName
171
     * @return bool
172
     */
173 32
    public function componentExists(string $componentName) : bool
174
    {
175 32
        return \array_key_exists($componentName, $this->_components);
176
    }
177
178
    /**
179
     * Add a formatter for the column headers.
180
     *
181
     * @param HeaderFormatter $headerFormatter
182
     * @return DataTable
183
     */
184 12
    public function formatHeaders(HeaderFormatter $headerFormatter) : DataTable
185
    {
186 12
        $this->_headerFormatters[] = $headerFormatter;
187
188 12
        return $this;
189
    }
190
191
    /**
192
     * Add a formatter for a column.
193
     *
194
     * @param string $columnName
195
     * @param ColumnFormatter $columnFormatter
196
     * @return DataTable
197
     */
198 2
    public function formatColumn(string $columnName, ColumnFormatter $columnFormatter) : DataTable
199
    {
200
        /** @var Column $column */
201 2
        $column = \array_first(
202 2
            $this->_columns,
203
            function($index, $column) use ($columnName) {
204
                /** @var Column $column */
205 2
                return $column->getName() === $columnName;
206 2
            }
207
        );
208
209 2
        if ($column !== null)
210
        {
211 2
            $column->setFormatter($columnFormatter);
212
        }
213
214 2
        return $this;
215
    }
216
217
    /**
218
     * Add classes to the table.
219
     *
220
     * @param string $classes
221
     *
222
     * @return $this
223
     */
224 1
    public function classes(string $classes) : DataTable
225
    {
226 1
        $this->_classes = $classes;
227
228 1
        return $this;
229
    }
230
231
    /**
232
     * Add a relation to the table.
233
     *
234
     * @param array $relations
235
     * @return DataTable
236
     */
237 4
    public function with(array $relations) : DataTable
238
    {
239 4
        $this->_relations += $relations;
240
241 4
        return $this;
242
    }
243
244
    /**
245
     * Set the HTML which should be displayed when the dataset is empty.
246
     *
247
     * @param string $html
248
     * @return DataTable
249
     */
250 1
    public function noDataHtml(string $html) : DataTable
251
    {
252 1
        $this->_noDataHtml = $html;
253
254 1
        return $this;
255
    }
256
257
    /**
258
     * Set a view which should be displayed when the dataset is empty.
259
     *
260
     * @param string $viewName
261
     * @return DataTable
262
     * @throws \Throwable
263
     */
264 1
    public function noDataView(string $viewName) : DataTable
265
    {
266 1
        $this->_noDataHtml = \view($viewName)->render();
267
268 1
        return $this;
269
    }
270
271
    /**
272
     * Renders the table.
273
     *
274
     * @return string
275
     * @throws \RuntimeException
276
     */
277 40
    public function render() : string
278
    {
279 40
        $data = $this->_getData();
280
281 39
        if ($data->count() === 0)
282
        {
283 4
            return $this->_noDataHtml;
284
        }
285
286 35
        $this->_initColumns();
287
288 35
        return TableRenderer::open($this->_classes) .
289 35
               TableRenderer::renderHeaders($this->_fetchHeaders(), $this->_headerFormatters) .
290 35
               TableRenderer::renderBody($data, $this->_columns) .
291 35
               TableRenderer::close();
292
    }
293
294
    /**
295
     * Get data which should be displayed in the table.
296
     *
297
     * @return Collection
298
     *
299
     * @throws \RuntimeException
300
     */
301 40
    private function _getData() : Collection
302
    {
303 40
        if ($this->_queryBuilder === null)
304
        {
305 1
            throw new RuntimeException('Unknown base model!');
306
        }
307
308 39
        $this->_addRelations();
309
310
        /** @var DataComponent $component */
311 39
        foreach ($this->_components as $component)
312
        {
313 15
            $component->transformData();
314
        }
315
316 39
        return $this->_setSelection()->_queryBuilder->get();
317
    }
318
319 39
    private function _addRelations() : void
320
    {
321 39
        if (\count($this->_relations) === 0)
322
        {
323 35
            return;
324
        }
325
326 4
        foreach ($this->_relations as $relation)
327
        {
328 4
            $this->_addJoin($relation, $this->_model->$relation());
329
        }
330
331 4
        $this->_queryBuilder->getQuery()->groupBy($this->_model->getTable() . '.' . $this->_model->getKeyName());
332 4
    }
333
334
    /**
335
     * @param string $relation
336
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relationship
337
     */
338 4
    private function _addJoin(string $relation, \Illuminate\Database\Eloquent\Relations\Relation $relationship) : void
339
    {
340
        /** @var Model $related */
341 4
        $related = $relationship->getRelated();
342
343 4
        $this->_queryBuilder->join(
344 4
            $related->getTable() . ' AS ' . $relation,
345 4
            $this->_model->getTable() . '.' . $this->_model->getKeyName(),
346 4
            '=',
347 4
            $relation . '.' . $related->getForeignKey()
348
        );
349 4
    }
350
351
    /**
352
     * @return DataTable
353
     */
354 39
    private function _setSelection() : DataTable
355
    {
356 39
        $query = $this->_queryBuilder->getQuery();
357
358 39
        $columns = $this->_getColumnsForSelect();
359 39
        if (!empty($columns))
360
        {
361 33
            $query->selectRaw(
362 33
                \implode(',',
363
                    \array_map(function($column) {
364 33
                        return $column->getIdentifier();
365 33
                    }, $columns)
366
                )
367
            );
368
        }
369
370 39
        return $this;
371
    }
372
373
    /**
374
     * @return array
375
     */
376 39
    private function _getColumnsForSelect() : array
377
    {
378 39
        return \array_filter(
379 39
            $this->_columns,
380
            function($column) {
381 34
                return !$column->isMutated();
382 39
            }
383
        );
384
    }
385
386 35
    private function _initColumns() : void
387
    {
388 35
        if (\count($this->_columns) === 0)
389
        {
390 2
            $this->_columns = $this->_fetchColumns(Schema::getColumnListing($this->_queryBuilder->getQuery()->from));
391
        }
392 35
    }
393
394
    /**
395
     * @return array
396
     */
397 35
    private function _fetchHeaders() : array
398
    {
399 35
        return \array_map(
400 35
            function($column) {
401
                /** @var Column $column */
402 35
                return new Header($column->getKey());
403 35
            },
404 35
            $this->_columns
405
        );
406
    }
407
408
    /**
409
     * @param $name
410
     * @return mixed
411
     */
412 2
    public function __get($name)
413
    {
414 2
        if (\array_key_exists($name, $this->_components))
415
        {
416 2
            return $this->_components[$name];
417
        }
418
419
        return $this->{$name};
420
    }
421
}