Completed
Push — master ( da17d7...92c457 )
by Sébastien
11:03
created

ColumnModel   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 341
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 8
dl 0
loc 341
ccs 112
cts 112
cp 1
rs 8.5599
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getFormatters() 0 11 3
A __construct() 0 5 1
A addRowRenderer() 0 15 3
A getRowRenderers() 0 4 1
A getUniqueFormatters() 0 22 5
B add() 0 38 10
A exists() 0 8 2
A getExcluded() 0 11 3
A get() 0 8 2
A sort() 0 21 4
A exclude() 0 9 1
A includeOnly() 0 24 4
A getColumns() 0 11 4
A setFormatter() 0 6 1
A search() 0 8 2
A setMetatadata() 0 6 1
A getMetadata() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ColumnModel often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ColumnModel, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * soluble-flexstore library
7
 *
8
 * @author    Vanvelthem Sébastien
9
 * @link      https://github.com/belgattitude/soluble-flexstore
10
 * @copyright Copyright (c) 2016-2017 Vanvelthem Sébastien
11
 * @license   MIT License https://github.com/belgattitude/soluble-flexstore/blob/master/LICENSE.md
12
 *
13
 */
14
15
namespace Soluble\FlexStore\Column;
16
17
use Soluble\FlexStore\Renderer\RowRendererInterface;
18
use Soluble\FlexStore\Column\ColumnModel\Search;
19
use Soluble\FlexStore\Formatter\FormatterInterface;
20
use Soluble\Metadata\ColumnsMetadata;
21
use ArrayObject;
22
23
class ColumnModel
24
{
25
    const ADD_COLUMN_AFTER = 'after';
26
    const ADD_COLUMN_BEFORE = 'before';
27
28
    /**
29
     * @var ArrayObject
30
     */
31
    protected $columns;
32
33
    /**
34
     * @var Search
35
     */
36
    protected $search;
37
38
    /**
39
     * @var ArrayObject
40
     */
41
    protected $row_renderers;
42
43
    /**
44
     * @var ColumnsMetadata|null
45
     */
46 32
    protected $metadata;
47
48 32
    public function __construct()
49 32
    {
50 32
        $this->columns = new ArrayObject();
51
        $this->row_renderers = new ArrayObject();
52
    }
53
54
    /**
55
     * Add a row renderer.
56
     *
57
     * @throws Exception\InvalidArgumentException
58
     *
59 6
     * @param RowRendererInterface $renderer
60
     */
61
    public function addRowRenderer(RowRendererInterface $renderer): void
62 6
    {
63
        // Test if all required columns are present in column model
64 6
        $required_columns = $renderer->getRequiredColumns();
65 2
66 1
        foreach ($required_columns as $column) {
67 1
            if (!$this->exists($column)) {
68 2
                $cls = get_class($renderer);
69
                $msg = "Renderer '$cls' requires column '$column' to be present in column model.";
70
                throw new Exception\MissingColumnException(__METHOD__ . ': ' . $msg);
71
            }
72 5
        }
73 5
74
        $this->row_renderers->append($renderer);
75
    }
76
77
    /**
78 9
     * @return ArrayObject
79
     */
80 9
    public function getRowRenderers()
81
    {
82
        return $this->row_renderers;
83
    }
84
85
    /**
86
     * Return an array object containing all
87
     * columns that have a formatter (FormatterInterface).
88
     * [column_name] => [FormatterInterface].
89
     *
90
     * @see self::getUniqueFormatters()
91
     */
92 9
    public function getFormatters(): ArrayObject
93
    {
94 9
        $arr = new ArrayObject();
95 9
        foreach ($this->columns as $key => $column) {
96 9
            if (($formatter = $column->getFormatter()) !== null) {
97 9
                $arr->offsetSet($key, $formatter);
98
            }
99
        }
100
101 9
        return $arr;
102
    }
103
104
    /**
105
     * This method returns unique formatters set in the column model
106
     * in an ArrayObject.
107
     *
108
     *
109
     * @param bool $include_excluded_columns
110
     *
111
     * @see self::getFormatters()
112
     */
113
    public function getUniqueFormatters(bool $include_excluded_columns = false): ArrayObject
114
    {
115 9
        $unique = new ArrayObject();
116
117 9
        $formatters = $this->getFormatters();
118
        foreach ($formatters as $column => $formatter) {
119 9
            if ($include_excluded_columns || !$this->get($column)->isExcluded()) {
120 9
                $hash = spl_object_hash($formatter);
121 2
                if (!$unique->offsetExists($hash)) {
122 2
                    $tmp = new ArrayObject([
123 2
                                                'formatter' => $formatter,
124 2
                                                'columns' => new ArrayObject([$column])
125 2
                    ]);
126 2
                    $unique->offsetSet($hash, $tmp);
127
                } else {
128 2
                    $unique->offsetGet($hash)->offsetGet('columns')->append($column);
129
                }
130 2
            }
131
        }
132
133
        return $unique;
134
    }
135 9
136
    /**
137
     * Add a new column to the column model.
138
     *
139
     * @throws Exception\InvalidArgumentException when mode is not supported
140
     * @throws Exception\DuplicateColumnException when column name already exists
141
     * @throws Exception\ColumnNotFoundException  when after_column does not exists
142
     *
143
     * @param string $after_column add the new column after this existing one
144
     * @param string $mode         change after to before (see self::ADD_COLUMN_AFTER, self::ADD_COLUMN_BEFORE)
145
     */
146
    public function add(Column $column, string $after_column = null, string $mode = self::ADD_COLUMN_AFTER): self
147
    {
148
        $name = $column->getName();
149
        if ($this->exists($name)) {
150
            $msg = "Cannot add column '$name', it's already present in column model";
151 32
            throw new Exception\DuplicateColumnException(__METHOD__ . ': ' . $msg);
152
        }
153 32
154 32
        if ($after_column !== null) {
155 1
            // Test existence of column
156 1
            if (!$this->exists($after_column)) {
157
                $msg = "Cannot add column '$name' after '$after_column', column does not exists.";
158
                throw new Exception\ColumnNotFoundException(__METHOD__ . ': ' . $msg);
159 32
            }
160
161 2
            if (!in_array($mode, [self::ADD_COLUMN_BEFORE, self::ADD_COLUMN_AFTER], true)) {
162 1
                $msg = "Cannot add column '$name', invalid mode specified '$mode'";
163 1
                throw new Exception\InvalidArgumentException(__METHOD__ . ': ' . $msg);
164
            }
165
166 2
            $new_columns = new ArrayObject();
167 1
            foreach ($this->columns as $key => $col) {
168 1
                if ($mode === self::ADD_COLUMN_BEFORE && $key === $after_column) {
169
                    $new_columns->offsetSet($name, $column);
170
                }
171 2
                $new_columns->offsetSet($key, $col);
172 2
                if ($mode === self::ADD_COLUMN_AFTER && $key === $after_column) {
173 2
                    $new_columns->offsetSet($name, $column);
174 1
                }
175
            }
176 2
            $this->columns->exchangeArray($new_columns);
177 2
        } else {
178 2
            // Simply append
179
            $this->columns->offsetSet($name, $column);
180
        }
181 2
182
        return $this;
183
    }
184 32
185
    /**
186
     * Tells whether a column exists.
187 32
     *
188
     * @throws Exception\InvalidArgumentException
189
     */
190
    public function exists(string $column): bool
191
    {
192
        if (trim($column) === '') {
193
            throw new Exception\InvalidArgumentException(__METHOD__ . ' Column name cannot be empty');
194
        }
195
196
        return $this->columns->offsetExists($column);
197
    }
198
199 32
    /**
200
     * Return column that have been excluded in getData() and getColumns().
201 32
     *
202 2
     * @return array
203
     */
204 32
    public function getExcluded(): array
205 1
    {
206
        $arr = [];
207
        foreach ($this->columns as $name => $column) {
208 32
            if ($column->isExcluded()) {
209
                $arr[] = $name;
210
            }
211
        }
212
213
        return $arr;
214
    }
215
216 4
    /**
217
     * Return column from identifier name.
218 4
     *
219 4
     * @param string $column column name
220 4
     *
221 4
     * @throws Exception\InvalidArgumentException
222
     * @throws Exception\ColumnNotFoundException  when column does not exists in model
223
     */
224
    public function get($column): Column
225 4
    {
226
        if (!$this->exists($column)) {
227
            throw new Exception\ColumnNotFoundException(__METHOD__ . " Column '$column' not present in column model.");
228
        }
229
230
        return $this->columns->offsetGet($column);
231
    }
232
233
    /**
234
     * Sort columns in the order specified, columns that exists
235
     * in the dataset but not in the sorted_columns will be
236
     * appended to the end.
237
     *
238 14
     * @param string[] $sorted_columns
239
     */
240 14
    public function sort(array $sorted_columns): self
241 1
    {
242
        $diff = array_diff_assoc($sorted_columns, array_unique($sorted_columns));
243
        if (count($diff) > 0) {
244 12
            $cols = implode(',', $diff);
245
            throw new Exception\DuplicateColumnException(__METHOD__ . " Duplicate column found in paramter sorted_columns : '$cols'");
246
        }
247
        $columns = [];
248
249
        foreach ($sorted_columns as $idx => $column) {
250
            if (!$this->exists($column)) {
251
                throw new Exception\InvalidArgumentException(__METHOD__ . " Column '$column' does not exists.");
252
            }
253
            $columns[$column] = $this->get($column);
254
        }
255
        // Appending eventual non sorted columns at the end
256 7
        $columns = array_merge($columns, (array) $this->columns);
257
        $this->columns->exchangeArray($columns);
258 7
259 7
        return $this;
260 1
    }
261 1
262
    /**
263 6
     * Set column that must be excluded in getData() and getColumns().
264
     *
265 6
     * @param string[] $excluded_columns column names to exclude
266 6
     * @param bool     $excluded         whether to set exclude to true (default) or false (opposite: include)
267 1
     */
268
    public function exclude(array $excluded_columns, bool $excluded = true): self
269 6
    {
270
        // trim column names automatically
271
        $excluded_columns = array_map('trim', $excluded_columns);
272 5
273 5
        $this->search()->in($excluded_columns)->setExcluded($excluded);
274
275 5
        return $this;
276
    }
277
278
    /**
279
     * Exclude all other columns that the one specified
280
     * Column sort is preserved in getData().
281
     *
282
     * @throws Exception\InvalidArgumentException
283
     *
284
     * @param string[] $include_only_columns
285
     * @param bool     $sort                 automatically apply sortColumns
286
     * @param bool     $preserve_excluded    preserve excluded columns
287
     */
288 7
    public function includeOnly(array $include_only_columns, bool $sort = true, bool $preserve_excluded = true): self
289
    {
290 7
        // trim column
291 1
        $include_only_columns = array_map('trim', $include_only_columns);
292
293
        if ($preserve_excluded) {
294 6
            $previous_excluded_cols = $this->getExcluded();
295
        } else {
296 6
            $previous_excluded_cols = [];
297
        }
298 6
299
        $this->search()->all()->setExcluded(true);
300
        $this->search()->in($include_only_columns)->setExcluded(false);
301
302
        if ($sort) {
303
            $this->sort($include_only_columns);
304
        }
305
306
        if (count($previous_excluded_cols) > 0) {
307
            $this->exclude($previous_excluded_cols);
308
        }
309
310
        return $this;
311
    }
312
313 3
    /**
314
     * Return columns.
315 3
     */
316 3
    public function getColumns($include_excluded_columns = false): ArrayObject
317 1
    {
318
        $arr = new ArrayObject();
319
        foreach ($this->columns as $key => $column) {
320
            if ($include_excluded_columns || !$column->isExcluded()) {
321 2
                $arr->offsetSet($key, $column);
322
            }
323 2
        }
324 2
325
        return $arr;
326 1
    }
327
328
    /**
329 2
     * Set formatter to specific columns.
330 2
     *
331
     * @throws Exception\InvalidArgumentException
332 2
     *
333 2
     * @param FormatterInterface $formatter
334
     * @param string[]           $columns
335
     */
336 2
    public function setFormatter(FormatterInterface $formatter, array $columns): self
337 1
    {
338
        $this->search()->in($columns)->setFormatter($formatter);
339
340 2
        return $this;
341
    }
342
343
    public function search(): Search
344
    {
345
        if ($this->search === null) {
346
            $this->search = new Search($this->columns);
347
        }
348
349
        return $this->search;
350 19
    }
351
352 19
    public function setMetatadata(ColumnsMetadata $metadata): self
353 19
    {
354 19
        $this->metadata = $metadata;
355 19
356
        return $this;
357
    }
358
359 19
    public function getMetadata(): ?ArrayObject
360
    {
361
        return $this->metadata;
362
    }
363
}
364