Completed
Push — master ( a7eca7...eff6ed )
by Timo
05:40
created

DataTable::model()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 2
nop 2
crap 3
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
    /** @var string */
43
    private $_noDataHtml = '<div>no data</div>';
44
45
    /**
46
     * DataTable constructor.
47
     * @param Request $request
48
     */
49 42
    public function __construct(Request $request)
50
    {
51 42
        $this->_request = $request;
52 42
    }
53
54
    /**
55
     * Set the base model whose data is displayed in the table.
56
     *
57
     * @param string $modelName
58
     * @param array $columns
59
     * @return $this
60
     * @throws \RuntimeException
61
     */
62 41
    public function model(string $modelName, array $columns = []): DataTable
63
    {
64 41
        if (!\class_exists($modelName) || !\is_subclass_of($modelName, Model::class))
65
        {
66 2
            throw new RuntimeException('Class "' . $modelName . '" does not exist or is not an active record!');
67
        }
68
69 39
        $this->_queryBuilder = (new $modelName)->newQuery();
70 39
        $this->_columns = $this->_fetchColumns($columns);
71
72 39
        return $this;
73
    }
74
75
    /**
76
     * Returns an array of Column objects which may be bound to a formatter.
77
     *
78
     * @param array $columns
79
     * @return array
80
     */
81 40
    private function _fetchColumns(array $columns): array
82
    {
83 40
        $columnModels = [];
84 40
        foreach ($columns as $column => $formatter)
85
        {
86 37
            if (\is_int($column))
87
            {
88 35
                $column = $formatter;
89 35
                $formatter = null;
90
            }
91 37
            $columnModels[] = new Column($column, $formatter);
92
        }
93
94 40
        return $columnModels;
95
    }
96
97
    /**
98
     * @return Builder
99
     */
100 18
    public function query(): Builder
101
    {
102 18
        return $this->_queryBuilder;
103
    }
104
105
    /**
106
     * Displayed columns
107
     *
108
     * @param array $columns
109
     * @return $this
110
     */
111 2
    public function columns(array $columns): DataTable
112
    {
113 2
        $this->_columns += $this->_fetchColumns($columns);
114
115 2
        return $this;
116
    }
117
118
    /**
119
     * Add a component to the data table.
120
     * For example a "Paginator" or a "Sorter".
121
     *
122
     * @param DataComponent $component
123
     *
124
     * @return $this
125
     */
126 20
    public function addComponent(DataComponent $component): DataTable
127
    {
128 20
        $component->init($this->_queryBuilder, $this->_request);
129 20
        $this->_components[] = $component;
130
131 20
        return $this;
132
    }
133
134
    /**
135
     * Add a formatter for the column headers.
136
     *
137
     * @param HeaderFormatter $headerFormatter
138
     * @return $this
139
     */
140 8
    public function formatHeaders(HeaderFormatter $headerFormatter): DataTable
141
    {
142 8
        $this->_headerFormatters[] = $headerFormatter;
143
144 8
        return $this;
145
    }
146
147
    /**
148
     * Add a formatter for a column.
149
     *
150
     * @param string $columnName
151
     * @param ColumnFormatter $columnFormatter
152
     * @return DataTable
153
     */
154 2
    public function formatColumn(string $columnName, ColumnFormatter $columnFormatter): DataTable
155
    {
156
        /** @var Column $column */
157 2
        $column = \array_first(
158 2
            $this->_columns,
159
            function ($index, $column) use ($columnName)
160
            {
161
                /** @var Column $column */
162 2
                return $column->getName() === $columnName;
163 2
            }
164
        );
165
166 2
        if ($column !== null)
167
        {
168 2
            $column->setFormatter($columnFormatter);
169
        }
170
171 2
        return $this;
172
    }
173
174
    /**
175
     * Add classes to the table.
176
     *
177
     * @param string $classes
178
     *
179
     * @return $this
180
     */
181 1
    public function classes(string $classes): DataTable
182
    {
183 1
        $this->_classes = $classes;
184
185 1
        return $this;
186
    }
187
188
    /**
189
     * Add a relation to the table.
190
     *
191
     * @param array $relations
192
     * @return $this
193
     */
194 3
    public function with(array $relations): DataTable
195
    {
196 3
        $this->_relations += $relations;
197
198 3
        return $this;
199
    }
200
201
    /**
202
     * Set the HTML which should be displayed when the dataset is empty.
203
     *
204
     * @param string $html
205
     * @return DataTable
206
     */
207
    public function noDataHtml(string $html): DataTable
208
    {
209
        $this->_noDataHtml = $html;
210
211
        return $this;
212
    }
213
214
    /**
215
     * Set a view which should be displayed when the dataset is empty.
216
     *
217
     * @param string $viewName
218
     * @return DataTable
219
     */
220
    public function noDataView(string $viewName): DataTable
221
    {
222
        $this->_noDataHtml = \view($viewName)->render();
1 ignored issue
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

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