Completed
Branch master (a2d832)
by Timo
02:40
created

DataTable::renderRow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
namespace hamburgscleanest\DataTables\Models;
4
5
use Closure;
6
use hamburgscleanest\DataTables\Interfaces\HeaderFormatter;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Http\Request;
10
use Illuminate\Support\Collection;
11
use Illuminate\Support\Facades\Schema;
12
use RuntimeException;
13
14
/**
15
 * Class DataTable
16
 * @package hamburgscleanest\DataTables\Models
17
 */
18
class DataTable {
19
20
    /** @var Request */
21
    private $_request;
22
23
    /** @var Builder */
24
    private $_queryBuilder;
25
26
    /** @var array */
27
    private $_headerFormatters = [];
28
29
    /** @var array */
30
    private $_components = [];
31
32
    /** @var Closure */
33
    private $_rowRenderer; // TODO: IColumnFormatter => DateColumnFormatter etc.
34
35
    /** @var string */
36
    private $_classes;
37
38
    /** @var array */
39
    private $_columns = [];
40
41
    /** @var array */
42
    private $_relations = [];
43
44
    /**
45
     * DataTable constructor.
46
     * @param Request $request
47
     */
48 10
    public function __construct(Request $request)
1 ignored issue
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
49
    {
50 10
        $this->_request = $request;
51 10
    }
52
53
    /**
54
     * Set the base model whose data is displayed in the table.
55
     *
56
     * @param string $modelName
57
     * @param array $columns
58
     * @return $this
59
     * @throws \RuntimeException
60
     */
61 10
    public function model(string $modelName, array $columns = [])
62
    {
63 10
        if (!\class_exists($modelName))
64
        {
65
            throw new RuntimeException('Class "' . $modelName . '" does not exist!');
66
        }
67
68 10
        if (!\is_subclass_of($modelName, Model::class))
69
        {
70
            throw new RuntimeException('"' . $modelName . '" is not an active record!');
71
        }
72
73 10
        $this->_queryBuilder = (new $modelName)->newQuery();
74 10
        $this->_columns = $columns;
75
76 10
        return $this;
77
    }
78
79
    /**
80
     * @return Builder
81
     */
82 6
    public function query()
83
    {
84 6
        return $this->_queryBuilder;
85
    }
86
87
    /**
88
     * Displayed columns
89
     *
90
     * @param array $columns
91
     * @return $this
92
     */
93
    public function columns(array $columns)
94
    {
95
        $this->_columns += $columns;
96
97
        return $this;
98
    }
99
100
    /**
101
     * Manipulate each rendered row.
102
     *
103
     * @param Closure $customRowRenderer
104
     * @return $this
105
     */
106 1
    public function renderRow(Closure $customRowRenderer)
107
    {
108 1
        $this->_rowRenderer = $customRowRenderer;
109
110 1
        return $this;
111
    }
112
113
    /**
114
     * Add a component to the data table.
115
     * For example a "Paginator" or a "Sorter".
116
     *
117
     * @param DataComponent $component
118
     *
119
     * @return $this
120
     */
121 4
    public function addComponent(DataComponent $component)
122
    {
123 4
        $component->init($this->_queryBuilder, $this->_request);
124 4
        $this->_components[] = $component;
125
126 4
        return $this;
127
    }
128
129
    /**
130
     * Add a formatter for the column headers.
131
     *
132
     * @param HeaderFormatter $headerFormatter
133
     * @return $this
134
     */
135
    public function formatHeaders(HeaderFormatter $headerFormatter)
136
    {
137
        $this->_headerFormatters[] = $headerFormatter;
138
139
        return $this;
140
    }
141
142
    /**
143
     * Get data which should be displayed in the table.
144
     *
145
     * @return \Illuminate\Database\Eloquent\Collection
146
     *
147
     * @throws \RuntimeException
148
     */
149 9
    private function _getData()
150
    {
151 9
        if ($this->_queryBuilder === null)
152
        {
153
            throw new RuntimeException('Unknown base model!');
154
        }
155
156 9
        if (\count($this->_relations) > 0)
157
        {
158
            $this->_queryBuilder->with($this->_relations);
159
        }
160
161
        /** @var DataComponent $component */
162 9
        foreach ($this->_components as $component)
163
        {
164 3
            $component->transformData();
165
        }
166
167 9
        return $this->_queryBuilder->get();
168
    }
169
170
    /**
171
     * Add classes to the table.
172
     *
173
     * @param string $classes
174
     *
175
     * @return $this
176
     */
177 1
    public function classes(string $classes)
178
    {
179 1
        $this->_classes = $classes;
180
181 1
        return $this;
182
    }
183
184
    /**
185
     * Renders the column headers.
186
     *
187
     * @return string
188
     */
189 7
    private function _renderHeaders()
190
    {
191 7
        if (\count($this->_columns) === 0)
192
        {
193
            $this->_columns = Schema::getColumnListing($this->_queryBuilder->getQuery()->from);
194
        }
195
196 7
        $headers = array_map(
197 7
            function($name)
198
            {
199 7
                return new Header($name);
200 7
            },
201 7
            $this->_columns
202
        );
203
204 7
        $html = '<tr>';
205
206
        /** @var Header $header */
207 7
        foreach ($headers as $header)
208
        {
209 7
            $header->formatArray($this->_headerFormatters, $this->_request);
210
211 7
            $html .= '<th>' . $header->name . '</th>';
212
        }
213 7
        $html .= '</tr>';
214
215 7
        return $html;
216
    }
217
218
    /**
219
     * Displays the table body.
220
     *
221
     * @param Collection $data
222
     *
223
     * @return string
224
     */
225 7
    private function _renderBody(Collection $data)
226
    {
227 7
        $html = '';
228 7
        foreach ($data as $row)
229
        {
230 7
            $html .= $this->_renderRow($row);
231
        }
232
233 7
        return $html;
234
    }
235
236
    /**
237
     * Get all the mutated attributes which are needed.
238
     *
239
     * @param Model $model
240
     * @param array $columns
241
     * @return array
242
     */
243 7
    private function _getMutatedAttributes(Model $model, array $columns = []): array
244
    {
245 7
        $attributes = [];
246 7
        foreach (\array_intersect_key($model->getMutatedAttributes(), $columns) as $attribute)
247
        {
248 7
            $attributes[$attribute] = $model->{$attribute};
249
        }
250
251 7
        return $attributes;
252
    }
253
254
    /**
255
     * Displays a single row.
256
     *
257
     * @param Model $rowModel
258
     *
259
     * @return string
260
     */
261 7
    private function _renderRow(Model $rowModel)
262
    {
263 7
        if ($this->_rowRenderer !== null)
264
        {
265 1
            $rowModel = $this->_rowRenderer->call($this, $rowModel);
266
        }
267
268 7
        $attributes = $rowModel->getAttributes() + $this->_getMutatedAttributes($rowModel, $this->_columns);
269
270 7
        $html = '<tr>';
271 7
        foreach ($this->_columns as $column)
272
        {
273 7
            $html .= '<td>' . ($attributes[$column] ?? '') . '</td>';
274
        }
275 7
        $html .= '</tr>';
276
277 7
        return $html;
278
    }
279
280
    /**
281
     * Add a relation to the table.
282
     *
283
     * @param array $relations
284
     * @return $this
285
     */
286
    public function with(array $relations)
287
    {
288
        $this->_relations += $relations;
289
290
        return $this;
291
    }
292
293
    /**
294
     * Starts the table.
295
     *
296
     * @return string
297
     */
298 7
    private function _open()
299
    {
300 7
        return '<table class="' . ($this->_classes ?? 'table') . '">';
301
    }
302
303
    /**
304
     * Closes the table.
305
     *
306
     * @return string
307
     */
308 7
    private function _close()
309
    {
310 7
        return '</table>';
311
    }
312
313
    /**
314
     * Renders the table.
315
     *
316
     * @param null|Closure $noDataView
317
     *
318
     * @return string
319
     * @throws \RuntimeException
320
     */
321 9
    public function render(Closure $noDataView = null)
322
    {
323 9
        $data = $this->_getData();
324
325 9
        if ($data->count() === 0)
326
        {
327 2
            return $noDataView !== null ? $noDataView->call($this) : '<div>no data</div>';
328
        }
329
330 7
        return $this->_open() . $this->_renderHeaders() . $this->_renderBody($data) . $this->_close();
331
    }
332
}