Completed
Push — master ( 469abd...76f272 )
by Timo
04:05
created

DataTable::model()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
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 $_columnFormatters = [];
32
33
    /** @var array */
34
    private $_components = [];
35
36
    /** @var string */
37
    private $_classes;
38
39
    /** @var array */
40
    private $_columns = [];
41
42
    /** @var array */
43
    private $_relations = [];
44
45
    /**
46
     * DataTable constructor.
47
     * @param Request $request
48
     */
49 34
    public function __construct(Request $request)
50
    {
51 34
        $this->_request = $request;
52 34
    }
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 33
    public function model(string $modelName, array $columns = []): DataTable
63
    {
64 33
        if (!\class_exists($modelName))
65
        {
66 1
            throw new RuntimeException('Class "' . $modelName . '" does not exist!');
67
        }
68
69 32
        if (!\is_subclass_of($modelName, Model::class))
70
        {
71 1
            throw new RuntimeException('"' . $modelName . '" is not an active record!');
72
        }
73
74 31
        $this->_queryBuilder = (new $modelName)->newQuery();
75 31
        $this->_columns = $this->_fetchColumns($columns);
76
77 31
        return $this;
78
    }
79
80
    /**
81
     * Returns an array of Column objects which may be bound to a formatter.
82
     *
83
     * @param array $columns
84
     * @return array
85
     */
86 32
    private function _fetchColumns(array $columns): array
87
    {
88 32
        $columnModels = [];
89 32
        foreach ($columns as $column => $formatter)
90
        {
91 29
            if (\is_int($column))
92
            {
93 28
                $column = $formatter;
94 28
                $formatter = null;
95
            }
96 29
            $columnModels[] = new Column($column, $formatter);
97
        }
98
99 32
        return $columnModels;
100
    }
101
102
    /**
103
     * @return Builder
104
     */
105 14
    public function query(): Builder
106
    {
107 14
        return $this->_queryBuilder;
108
    }
109
110
    /**
111
     * Displayed columns
112
     *
113
     * @param array $columns
114
     * @return $this
115
     */
116 1
    public function columns(array $columns): DataTable
117
    {
118 1
        $this->_columns += $this->_fetchColumns($columns);
119
120 1
        return $this;
121
    }
122
123
    /**
124
     * Add a component to the data table.
125
     * For example a "Paginator" or a "Sorter".
126
     *
127
     * @param DataComponent $component
128
     *
129
     * @return $this
130
     */
131 18
    public function addComponent(DataComponent $component): DataTable
132
    {
133 18
        $component->init($this->_queryBuilder, $this->_request);
134 18
        $this->_components[] = $component;
135
136 18
        return $this;
137
    }
138
139
    /**
140
     * Add a formatter for the column headers.
141
     *
142
     * @param HeaderFormatter $headerFormatter
143
     * @return $this
144
     */
145 6
    public function formatHeaders(HeaderFormatter $headerFormatter): DataTable
146
    {
147 6
        $this->_headerFormatters[] = $headerFormatter;
148
149 6
        return $this;
150
    }
151
152
    /**
153
     * Add a formatter for a column.
154
     *
155
     * @param string $columnName
156
     * @param ColumnFormatter $columnFormatter
157
     * @return DataTable
158
     */
159 2
    public function formatColumn(string $columnName, ColumnFormatter $columnFormatter): DataTable
160
    {
161
        /** @var Column $column */
162 2
        $column = \array_first(
163 2
            $this->_columns,
164
            function ($index, $column) use ($columnName)
165
            {
166 2
                return $column->name === $columnName;
167 2
            }
168
        );
169
170 2
        if ($column !== null)
171
        {
172 2
            $column->setFormatter($columnFormatter);
173
        }
174
175 2
        return $this;
176
    }
177
178
    /**
179
     * Add classes to the table.
180
     *
181
     * @param string $classes
182
     *
183
     * @return $this
184
     */
185 1
    public function classes(string $classes): DataTable
186
    {
187 1
        $this->_classes = $classes;
188
189 1
        return $this;
190
    }
191
192
    /**
193
     * Add a relation to the table.
194
     *
195
     * @param array $relations
196
     * @return $this
197
     */
198
    public function with(array $relations): DataTable
199
    {
200
        $this->_relations += $relations;
201
202
        return $this;
203
    }
204
205
    /**
206
     * Renders the table.
207
     *
208
     * @param null|Closure $noDataView
209
     *
210
     * @return string
211
     * @throws \RuntimeException
212
     */
213 26
    public function render(Closure $noDataView = null): string
214
    {
215 26
        $data = $this->_getData();
216
217 25
        if ($data->count() === 0)
218
        {
219 3
            return $noDataView !== null ? $noDataView->call($this) : '<div>no data</div>';
220
        }
221
222 22
        return $this->_open() . $this->_renderHeaders() . $this->_renderBody($data) . $this->_close();
223
    }
224
225
    /**
226
     * Get data which should be displayed in the table.
227
     *
228
     * @return Collection
229
     *
230
     * @throws \RuntimeException
231
     */
232 26
    private function _getData(): Collection
233
    {
234 26
        if ($this->_queryBuilder === null)
235
        {
236 1
            throw new RuntimeException('Unknown base model!');
237
        }
238
239 25
        if (\count($this->_relations) > 0)
240
        {
241
            $this->_queryBuilder->with($this->_relations);
242
        }
243
244
        /** @var DataComponent $component */
245 25
        foreach ($this->_components as $component)
246
        {
247 12
            $component->transformData();
248
        }
249
250 25
        return $this->_queryBuilder->get();
251
    }
252
253
    /**
254
     * Starts the table.
255
     *
256
     * @return string
257
     */
258 22
    private function _open(): string
259
    {
260 22
        return '<table class="' . ($this->_classes ?? 'table') . '">';
261
    }
262
263
    /**
264
     * Renders the column headers.
265
     *
266
     * @return string
267
     */
268 22
    private function _renderHeaders(): string
269
    {
270 22
        if (\count($this->_columns) === 0)
271
        {
272 1
            $this->_columns = $this->_fetchColumns(Schema::getColumnListing($this->_queryBuilder->getQuery()->from));
273
        }
274
275 22
        $headers = array_map(
276
            function ($column)
277
            {
278 22
                return Header::createFromColumn($column);
279 22
            },
280 22
            $this->_columns
281
        );
282
283 22
        $html = '<tr>';
284
285
        /** @var Header $header */
286 22
        foreach ($headers as $header)
287
        {
288 22
            $header->formatArray($this->_headerFormatters, $this->_request);
289
290 22
            $html .= '<th>' . $header->name . '</th>';
291
        }
292 22
        $html .= '</tr>';
293
294 22
        return $html;
295
    }
296
297
    /**
298
     * Displays the table body.
299
     *
300
     * @param Collection $data
301
     *
302
     * @return string
303
     */
304 22
    private function _renderBody(Collection $data): string
305
    {
306 22
        $html = '';
307 22
        foreach ($data as $row)
308
        {
309 22
            $html .= $this->_renderRow($row);
310
        }
311
312 22
        return $html;
313
    }
314
315
    /**
316
     * Displays a single row.
317
     *
318
     * @param Model $rowModel
319
     *
320
     * @return string
321
     */
322 22
    private function _renderRow(Model $rowModel): string
323
    {
324 22
        $attributes = $rowModel->getAttributes() + $this->_getMutatedAttributes($rowModel, $this->_getColumnNames());
325
326 22
        $html = '<tr>';
327
        /** @var Column $column */
328 22
        foreach ($this->_columns as $column)
329
        {
330 22
            $html .= '<td>' . $column->format($attributes[$column->name] ?? '') . '</td>';
331
        }
332 22
        $html .= '</tr>';
333
334 22
        return $html;
335
    }
336
337
    /**
338
     * Get all the mutated attributes which are needed.
339
     *
340
     * @param Model $model
341
     * @param array $columns
342
     * @return array
343
     */
344 22
    private function _getMutatedAttributes(Model $model, array $columns = []): array
345
    {
346 22
        $attributes = [];
347 22
        foreach (\array_intersect_key($model->getMutatedAttributes(), $columns) as $attribute)
348
        {
349 22
            $attributes[$attribute] = $model->{$attribute};
350
        }
351
352 22
        return $attributes;
353
    }
354
355
    /**
356
     * Get all column names.
357
     *
358
     * @return array
359
     */
360
    private function _getColumnNames(): array
361
    {
362
        return \array_map(function ($column) { return $column->name; }, $this->_columns);
363
    }
364
365
    /**
366
     * Closes the table.
367
     *
368
     * @return string
369
     */
370 22
    private function _close(): string
371
    {
372 22
        return '</table>';
373
    }
374
}