Completed
Push — master ( bbcbbc...8c42c0 )
by Timo
07:53
created

DataTable   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 99.06%

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 9
dl 0
loc 379
ccs 105
cts 106
cp 0.9906
rs 9
c 0
b 0
f 0

22 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 14 2
A _getData() 0 17 3
A _addRelations() 0 15 3
A _setSelection() 0 19 2
A _getColumnsForSelect() 0 10 1
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 hamburgscleanest\DataTables\Models\Column\Column;
10
use Illuminate\Database\Eloquent\Builder;
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Support\Collection;
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 45
            [$column, $formatter] = $this->_setColumnFormatter($column, $formatter);
79 45
            $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 45
    private function _setColumnFormatter($column, $formatter) : array
91
    {
92 45
        if (\is_int($column))
93
        {
94 43
            $column = $formatter;
95 43
            $formatter = null;
96
        }
97
98 45
        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 39
    public function query() : Builder
113
    {
114 39
        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 += Relationship::createFromArray($this->_model, $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 41
    public function render() : string
278
    {
279 41
        $data = $this->_getData();
280
281 40
        if ($data->count() === 0)
282
        {
283 4
            return $this->_noDataHtml;
284
        }
285
286 36
        return TableRenderer::open($this->_classes) .
287 36
               TableRenderer::renderHeaders($this->_fetchHeaders(), $this->_headerFormatters) .
288 36
               TableRenderer::renderBody($data, $this->_columns) .
289 36
               TableRenderer::close();
290
    }
291
292
    /**
293
     * Get data which should be displayed in the table.
294
     *
295
     * @return Collection
296
     *
297
     * @throws \RuntimeException
298
     */
299 41
    private function _getData() : Collection
300
    {
301 41
        if ($this->_queryBuilder === null)
302
        {
303 1
            throw new RuntimeException('Unknown base model!');
304
        }
305
306 40
        $this->_addRelations();
307
308
        /** @var DataComponent $component */
309 40
        foreach ($this->_components as $component)
310
        {
311 16
            $component->transformData();
312
        }
313
314 40
        return $this->_setSelection()->_queryBuilder->get();
315
    }
316
317 40
    private function _addRelations() : void
318
    {
319 40
        if (\count($this->_relations) === 0)
320
        {
321 36
            return;
322
        }
323
324
        /** @var Relationship $relation */
325 4
        foreach ($this->_relations as $relation)
326
        {
327 4
            $relation->addJoin($this->_queryBuilder);
328
        }
329
330 4
        $this->_queryBuilder->getQuery()->groupBy($this->_model->getTable() . '.' . $this->_model->getKeyName());
331 4
    }
332
333
    /**
334
     * @return DataTable
335
     */
336 40
    private function _setSelection() : DataTable
337
    {
338 40
        $query = $this->_queryBuilder->getQuery();
339
340 40
        $columns = $this->_getColumnsForSelect();
341 40
        if (!empty($columns))
342
        {
343 34
            $query->selectRaw(
344 34
                \implode(',',
345
                    \array_map(function($column) {
346
                        /** @var Column $column */
347 34
                        return $column->getIdentifier();
348 34
                    }, $columns)
349
                )
350
            );
351
        }
352
353 40
        return $this;
354
    }
355
356
    /**
357
     * @return array
358
     */
359 40
    private function _getColumnsForSelect() : array
360
    {
361 40
        return \array_filter(
362 40
            $this->_columns,
363
            function($column) {
364
                /** @var Column $column */
365 35
                return !$column->isMutated();
366 40
            }
367
        );
368
    }
369
370
    /**
371
     * @return array
372
     */
373 36
    private function _fetchHeaders() : array
374
    {
375 36
        return \array_map(
376 36
            function($column) {
377
                /** @var Column $column */
378 34
                return new Header($column->getKey());
379 36
            },
380 36
            $this->_columns
381
        );
382
    }
383
384
    /**
385
     * @param $name
386
     * @return mixed
387
     */
388 2
    public function __get($name)
389
    {
390 2
        if (\array_key_exists($name, $this->_components))
391
        {
392 2
            return $this->_components[$name];
393
        }
394
395
        return $this->{$name};
396
    }
397
}