Completed
Pull Request — master (#161)
by Phil
06:54 queued 03:51
created

TableNode   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 349
Duplicated Lines 5.73 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 86.73%

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 1
dl 20
loc 349
ccs 98
cts 113
cp 0.8673
rs 8.96
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A fromList() 0 11 2
A getNodeType() 0 4 1
A getHash() 0 4 1
A getTable() 0 4 1
A getRows() 0 4 1
A getLines() 0 4 1
A getRow() 10 10 2
A getRowLine() 10 10 2
A __toString() 0 4 1
A getIterator() 0 4 1
A getLine() 0 4 1
B __construct() 0 36 9
A getColumnsHash() 0 12 2
A getRowsHash() 0 10 3
A getColumn() 0 15 3
A getRowAsString() 0 9 2
A getRowAsStringWithWrappedValues() 0 11 2
A getTableAsString() 0 9 2
A mergeRowsFromTable() 0 16 4
A padRight() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like TableNode 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 TableNode, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the Behat Gherkin.
5
 * (c) Konstantin Kudryashov <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Behat\Gherkin\Node;
12
13
use ArrayIterator;
14
use Behat\Gherkin\Exception\NodeException;
15
use Iterator;
16
use IteratorAggregate;
17
18
/**
19
 * Represents Gherkin Table argument.
20
 *
21
 * @author Konstantin Kudryashov <[email protected]>
22
 */
23
class TableNode implements ArgumentInterface, IteratorAggregate
24
{
25
    /**
26
     * @var array
27
     */
28
    private $table;
29
    /**
30
     * @var integer
31
     */
32
    private $maxLineLength = array();
33
34
    /**
35
     * Initializes table.
36
     *
37
     * @param array $table Table in form of [$rowLineNumber => [$val1, $val2, $val3]]
38
     *
39
     * @throws NodeException If the given table is invalid
40
     */
41 250
    public function __construct(array $table)
42
    {
43 250
        $this->table = $table;
44 250
        $columnCount = null;
45
46 250
        foreach ($this->getRows() as $row) {
47
48 235
            if (!is_array($row)) {
49 1
                throw new NodeException('Table is not two-dimensional.');
50
            }
51
52 234
            if ($columnCount === null) {
53 234
                $columnCount = count($row);
54 234
            }
55
56 234
            if (count($row) !== $columnCount) {
57 1
                throw new NodeException('Table does not have same number of columns in every row.');
58
            }
59
60 234
            if (!is_array($row)) {
61
                throw new NodeException('Table is not two-dimensional.');
62
            }
63
64 234
            foreach ($row as $column => $string) {
65 234
                if (!isset($this->maxLineLength[$column])) {
66 234
                    $this->maxLineLength[$column] = 0;
67 234
                }
68
69 234
                if (!is_scalar($string)) {
70 1
                    throw new NodeException('Table is not two-dimensional.');
71
                }
72
73 233
                $this->maxLineLength[$column] = max($this->maxLineLength[$column], mb_strlen($string, 'utf8'));
74 233
            }
75 248
        }
76 247
    }
77
78
    /**
79
     * Creates a table from a given list.
80
     *
81
     * @param array $list One-dimensional array
82
     *
83
     * @return TableNode
84
     *
85
     * @throws NodeException If the given list is not a one-dimensional array
86
     */
87 2
    public static function fromList(array $list)
88
    {
89 2
        if (count($list) !== count($list, COUNT_RECURSIVE)) {
90 1
            throw new NodeException('List is not a one-dimensional array.');
91
        }
92
93 1
        array_walk($list, function (&$item) {
94 1
            $item = array($item);
95 1
        });
96 1
        return new self($list);
97
    }
98
99
    /**
100
     * Returns node type.
101
     *
102
     * @return string
103
     */
104
    public function getNodeType()
105
    {
106
        return 'Table';
107
    }
108
109
    /**
110
     * Returns table hash, formed by columns (ColumnsHash).
111
     *
112
     * @return array
113
     */
114 4
    public function getHash()
115
    {
116 4
        return $this->getColumnsHash();
117
    }
118
119
    /**
120
     * Returns table hash, formed by columns.
121
     *
122
     * @return array
123
     */
124 10
    public function getColumnsHash()
125
    {
126 10
        $rows = $this->getRows();
127 10
        $keys = array_shift($rows);
128
129 10
        $hash = array();
130 10
        foreach ($rows as $row) {
131 8
            $hash[] = array_combine($keys, $row);
132 10
        }
133
134 10
        return $hash;
135
    }
136
137
    /**
138
     * Returns table hash, formed by rows.
139
     *
140
     * @return array
141
     */
142 2
    public function getRowsHash()
143
    {
144 2
        $hash = array();
145
146 2
        foreach ($this->getRows() as $row) {
147 2
            $hash[array_shift($row)] = (1 == count($row)) ? $row[0] : $row;
148 2
        }
149
150 2
        return $hash;
151
    }
152
153
    /**
154
     * Returns numerated table lines.
155
     * Line numbers are keys, lines are values.
156
     *
157
     * @return array
158
     */
159 14
    public function getTable()
160
    {
161 14
        return $this->table;
162
    }
163
164
    /**
165
     * Returns table rows.
166
     *
167
     * @return array
168
     */
169 250
    public function getRows()
170
    {
171 250
        return array_values($this->table);
172
    }
173
174
    /**
175
     * Returns table definition lines.
176
     *
177
     * @return array
178
     */
179 11
    public function getLines()
180
    {
181 11
        return array_keys($this->table);
182
    }
183
184
    /**
185
     * Returns specific row in a table.
186
     *
187
     * @param integer $index Row number
188
     *
189
     * @return array
190
     *
191
     * @throws NodeException If row with specified index does not exist
192
     */
193 17 View Code Duplication
    public function getRow($index)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
    {
195 17
        $rows = $this->getRows();
196
197 17
        if (!isset($rows[$index])) {
198
            throw new NodeException(sprintf('Rows #%d does not exist in table.', $index));
199
        }
200
201 17
        return $rows[$index];
202
    }
203
204
    /**
205
     * Returns specific column in a table.
206
     *
207
     * @param integer $index Column number
208
     *
209
     * @return array
210
     *
211
     * @throws NodeException If column with specified index does not exist
212
     */
213 1
    public function getColumn($index)
214
    {
215 1
        if ($index >= count($this->getRow(0))) {
216
            throw new NodeException(sprintf('Column #%d does not exist in table.', $index));
217
        }
218
219 1
        $rows = $this->getRows();
220 1
        $column = array();
221
222 1
        foreach ($rows as $row) {
223 1
            $column[] = $row[$index];
224 1
        }
225
226 1
        return $column;
227
    }
228
229
    /**
230
     * Returns line number at which specific row was defined.
231
     *
232
     * @param integer $index
233
     *
234
     * @return integer
235
     *
236
     * @throws NodeException If row with specified index does not exist
237
     */
238 10 View Code Duplication
    public function getRowLine($index)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
239
    {
240 10
        $lines = array_keys($this->table);
241
242 10
        if (!isset($lines[$index])) {
243
            throw new NodeException(sprintf('Rows #%d does not exist in table.', $index));
244
        }
245
246 10
        return $lines[$index];
247
    }
248
249
    /**
250
     * Converts row into delimited string.
251
     *
252
     * @param integer $rowNum Row number
253
     *
254
     * @return string
255
     */
256 6
    public function getRowAsString($rowNum)
257
    {
258 6
        $values = array();
259 6
        foreach ($this->getRow($rowNum) as $column => $value) {
260 6
            $values[] = $this->padRight(' ' . $value . ' ', $this->maxLineLength[$column] + 2);
261 6
        }
262
263 6
        return sprintf('|%s|', implode('|', $values));
264
    }
265
266
    /**
267
     * Converts row into delimited string.
268
     *
269
     * @param integer  $rowNum  Row number
270
     * @param callable $wrapper Wrapper function
271
     *
272
     * @return string
273
     */
274
    public function getRowAsStringWithWrappedValues($rowNum, $wrapper)
275
    {
276
        $values = array();
277
        foreach ($this->getRow($rowNum) as $column => $value) {
278
            $value = $this->padRight(' ' . $value . ' ', $this->maxLineLength[$column] + 2);
279
280
            $values[] = call_user_func($wrapper, $value, $column);
281
        }
282
283
        return sprintf('|%s|', implode('|', $values));
284
    }
285
286
    /**
287
     * Converts entire table into string
288
     *
289
     * @return string
290
     */
291 2
    public function getTableAsString()
292
    {
293 2
        $lines = array();
294 2
        for ($i = 0; $i < count($this->getRows()); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
295 2
            $lines[] = $this->getRowAsString($i);
296 2
        }
297
298 2
        return implode("\n", $lines);
299
    }
300
301
    /**
302
     * Returns line number at which table was started.
303
     *
304
     * @return integer
305
     */
306 5
    public function getLine()
307
    {
308 5
        return $this->getRowLine(0);
309
    }
310
311
    /**
312
     * Converts table into string
313
     *
314
     * @return string
315
     */
316
    public function __toString()
317
    {
318
        return $this->getTableAsString();
319
    }
320
321
    /**
322
     * Retrieves a hash iterator.
323
     *
324
     * @return Iterator
325
     */
326 1
    public function getIterator()
327
    {
328 1
        return new ArrayIterator($this->getHash());
329
    }
330
331
    /**
332
     * Obtains and adds rows from another table to the current table.
333
     * The second table should have the same structure as the current one.
334
     * @param TableNode $node
335
     *
336
     * @deprecated remove together with OutlineNode::getExampleTable
337
     */
338 8
    public function mergeRowsFromTable(TableNode $node)
339
    {
340
        // check structure
341 8
        if ($this->getRow(0) !== $node->getRow(0)) {
342 3
            throw new NodeException("Tables have different structure. Cannot merge one into another");
343
        }
344
345 5
        $firstLine = $node->getLine();
346 5
        foreach ($node->getTable() as $line => $value) {
347 5
            if ($line === $firstLine) {
348 5
                continue;
349
            }
350
351 5
            $this->table[$line] = $value;
352 5
        }
353 5
    }
354
355
    /**
356
     * Pads string right.
357
     *
358
     * @param string  $text   Text to pad
359
     * @param integer $length Length
360
     *
361
     * @return string
362
     */
363 6
    protected function padRight($text, $length)
364
    {
365 6
        while ($length > mb_strlen($text, 'utf8')) {
366 6
            $text = $text . ' ';
367 6
        }
368
369 6
        return $text;
370
    }
371
}
372