AbstractCsvIterator::__construct()   B
last analyzed

Complexity

Conditions 10
Paths 13

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 44
rs 7.6666
c 0
b 0
f 0
cc 10
nc 13
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace itertools;
4
5
use EmptyIterator;
6
use Exception;
7
use InvalidArgumentException;
8
9
abstract class AbstractCsvIterator extends TakeWhileIterator
10
{
11
    protected $options = array();
12
13
    public function __construct(array $options = array())
14
    {
15
        $defaultOptions = array(
16
            'delimiter' => ',',
17
            'enclosure' => '"',
18
            'escape' => '\\',
19
            'hasHeader' => true,
20
            'header' => null,
21
            'ignoreMissingRows' => false, // Deprecated: use ignoreMissingColumns
22
            'ignoreMissingColumns' => false, // columns*
23
            'combineFirstNRowsAsHeader' => 1,
24
            'skipFirstNRows' => 0
25
        );
26
27
        $unknownOptions = array_diff(array_keys($options), array_keys($defaultOptions));
28
        if(count($unknownOptions) != 0) {
29
            throw new InvalidArgumentException('Unknown options specified: ' . implode(', ', $unknownOptions));
30
        }
31
        if(array_key_exists('header', $options) && null !== $options['header'] && ! array_key_exists('hasHeader', $options)) {
32
            $options['hasHeader'] = false;
33
        }
34
        if(array_key_exists('ignoreMissingRows', $options)) {
35
            $options['ignoreMissingColumns'] = $options['ignoreMissingRows'];
36
        }
37
38
        $this->options = array_merge($this->options, $defaultOptions, $options);
39
40
        if($this->options['hasHeader'] || null !== $this->options['header']) {
41
            if(null === $this->options['header']) {
42
                $it = $this->getLineIteratorWithHeaderInFirstLines();
43
            } else {
44
                $i = 0;
45
                while($this->options['skipFirstNRows'] > $i) {
46
                    $i++;
47
                    $this->retrieveNextCsvRow();
48
                }
49
50
                $it = $this->getLineIteratorWithHeader($this->options['header']);
51
            }
52
        } else {
53
            $it = $this->getLineIteratorWithoutHeader();
54
        }
55
        parent::__construct($it, function($r) { return $r !== false; });
56
    }
57
58
    abstract public function retrieveNextCsvRow();
59
60
    protected function getLineIteratorWithoutHeader()
61
    {
62
        return new SliceIterator(new CallbackIterator(array($this, 'retrieveNextCsvRow')), $this->options['skipFirstNRows']);
63
    }
64
65
    protected function getLineIteratorWithHeader($header)
66
    {
67
        if(false === $header || null === $header) {
68
            return new EmptyIterator();
69
        }
70
        $nextRowRetriever = array($this, 'retrieveNextCsvRow');
71
        $options = $this->options;
72
73
        if ($options['hasHeader'] && null !== $options['header']) {
74
            $this->retrieveNextCsvRow();
75
        }
76
        return new CallbackIterator(function() use ($nextRowRetriever, $header, $options) {
77
            $row = call_user_func($nextRowRetriever);
78
            if(false === $row || array(null) === $row) {
79
                return false;
80
            }
81
            if(count($header) == count($row)) {
82
                return array_combine($header, $row);
83
            }
84
            if(! $options['ignoreMissingColumns']) {
85
                throw new InvalidCsvException('Your headers and columns do not match');
86
            }
87
            if(count($header) < count($row)) {
88
                $row = array_slice($row, 0, count($header));
89
            } else {
90
                $row = array_merge($row, array_fill(0, count($header) - count($row), null));
91
            }
92
            return array_combine($header, $row);
93
        });
94
    }
95
96
    protected function getLineIteratorWithHeaderInFirstLines()
97
    {
98
        $headers = array_map(array($this, 'retrieveNextCsvRow'), range(1, $this->options['skipFirstNRows'] + $this->options['combineFirstNRowsAsHeader']));
99
        $combinedHeader = null;
100
        $i = 0;
101
        foreach($headers as $header) {
102
            if($this->options['skipFirstNRows'] > $i) {
103
                $i++;
104
                continue;
105
            }
106
107
            if(false === $header || null === $header) {
108
                return new EmptyIterator();
109
            }
110
            if(null === $combinedHeader) {
111
                $combinedHeader = array_fill_keys(array_keys($header), '');
112
            }
113
            $previousNonEmptyColumnTitle = '';
114
            foreach($header as $i => $columnTitle) {
115
                if(! array_key_exists($i, $header)) {
116
                    if(! $this->options['ignoreMissingColumns']) {
117
                        throw new InvalidCsvException('You provided a csv with missing columns');
118
                    } else {
119
                        continue;
120
                    }
121
                }
122
                $combinedHeader[$i] .=
123
                    ('' == trim($combinedHeader[$i]) || '' == trim($columnTitle) ? '' : ' ') .
124
                    ('' == trim($columnTitle) ? $previousNonEmptyColumnTitle : $columnTitle);
125
                if('' != trim($columnTitle)) {
126
                    $previousNonEmptyColumnTitle = $columnTitle;
127
                }
128
            }
129
        }
130
        if(null === $combinedHeader) {
131
            throw new Exception('Unable to get header');
132
        }
133
        // make columns unique (double columns become empty)
134
        $previousColumns = array();
135
        foreach($combinedHeader as & $name) {
136
            if(array_key_exists($name, $previousColumns)) {
137
                $name = '';
138
            }
139
            $previousColumns[$name] = '';
140
        }
141
        return $this->getLineIteratorWithHeader($combinedHeader);
142
    }
143
}
144
145