DataTable::render()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 0
dl 0
loc 21
ccs 11
cts 11
cp 1
crap 3
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
namespace hamburgscleanest\DataTables\Models;
4
5
use hamburgscleanest\DataTables\Exceptions\ColumnNotFoundException;
6
use hamburgscleanest\DataTables\Exceptions\MultipleComponentAssertionException;
7
use hamburgscleanest\DataTables\Exceptions\NotAnActiveRecordException;
8
use hamburgscleanest\DataTables\Exceptions\UnknownBaseModelException;
9
use hamburgscleanest\DataTables\Facades\TableRenderer;
10
use hamburgscleanest\DataTables\Interfaces\ColumnFormatter;
11
use hamburgscleanest\DataTables\Interfaces\HeaderFormatter;
12
use hamburgscleanest\DataTables\Models\Cache\Cache;
13
use hamburgscleanest\DataTables\Models\Cache\NoCache;
14
use hamburgscleanest\DataTables\Models\Column\Column;
15
use hamburgscleanest\DataTables\Models\Column\ColumnCollection;
16
use Illuminate\Database\Eloquent\Builder;
17
use Illuminate\Database\Eloquent\Model;
18
use Illuminate\Support\Collection;
19
20
/**
21
 * Class DataTable
22
 * @package hamburgscleanest\DataTables\Models
23
 */
24
class DataTable {
25
26
    /** @var Builder */
27
    private $_queryBuilder;
28
29
    /** @var array */
30
    private $_headerFormatters = [];
31
32
    /** @var array */
33
    private $_components = [];
34
35
    /** @var string */
36
    private $_classes;
37
38
    /** @var ColumnCollection */
39
    private $_columns;
40
41
    /** @var Model */
42
    private $_model;
43
44
    /** @var array */
45
    private $_relations = [];
46
47
    /** @var string */
48
    private $_noDataHtml = '<div>no data</div>';
49
50
    /** @var Cache */
51
    private $_cache;
52
53
    /**
54
     * Set the base model whose data is displayed in the table.
55
     *
56
     * @param string $modelName
57
     * @param array $columns
58
     * @param Cache|null $cache
59
     * @return DataTable
60
     * @throws NotAnActiveRecordException
61
     */
62 73
    public function model(string $modelName, array $columns = [], Cache $cache = null) : DataTable
63
    {
64 73
        if (!\is_subclass_of($modelName, Model::class))
65
        {
66 2
            throw new NotAnActiveRecordException($modelName);
67
        }
68
69 71
        $this->_model = new $modelName;
70 71
        $this->_queryBuilder = $this->_model->newQuery();
71 71
        $this->_columns = new ColumnCollection($columns, $this->_model);
72 71
        $this->_cache = $cache ?? new NoCache();
0 ignored issues
show
Documentation Bug introduced by
It seems like $cache ?? new hamburgscl...\Models\Cache\NoCache() can also be of type hamburgscleanest\DataTables\Models\Cache\NoCache. However, the property $_cache is declared as type hamburgscleanest\DataTables\Models\Cache\Cache. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
73
74 71
        return $this;
75
    }
76
77
    /**
78
     * @param Cache $cache
79
     * @return DataTable
80
     */
81 1
    public function cache(Cache $cache) : DataTable
82
    {
83 1
        $this->_cache = $cache;
84
85 1
        return $this;
86
    }
87
88
    /**
89
     * @return ColumnCollection
90
     */
91 5
    public function getColumns() : ColumnCollection
92
    {
93 5
        return $this->_columns;
94
    }
95
96
    /**
97
     * @return Builder
98
     */
99 52
    public function query() : Builder
100
    {
101 52
        return $this->_queryBuilder;
102
    }
103
104
    /**
105
     * Displayed columns
106
     *
107
     * @param array $columns
108
     * @return DataTable
109
     * @throws UnknownBaseModelException
110
     */
111 3
    public function columns(array $columns) : DataTable
112
    {
113 3
        if ($this->_columns === null)
114
        {
115 1
            throw new UnknownBaseModelException();
116
        }
117
118 2
        $this->_columns->push($columns);
119
120 2
        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
     * @param null|string $name
130
     * @return DataTable
131
     * @throws MultipleComponentAssertionException
132
     */
133 34
    public function addComponent(DataComponent $component, ? string $name = null) : DataTable
134
    {
135 34
        $componentName = $this->_getComponentName($component, $name);
136 34
        if ($this->componentExists($componentName))
137
        {
138 1
            throw new MultipleComponentAssertionException();
139
        }
140
141 34
        $this->_components[$componentName] = $component->init($this);
142
143 34
        return $this;
144
    }
145
146
    /**
147
     * @param DataComponent $component
148
     * @param null|string $name
149
     * @return string
150
     */
151 34
    private function _getComponentName(DataComponent $component, string $name = null) : string
152
    {
153 34
        if ($name !== null)
154
        {
155 2
            return \str_replace(' ', '', \mb_strtolower($name));
156
        }
157
158 32
        return $component->getName();
159
    }
160
161
    /**
162
     * Check whether a component exists for the given data table.
163
     * @param string $componentName
164
     * @return bool
165
     */
166 34
    public function componentExists(string $componentName) : bool
167
    {
168 34
        return \array_key_exists($componentName, $this->_components);
169
    }
170
171
    /**
172
     * Add a formatter for the column headers.
173
     *
174
     * @param HeaderFormatter $headerFormatter
175
     * @return DataTable
176
     */
177 12
    public function formatHeaders(HeaderFormatter $headerFormatter) : DataTable
178
    {
179 12
        $this->_headerFormatters[] = $headerFormatter;
180
181 12
        return $this;
182
    }
183
184
    /**
185
     * Add a formatter for a column.
186
     *
187
     * @param string $columnName
188
     * @param ColumnFormatter $columnFormatter
189
     * @return DataTable
190
     * @throws ColumnNotFoundException
191
     */
192 11
    public function formatColumn(string $columnName, ColumnFormatter $columnFormatter) : DataTable
193
    {
194
        /** @var Column $column */
195
        $column = $this->_columns->first(function($column) use ($columnName) {
196
            /** @var Column $column */
197 11
            return $column->getName() === $columnName;
198 11
        });
199
200 11
        if ($column === null)
201
        {
202 1
            throw new ColumnNotFoundException($columnName);
203
        }
204
205 10
        $column->setFormatter($columnFormatter);
206
207 10
        return $this;
208
    }
209
210
    /**
211
     * Add classes to the table.
212
     *
213
     * @param string $classes
214
     *
215
     * @return DataTable
216
     */
217 1
    public function classes(string $classes) : DataTable
218
    {
219 1
        $this->_classes = $classes;
220
221 1
        return $this;
222
    }
223
224
    /**
225
     * Add a relation to the table.
226
     *
227
     * @param array $relations
228
     * @return DataTable
229
     */
230 4
    public function with(array $relations) : DataTable
231
    {
232 4
        $this->_relations += Relationship::createFromArray($this->_model, $relations);
233
234 4
        return $this;
235
    }
236
237
    /**
238
     * Set the HTML which should be displayed when the dataset is empty.
239
     *
240
     * @param string $html
241
     * @return DataTable
242
     */
243 1
    public function noDataHtml(string $html) : DataTable
244
    {
245 1
        $this->_noDataHtml = $html;
246
247 1
        return $this;
248
    }
249
250
    /**
251
     * Set a view which should be displayed when the dataset is empty.
252
     *
253
     * @param string $viewName
254
     * @return DataTable
255
     * @throws \Throwable
256
     */
257 1
    public function noDataView(string $viewName) : DataTable
258
    {
259 1
        $this->_noDataHtml = \view($viewName)->render();
260
261 1
        return $this;
262
    }
263
264
    /**
265
     * Renders the table.
266
     *
267
     * @return string
268
     * @throws UnknownBaseModelException
269
     */
270 52
    public function render() : string
271
    {
272 52
        if ($this->_queryBuilder === null)
273
        {
274 1
            throw new UnknownBaseModelException();
275
        }
276
277
        /** @var Collection $data */
278
        $data = $this->_cache->retrieve(function() {
279 51
            return $this->_getData();
280 51
        });
281
282 51
        if ($data->count() === 0)
283
        {
284 5
            return $this->_noDataHtml;
285
        }
286
287 46
        return TableRenderer::open($this->_classes) .
0 ignored issues
show
Bug introduced by
The method open() does not exist on hamburgscleanest\DataTables\Facades\TableRenderer. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

287
        return TableRenderer::/** @scrutinizer ignore-call */ open($this->_classes) .
Loading history...
288 46
               TableRenderer::renderHeaders($this->_columns->getHeaders(), $this->_headerFormatters) .
0 ignored issues
show
Bug introduced by
The method renderHeaders() does not exist on hamburgscleanest\DataTables\Facades\TableRenderer. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

288
               TableRenderer::/** @scrutinizer ignore-call */ 
289
                              renderHeaders($this->_columns->getHeaders(), $this->_headerFormatters) .
Loading history...
289 46
               TableRenderer::renderBody($data, $this->_columns->toArray()) .
0 ignored issues
show
Bug introduced by
The method renderBody() does not exist on hamburgscleanest\DataTables\Facades\TableRenderer. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

289
               TableRenderer::/** @scrutinizer ignore-call */ 
290
                              renderBody($data, $this->_columns->toArray()) .
Loading history...
290 46
               TableRenderer::close();
0 ignored issues
show
Bug introduced by
The method close() does not exist on hamburgscleanest\DataTables\Facades\TableRenderer. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

290
               TableRenderer::/** @scrutinizer ignore-call */ 
291
                              close();
Loading history...
291
    }
292
293
    /**
294
     * Get data which should be displayed in the table.
295
     *
296
     * @return Collection
297
     */
298 51
    private function _getData() : Collection
299
    {
300 51
        $this->_addRelations();
301
302
        /** @var DataComponent $component */
303 51
        foreach ($this->_components as $component)
304
        {
305 18
            $component->transformData();
306
        }
307
308 51
        return $this->_setSelection()->_queryBuilder->get();
309
    }
310
311 51
    private function _addRelations() : void
312
    {
313 51
        if (\count($this->_relations) === 0)
314
        {
315 47
            return;
316
        }
317
318
        /** @var Relationship $relation */
319 4
        foreach ($this->_relations as $relation)
320
        {
321 4
            $relation->addJoin($this->_queryBuilder);
322
        }
323
324 4
        $this->_queryBuilder->getQuery()->groupBy($this->_model->getTable() . '.' . $this->_model->getKeyName());
325 4
    }
326
327
    /**
328
     * @return DataTable
329
     */
330 51
    private function _setSelection() : DataTable
331
    {
332 51
        $query = $this->_queryBuilder->getQuery();
333
334 51
        $columns = $this->_columns->getUnmutatedColumns();
335 51
        if (!empty($columns))
336
        {
337 45
            $query->selectRaw(
338 45
                \implode(',',
339 45
                    \array_map(function($column) {
340
                        /** @var Column $column */
341 45
                        return $column->getIdentifier();
342 45
                    }, $columns)
343
                )
344
            );
345
        }
346
347 51
        return $this;
348
    }
349
350
    /**
351
     * @param $name
352
     * @return mixed
353
     */
354 2
    public function __get($name)
355
    {
356 2
        if (\array_key_exists($name, $this->_components))
357
        {
358 2
            return $this->_components[$name];
359
        }
360
361
        return $this->{$name};
362
    }
363
}