Completed
Push — master ( 017615...0c1e2b )
by Sébastien
06:17
created

ColumnModel::includeOnly()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

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