Completed
Push — master ( 89a2b5...bc8cb2 )
by Timo
08:27
created

DataTable::_addRelations()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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