Completed
Push — master ( f5e876...d97b85 )
by Timo
13s
created

DataTable::cache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
ccs 0
cts 3
cp 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 2
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 hamburgscleanest\DataTables\Models\Cache\Cache;
10
use hamburgscleanest\DataTables\Models\Cache\NoCache;
11
use hamburgscleanest\DataTables\Models\Column\Column;
12
use Illuminate\Database\Eloquent\Builder;
13
use Illuminate\Database\Eloquent\Model;
14
use Illuminate\Support\Collection;
15
use RuntimeException;
16
17
/**
18
 * Class DataTable
19
 * @package hamburgscleanest\DataTables\Models
20
 */
21
class DataTable {
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
    /** @var Cache */
48
    private $_cache;
49
50
    /**
51
     * Set the base model whose data is displayed in the table.
52
     *
53
     * @param string $modelName
54
     * @param array $columns
55
     * @param Cache|null $cache
56
     * @return $this|DataTable
57
     * @throws \RuntimeException
58
     */
59 71
    public function model(string $modelName, array $columns = [], Cache $cache = null) : DataTable
60
    {
61 71
        if (!\is_subclass_of($modelName, Model::class))
62
        {
63 2
            throw new RuntimeException('Class "' . $modelName . '" is not an active record!');
64
        }
65
66 69
        $this->_model = new $modelName;
67 69
        $this->_queryBuilder = $this->_model->newQuery();
68 69
        $this->_columns = $this->_fetchColumns($columns);
69 69
        $this->_cache = $cache ?? new NoCache();
1 ignored issue
show
Documentation Bug introduced by
It seems like $cache ?? new \hamburgsc...\Models\Cache\NoCache() can also be of type object<hamburgscleanest\...s\Models\Cache\NoCache>. However, the property $_cache is declared as type object<hamburgscleanest\...les\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...
70
71 69
        return $this;
72
    }
73
74
    /**
75
     * Returns an array of Column objects which may be bound to a formatter.
76
     *
77
     * @param array $columns
78
     * @return array
79
     */
80 70
    private function _fetchColumns(array $columns) : array
81
    {
82 70
        $columnModels = [];
83 70
        foreach ($columns as $column => $formatter)
84
        {
85 57
            [$column, $formatter] = $this->_setColumnFormatter($column, $formatter);
86 57
            $columnModels[] = new Column($column, $formatter, $this->_model);
87
        }
88
89 70
        return $columnModels;
90
    }
91
92
    /**
93
     * @param $column
94
     * @param $formatter
95
     * @return array
96
     */
97 57
    private function _setColumnFormatter($column, $formatter) : array
98
    {
99 57
        if (\is_int($column))
100
        {
101 55
            $column = $formatter;
102 55
            $formatter = null;
103
        }
104
105 57
        return [$column, $formatter];
106
    }
107
108
    /**
109
     * @param Cache $cache
110
     * @return DataTable
111
     */
112
    public function cache(Cache $cache) : DataTable
113
    {
114
        $this->_cache = $cache;
115
116
        return $this;
117
    }
118
119
    /**
120
     * @return array
121
     */
122 5
    public function getColumns() : array
123
    {
124 5
        return $this->_columns;
125
    }
126
127
    /**
128
     * @return Builder
129
     */
130 51
    public function query() : Builder
131
    {
132 51
        return $this->_queryBuilder;
133
    }
134
135
    /**
136
     * Displayed columns
137
     *
138
     * @param array $columns
139
     * @return $this
140
     */
141 3
    public function columns(array $columns) : DataTable
142
    {
143 3
        $this->_columns += $this->_fetchColumns($columns);
144
145 3
        return $this;
146
    }
147
148
    /**
149
     * Add a component to the data table.
150
     * For example a "Paginator" or a "Sorter".
151
     *
152
     * @param DataComponent $component
153
     *
154
     * @param null|string $name
155
     * @return DataTable
156
     * @throws \hamburgscleanest\DataTables\Exceptions\MultipleComponentAssertionException
157
     */
158 33
    public function addComponent(DataComponent $component, ? string $name = null) : DataTable
159
    {
160 33
        $componentName = $this->_getComponentName($component, $name);
161 33
        if ($this->componentExists($componentName))
162
        {
163 1
            throw new MultipleComponentAssertionException();
164
        }
165
166 33
        $this->_components[$componentName] = $component->init($this);
167
168 33
        return $this;
169
    }
170
171
    /**
172
     * @param DataComponent $component
173
     * @param null|string $name
174
     * @return string
175
     */
176 33
    private function _getComponentName(DataComponent $component, ? string $name = null) : string
177
    {
178 33
        if ($name !== null)
179
        {
180 2
            return \str_replace(' ', '', \mb_strtolower($name));
181
        }
182
183 31
        return $component->getName();
184
    }
185
186
    /**
187
     * Check whether a component exists for the given data table.
188
     * @param string $componentName
189
     * @return bool
190
     */
191 33
    public function componentExists(string $componentName) : bool
192
    {
193 33
        return \array_key_exists($componentName, $this->_components);
194
    }
195
196
    /**
197
     * Add a formatter for the column headers.
198
     *
199
     * @param HeaderFormatter $headerFormatter
200
     * @return DataTable
201
     */
202 12
    public function formatHeaders(HeaderFormatter $headerFormatter) : DataTable
203
    {
204 12
        $this->_headerFormatters[] = $headerFormatter;
205
206 12
        return $this;
207
    }
208
209
    /**
210
     * Add a formatter for a column.
211
     *
212
     * @param string $columnName
213
     * @param ColumnFormatter $columnFormatter
214
     * @return DataTable
215
     */
216 10
    public function formatColumn(string $columnName, ColumnFormatter $columnFormatter) : DataTable
217
    {
218
        /** @var Column $column */
219 10
        $column = \array_first(
220 10
            $this->_columns,
221
            function($column) use ($columnName) {
222
                /** @var Column $column */
223 10
                return $column->getName() === $columnName;
224 10
            }
225
        );
226
227 10
        if ($column !== null)
228
        {
229 10
            $column->setFormatter($columnFormatter);
230
        }
231
232 10
        return $this;
233
    }
234
235
    /**
236
     * Add classes to the table.
237
     *
238
     * @param string $classes
239
     *
240
     * @return $this
241
     */
242 1
    public function classes(string $classes) : DataTable
243
    {
244 1
        $this->_classes = $classes;
245
246 1
        return $this;
247
    }
248
249
    /**
250
     * Add a relation to the table.
251
     *
252
     * @param array $relations
253
     * @return DataTable
254
     */
255 4
    public function with(array $relations) : DataTable
256
    {
257 4
        $this->_relations += Relationship::createFromArray($this->_model, $relations);
258
259 4
        return $this;
260
    }
261
262
    /**
263
     * Set the HTML which should be displayed when the dataset is empty.
264
     *
265
     * @param string $html
266
     * @return DataTable
267
     */
268 1
    public function noDataHtml(string $html) : DataTable
269
    {
270 1
        $this->_noDataHtml = $html;
271
272 1
        return $this;
273
    }
274
275
    /**
276
     * Set a view which should be displayed when the dataset is empty.
277
     *
278
     * @param string $viewName
279
     * @return DataTable
280
     * @throws \Throwable
281
     */
282 1
    public function noDataView(string $viewName) : DataTable
283
    {
284 1
        $this->_noDataHtml = \view($viewName)->render();
285
286 1
        return $this;
287
    }
288
289
    /**
290
     * Renders the table.
291
     *
292
     * @return string
293
     * @throws \RuntimeException
294
     */
295 52
    public function render() : string
296
    {
297 52
        if ($this->_queryBuilder === null)
298
        {
299 1
            throw new RuntimeException('Unknown base model!');
300
        }
301
302
        $data = $this->_cache->retrieve(function() { return $this->_getData(); });
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
303 51
        if ($data->count() === 0)
304
        {
305 5
            return $this->_noDataHtml;
306
        }
307
308 46
        return TableRenderer::open($this->_classes) .
309 46
               TableRenderer::renderHeaders($this->_fetchHeaders(), $this->_headerFormatters) .
310 46
               TableRenderer::renderBody($data, $this->_columns) .
311 46
               TableRenderer::close();
312
    }
313
314
    /**
315
     * Get data which should be displayed in the table.
316
     *
317
     * @return Collection
318
     */
319 51
    private function _getData() : Collection
320
    {
321 51
        $this->_addRelations();
322
323
        /** @var DataComponent $component */
324 51
        foreach ($this->_components as $component)
325
        {
326 18
            $component->transformData();
327
        }
328
329 51
        return $this->_setSelection()->_queryBuilder->get();
330
    }
331
332 51
    private function _addRelations() : void
333
    {
334 51
        if (\count($this->_relations) === 0)
335
        {
336 47
            return;
337
        }
338
339
        /** @var Relationship $relation */
340 4
        foreach ($this->_relations as $relation)
341
        {
342 4
            $relation->addJoin($this->_queryBuilder);
343
        }
344
345 4
        $this->_queryBuilder->getQuery()->groupBy($this->_model->getTable() . '.' . $this->_model->getKeyName());
346 4
    }
347
348
    /**
349
     * @return DataTable
350
     */
351 51
    private function _setSelection() : DataTable
352
    {
353 51
        $query = $this->_queryBuilder->getQuery();
354
355 51
        $columns = $this->_getColumnsForSelect();
356 51
        if (!empty($columns))
357
        {
358 45
            $query->selectRaw(
359 45
                \implode(',',
360
                    \array_map(function($column) {
361
                        /** @var Column $column */
362 45
                        return $column->getIdentifier();
363 45
                    }, $columns)
364
                )
365
            );
366
        }
367
368 51
        return $this;
369
    }
370
371
    /**
372
     * @return array
373
     */
374 51
    private function _getColumnsForSelect() : array
375
    {
376 51
        return \array_filter(
377 51
            $this->_columns,
378
            function($column) {
379
                /** @var Column $column */
380 46
                return !$column->isMutated();
381 51
            }
382
        );
383
    }
384
385
    /**
386
     * @return array
387
     */
388 46
    private function _fetchHeaders() : array
389
    {
390 46
        return \array_map(
391 46
            function($column) {
392
                /** @var Column $column */
393 44
                return new Header($column->getKey());
394 46
            },
395 46
            $this->_columns
396
        );
397
    }
398
399
    /**
400
     * @param $name
401
     * @return mixed
402
     */
403 2
    public function __get($name)
404
    {
405 2
        if (\array_key_exists($name, $this->_components))
406
        {
407 2
            return $this->_components[$name];
408
        }
409
410
        return $this->{$name};
411
    }
412
}