Completed
Push — master ( c48d7b...aaa409 )
by Timo
04:06
created

DataTable   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 401
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 99.12%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 7
dl 0
loc 401
ccs 112
cts 113
cp 0.9912
rs 8.2608
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A model() 0 17 3
A _fetchColumns() 0 15 3
A query() 0 4 1
A columns() 0 6 1
A addComponent() 0 7 1
A formatHeaders() 0 6 1
A formatColumn() 0 19 2
A classes() 0 6 1
A with() 0 6 1
A render() 0 11 3
A _getData() 0 20 4
A _open() 0 4 1
B _renderHeaders() 0 28 3
A _renderBody() 0 10 2
A _renderRow() 0 22 3
A _getMutatedAttributes() 0 10 2
A _getColumnNames() 0 17 1
B _getColumnValueFromRelation() 0 20 5
A _close() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like DataTable often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DataTable, and based on these observations, apply Extract Interface, too.

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
        return $this->_open() . $this->_renderHeaders() . $this->_renderBody($data) . $this->_close();
221
    }
222
223
    /**
224
     * Get data which should be displayed in the table.
225
     *
226
     * @return Collection
227
     *
228
     * @throws \RuntimeException
229
     */
230 31
    private function _getData(): Collection
231
    {
232 31
        if ($this->_queryBuilder === null)
233
        {
234 1
            throw new RuntimeException('Unknown base model!');
235
        }
236
237 30
        if (\count($this->_relations) > 0)
238
        {
239 2
            $this->_queryBuilder->with($this->_relations);
240
        }
241
242
        /** @var DataComponent $component */
243 30
        foreach ($this->_components as $component)
244
        {
245 12
            $component->transformData();
246
        }
247
248 30
        return $this->_queryBuilder->get();
249
    }
250
251
    /**
252
     * Starts the table.
253
     *
254
     * @return string
255
     */
256 27
    private function _open(): string
257
    {
258 27
        return '<table class="' . ($this->_classes ?? 'table') . '">';
259
    }
260
261
    /**
262
     * Renders the column headers.
263
     *
264
     * @return string
265
     */
266 27
    private function _renderHeaders(): string
267
    {
268 27
        if (\count($this->_columns) === 0)
269
        {
270 1
            $this->_columns = $this->_fetchColumns(Schema::getColumnListing($this->_queryBuilder->getQuery()->from));
271
        }
272
273 27
        $headers = array_map(
274
            function ($column)
275
            {
276 27
                return new Header($column);
277 27
            },
278 27
            $this->_columns
279
        );
280
281 27
        $html = '<tr>';
282
283
        /** @var Header $header */
284 27
        foreach ($headers as $header)
285
        {
286 27
            $header->formatArray($this->_headerFormatters, $this->_request);
287
288 27
            $html .= '<th>' . $header->name . '</th>';
289
        }
290 27
        $html .= '</tr>';
291
292 27
        return $html;
293
    }
294
295
    /**
296
     * Displays the table body.
297
     *
298
     * @param Collection $data
299
     *
300
     * @return string
301
     */
302 27
    private function _renderBody(Collection $data): string
303
    {
304 27
        $html = '';
305 27
        foreach ($data as $row)
306
        {
307 27
            $html .= $this->_renderRow($row);
308
        }
309
310 27
        return $html;
311
    }
312
313
    /**
314
     * Displays a single row.
315
     *
316
     * @param Model $rowModel
317
     *
318
     * @return string
319
     */
320 27
    private function _renderRow(Model $rowModel): string
321
    {
322 27
        $attributes = $rowModel->getAttributes() + $this->_getMutatedAttributes($rowModel, $this->_getColumnNames());
323
324 27
        $html = '<tr>';
325
        /** @var Column $column */
326 27
        foreach ($this->_columns as $column)
327
        {
328 27
            if ($column->isRelation())
329
            {
330 2
                $columnValue = $this->_getColumnValueFromRelation($rowModel, $column);
331
            } else
332
            {
333 27
                $columnValue = $column->format($attributes[$column->getName()] ?? '');
334
            }
335
336 27
            $html .= '<td>' . $columnValue . '</td>';
337
        }
338 27
        $html .= '</tr>';
339
340 27
        return $html;
341
    }
342
343
    /**
344
     * Get all the mutated attributes which are needed.
345
     *
346
     * @param Model $model
347
     * @param array $columns
348
     * @return array
349
     */
350 27
    private function _getMutatedAttributes(Model $model, array $columns = []): array
351
    {
352 27
        $attributes = [];
353 27
        foreach (\array_intersect_key($model->getMutatedAttributes(), $columns) as $attribute)
354
        {
355 27
            $attributes[$attribute] = $model->{$attribute};
356
        }
357
358 27
        return $attributes;
359
    }
360
361
    /**
362
     * Get all column names.
363
     *
364
     * @return array
365
     */
366 27
    private function _getColumnNames(): array
367
    {
368
        return \array_map(function ($column)
369
        {
370
            /** @var Column $column */
371 27
            return $column->getName();
372 27
        },
373
            \array_filter(
374 27
                $this->_columns,
375 27
                function ($column)
376
                {
377
                    /** @var Column $column */
378 27
                    return !$column->isRelation();
379 27
                }
380
            )
381
        );
382
    }
383
384
    /**
385
     * @param Model $model
386
     * @param Column $column
387
     * @return string
388
     */
389 2
    private function _getColumnValueFromRelation(Model $model, Column $column): string
390
    {
391 2
        $aggregate = $column->getAggregate();
392 2
        $aggregateFunctionSet = $aggregate !== 'first';
393 2
        $relation = $model->getRelation($column->getRelation());
394
395 2
        if ($relation === null)
396
        {
397
            return '';
398
        }
399
400 2
        $columnName = $column->getName();
401 2
        $columnValue = $relation instanceof Collection ? $relation->{$aggregate}($aggregateFunctionSet ? $columnName : null) : '1';
402 2
        if (!$aggregateFunctionSet)
403
        {
404 1
            $columnValue = (string) $relation->first()->{$columnName};
405
        }
406
407 2
        return $columnValue;
408
    }
409
410
    /**
411
     * Closes the table.
412
     *
413
     * @return string
414
     */
415 27
    private function _close(): string
416
    {
417 27
        return '</table>';
418
    }
419
}